summaryrefslogtreecommitdiff
path: root/src/System.Private.CoreLib/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/System.Private.CoreLib/shared')
-rw-r--r--src/System.Private.CoreLib/shared/Internal/IO/File.Unix.cs25
-rw-r--r--src/System.Private.CoreLib/shared/Internal/IO/File.Windows.cs77
-rw-r--r--src/System.Private.CoreLib/shared/Internal/IO/File.cs77
-rw-r--r--src/System.Private.CoreLib/shared/Internal/Runtime/CompilerServices/Unsafe.cs371
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/Interop.Errors.cs209
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/Interop.IOErrors.cs172
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/Interop.Libraries.cs12
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs33
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Casing.cs23
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs81
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.ICU.cs16
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Idna.cs21
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Locale.cs40
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Normalization.cs19
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.ResultCode.cs18
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs28
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Utils.cs52
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Close.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FLock.cs31
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FSync.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FTruncate.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.GetCwd.cs74
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.GetRandomBytes.cs21
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.LSeek.cs22
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.LockFileRegion.cs21
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.MksTemps.cs17
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Open.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.OpenFlags.cs27
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.PathConf.cs28
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Permissions.cs32
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.PosixFAdvise.cs36
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Read.cs25
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.ReadDir.cs102
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.ReadLink.cs63
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Stat.cs65
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.SysLog.cs58
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Unlink.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Write.cs27
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs44
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Crypt32/Interop.CryptProtectMemory.cs22
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Interop.BOOL.cs21
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Interop.Errors.cs46
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Interop.Libraries.cs16
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CREATEFILE2_EXTENDED_PARAMETERS.cs24
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CancelIoEx.cs16
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CloseHandle.cs16
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CreateFile.cs40
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CreateFile2.cs32
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FILE_INFO_BY_HANDLE_CLASS.cs39
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FileAttributes.cs17
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FileTypes.cs17
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FindClose.cs16
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FindFirstFileEx.cs43
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FlushFileBuffers.cs17
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs112
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FreeEnvironmentStrings.cs14
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FreeLibrary.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetCPInfo.cs24
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetEnvironmentStrings.cs14
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileAttributesEx.cs94
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileInformationByHandleEx.cs25
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileType_SafeHandle.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs18
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs18
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetTempFileNameW.cs14
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetTempPathW.cs14
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.Globalization.cs133
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.LoadLibraryEx.cs18
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.LockFile.cs20
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MAX_PATH.cs11
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MUI.cs18
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MultiByteToWideChar.cs20
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.OutputDebugString.cs14
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.ReadFile_SafeHandle_IntPtr.cs21
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.ReadFile_SafeHandle_NativeOverlapped.cs22
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SECURITY_ATTRIBUTES.cs21
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SecurityOptions.cs18
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetEndOfFile.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetEnvironmentVariable.cs14
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetFilePointerEx.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetThreadErrorMode.cs16
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.TimeZone.Registry.cs31
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.TimeZone.cs94
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WideCharToMultiByte.cs22
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WriteFile_SafeHandle_IntPtr.cs23
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WriteFile_SafeHandle_NativeOverlapped.cs22
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Normaliz/Interop.Idna.cs35
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Normaliz/Interop.Normalization.cs24
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/Ole32/Interop.CoCreateGuid.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysAllocStringLen.cs19
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysFreeString.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysStringLen.cs19
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.Constants.cs12
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.LoadString.cs16
-rw-r--r--src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.SendMessageTimeout.cs15
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/CriticalHandleMinusOneIsInvalid.cs20
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/CriticalHandleZeroOrMinusOneIsInvalid.cs20
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeDirectoryHandle.Unix.cs26
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs145
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs50
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs22
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeHandleMinusOneIsInvalid.cs19
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs19
-rw-r--r--src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeLibraryHandle.cs16
-rw-r--r--src/System.Private.CoreLib/shared/README.md19
-rw-r--r--src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems827
-rw-r--r--src/System.Private.CoreLib/shared/System/AccessViolationException.cs51
-rw-r--r--src/System.Private.CoreLib/shared/System/Action.cs38
-rw-r--r--src/System.Private.CoreLib/shared/System/AggregateException.cs484
-rw-r--r--src/System.Private.CoreLib/shared/System/ApplicationException.cs58
-rw-r--r--src/System.Private.CoreLib/shared/System/ArgumentException.cs97
-rw-r--r--src/System.Private.CoreLib/shared/System/ArgumentNullException.cs55
-rw-r--r--src/System.Private.CoreLib/shared/System/ArgumentOutOfRangeException.cs100
-rw-r--r--src/System.Private.CoreLib/shared/System/ArithmeticException.cs53
-rw-r--r--src/System.Private.CoreLib/shared/System/ArraySegment.cs368
-rw-r--r--src/System.Private.CoreLib/shared/System/ArrayTypeMismatchException.cs53
-rw-r--r--src/System.Private.CoreLib/shared/System/AssemblyLoadEventArgs.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/AssemblyLoadEventHandler.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/AsyncCallback.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/AttributeTargets.cs35
-rw-r--r--src/System.Private.CoreLib/shared/System/AttributeUsageAttribute.cs57
-rw-r--r--src/System.Private.CoreLib/shared/System/BadImageFormatException.cs129
-rw-r--r--src/System.Private.CoreLib/shared/System/BitConverter.cs474
-rw-r--r--src/System.Private.CoreLib/shared/System/Boolean.cs349
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/ArrayPool.cs98
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/ArrayPoolEventSource.cs104
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs265
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/IMemoryOwner.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/IPinnable.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/MemoryHandle.cs59
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/MemoryManager.cs76
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/FormattingHelpers.CountDigits.cs132
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs496
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Utilities.cs40
-rw-r--r--src/System.Private.CoreLib/shared/System/Byte.cs284
-rw-r--r--src/System.Private.CoreLib/shared/System/CLSCompliantAttribute.cs33
-rw-r--r--src/System.Private.CoreLib/shared/System/Char.cs1076
-rw-r--r--src/System.Private.CoreLib/shared/System/CharEnumerator.cs80
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Comparer.cs77
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/CompatibleComparer.cs61
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/DictionaryEntry.cs59
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs1624
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/ICollection.cs34
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/ICollectionDebugView.cs34
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IComparer.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IDictionary.cs50
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IDictionaryDebugView.cs80
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IEnumerable.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IEnumerator.cs26
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IEqualityComparer.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IList.cs36
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyCollection.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyDictionary.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyList.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/KeyNotFoundException.cs36
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/KeyValuePair.cs83
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/List.cs1188
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs34
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs76
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/HashHelpers.SerializationInfoTable.cs29
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/HashHelpers.cs92
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/Hashtable.cs1650
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/ICollection.cs69
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IComparer.cs22
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IDictionary.cs60
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IDictionaryEnumerator.cs68
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IEnumerable.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IEnumerator.cs41
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IEqualityComparer.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IHashCodeProvider.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IList.cs59
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IStructuralComparable.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/IStructuralEquatable.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/KeyValuePairs.cs33
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/ListDictionaryInternal.cs509
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/ObjectModel/Collection.cs396
-rw-r--r--src/System.Private.CoreLib/shared/System/Collections/ObjectModel/ReadOnlyCollection.cs279
-rw-r--r--src/System.Private.CoreLib/shared/System/ComponentModel/DefaultValueAttribute.cs228
-rw-r--r--src/System.Private.CoreLib/shared/System/ComponentModel/EditorBrowsableAttribute.cs48
-rw-r--r--src/System.Private.CoreLib/shared/System/Configuration/Assemblies/AssemblyHashAlgorithm.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Configuration/Assemblies/AssemblyVersionCompatibility.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Convert.Base64.cs217
-rw-r--r--src/System.Private.CoreLib/shared/System/Convert.cs2918
-rw-r--r--src/System.Private.CoreLib/shared/System/CurrentSystemTimeZone.cs197
-rw-r--r--src/System.Private.CoreLib/shared/System/DBNull.cs119
-rw-r--r--src/System.Private.CoreLib/shared/System/DataMisalignedException.cs42
-rw-r--r--src/System.Private.CoreLib/shared/System/DateTime.cs1586
-rw-r--r--src/System.Private.CoreLib/shared/System/DateTimeKind.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/DateTimeOffset.cs1005
-rw-r--r--src/System.Private.CoreLib/shared/System/DayOfWeek.cs26
-rw-r--r--src/System.Private.CoreLib/shared/System/DefaultBinder.cs1221
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/CodeAnalysis/SuppressMessageAttribute.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/ConditionalAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs107
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs350
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggableAttribute.cs54
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerBrowsableAttribute.cs41
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerDisplayAttribute.cs51
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerHiddenAttribute.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerNonUserCodeAttribute.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerStepThroughAttribute.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerStepperBoundaryAttribute.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerTypeProxyAttribute.cs46
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerVisualizerAttribute.cs97
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/StackTraceHiddenAttribute.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs661
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventActivityOptions.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventDescriptor.cs217
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventProvider.cs1323
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs5859
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSourceException.cs54
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/IEventProvider.cs43
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/StubEnvironment.cs373
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs63
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs127
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs358
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs64
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs146
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs76
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs130
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs156
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs320
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs130
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs230
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs96
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs126
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs277
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs303
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs728
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs115
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs349
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs910
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs272
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs391
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs205
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs103
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/Winmeta.cs187
-rw-r--r--src/System.Private.CoreLib/shared/System/DivideByZeroException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/DllNotFoundException.cs45
-rw-r--r--src/System.Private.CoreLib/shared/System/Double.cs453
-rw-r--r--src/System.Private.CoreLib/shared/System/DuplicateWaitObjectException.cs66
-rw-r--r--src/System.Private.CoreLib/shared/System/Empty.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/EntryPointNotFoundException.cs45
-rw-r--r--src/System.Private.CoreLib/shared/System/EventArgs.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/EventHandler.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/ExecutionEngineException.cs50
-rw-r--r--src/System.Private.CoreLib/shared/System/FieldAccessException.cs42
-rw-r--r--src/System.Private.CoreLib/shared/System/FlagsAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/FormatException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/FormattableString.cs81
-rw-r--r--src/System.Private.CoreLib/shared/System/Gen2GcCallback.cs76
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/BidiCategory.cs33
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/Calendar.cs840
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CalendarAlgorithmType.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs363
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Windows.cs469
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CalendarData.cs378
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CalendarWeekRule.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CalendricalCalculationsHelper.cs412
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CharUnicodeInfo.cs397
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/ChineseLunisolarCalendar.cs386
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs249
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs1014
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs644
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs1368
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CultureData.Unix.cs430
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs774
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CultureData.cs2574
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CultureNotFoundException.cs121
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/CultureTypes.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs1393
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs2875
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfoScanner.cs729
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs5668
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DateTimeStyles.cs49
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DaylightTime.cs45
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/DigitShapes.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/EastAsianLunisolarCalendar.cs703
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/GlobalizationExtensions.cs32
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendar.cs584
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendarHelper.cs651
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendarTypes.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/HebrewCalendar.cs1124
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/HebrewNumber.cs457
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Unix.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs94
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.WinRT.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.cs671
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.Unix.cs145
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.Windows.cs128
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.cs902
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/InternalGlobalizationHelper.cs48
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Unix.cs96
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs210
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.WinRT.cs62
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.cs405
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/JapaneseLunisolarCalendar.cs302
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/JulianCalendar.cs439
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/KoreanCalendar.cs262
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/KoreanLunisolarCalendar.cs1316
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/LocaleData.Unix.cs4571
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/Normalization.Unix.cs134
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/Normalization.Windows.cs148
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/NumberFormatInfo.cs842
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/NumberStyles.cs65
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/PersianCalendar.cs601
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/RegionInfo.cs399
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/SortKey.cs175
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/SortVersion.cs99
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/StringInfo.cs350
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TaiwanCalendar.cs281
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TaiwanLunisolarCalendar.cs322
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TextElementEnumerator.cs117
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs57
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Windows.cs65
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TextInfo.cs904
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/ThaiBuddhistCalendar.cs232
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TimeSpanFormat.cs537
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs1704
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/TimeSpanStyles.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/UmAlQuraCalendar.cs858
-rw-r--r--src/System.Private.CoreLib/shared/System/Globalization/UnicodeCategory.cs40
-rw-r--r--src/System.Private.CoreLib/shared/System/Guid.Unix.cs38
-rw-r--r--src/System.Private.CoreLib/shared/System/Guid.Windows.cs29
-rw-r--r--src/System.Private.CoreLib/shared/System/Guid.cs1436
-rw-r--r--src/System.Private.CoreLib/shared/System/HResults.cs128
-rw-r--r--src/System.Private.CoreLib/shared/System/HashCode.cs430
-rw-r--r--src/System.Private.CoreLib/shared/System/IAsyncResult.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/ICloneable.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/IComparable.cs37
-rw-r--r--src/System.Private.CoreLib/shared/System/IConvertible.cs63
-rw-r--r--src/System.Private.CoreLib/shared/System/ICustomFormatter.cs24
-rw-r--r--src/System.Private.CoreLib/shared/System/IDisposable.cs60
-rw-r--r--src/System.Private.CoreLib/shared/System/IEquatable.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/IFormatProvider.cs23
-rw-r--r--src/System.Private.CoreLib/shared/System/IFormattable.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/BinaryWriter.cs441
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/DirectoryNotFoundException.cs42
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/DisableMediaInsertionPrompt.cs36
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/EncodingCache.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/EndOfStreamException.cs36
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Error.cs47
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileAccess.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileLoadException.cs98
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileMode.cs38
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileNotFoundException.cs110
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileOptions.cs33
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileShare.cs45
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.Linux.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.OSX.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs813
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.Win32.cs47
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.WinRT.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs1657
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStream.cs881
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs259
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/IOException.cs42
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs823
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs147
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs286
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.cs771
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs252
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs102
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs428
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.cs208
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathTooLongException.cs38
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PinnedBufferMemoryStream.cs60
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/SeekOrigin.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/StreamHelpers.CopyValidation.cs46
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/StreamReader.cs1422
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs996
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/TextReader.cs412
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/TextWriter.cs870
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryAccessor.cs670
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs882
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs226
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs95
-rw-r--r--src/System.Private.CoreLib/shared/System/IObservable.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/IObserver.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/IProgress.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/ISpanFormattable.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/IndexOutOfRangeException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/InsufficientExecutionStackException.cs35
-rw-r--r--src/System.Private.CoreLib/shared/System/Int16.cs314
-rw-r--r--src/System.Private.CoreLib/shared/System/Int32.cs269
-rw-r--r--src/System.Private.CoreLib/shared/System/Int64.cs257
-rw-r--r--src/System.Private.CoreLib/shared/System/IntPtr.cs235
-rw-r--r--src/System.Private.CoreLib/shared/System/InvalidCastException.cs47
-rw-r--r--src/System.Private.CoreLib/shared/System/InvalidOperationException.cs45
-rw-r--r--src/System.Private.CoreLib/shared/System/InvalidProgramException.cs42
-rw-r--r--src/System.Private.CoreLib/shared/System/InvalidTimeZoneException.cs31
-rw-r--r--src/System.Private.CoreLib/shared/System/Lazy.cs548
-rw-r--r--src/System.Private.CoreLib/shared/System/MarshalByRefObject.cs29
-rw-r--r--src/System.Private.CoreLib/shared/System/Marvin.cs139
-rw-r--r--src/System.Private.CoreLib/shared/System/Math.cs825
-rw-r--r--src/System.Private.CoreLib/shared/System/MathF.cs235
-rw-r--r--src/System.Private.CoreLib/shared/System/MemberAccessException.cs50
-rw-r--r--src/System.Private.CoreLib/shared/System/Memory.cs441
-rw-r--r--src/System.Private.CoreLib/shared/System/MemoryDebugView.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs487
-rw-r--r--src/System.Private.CoreLib/shared/System/MemoryExtensions.cs1389
-rw-r--r--src/System.Private.CoreLib/shared/System/MethodAccessException.cs42
-rw-r--r--src/System.Private.CoreLib/shared/System/MidpointRounding.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/MissingMethodException.cs61
-rw-r--r--src/System.Private.CoreLib/shared/System/MulticastNotSupportedException.cs40
-rw-r--r--src/System.Private.CoreLib/shared/System/NonSerializedAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/NotFiniteNumberException.cs72
-rw-r--r--src/System.Private.CoreLib/shared/System/NotImplementedException.cs43
-rw-r--r--src/System.Private.CoreLib/shared/System/NotSupportedException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/NullReferenceException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/Nullable.cs152
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.Formatting.cs2291
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs31
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.Parsing.cs973
-rw-r--r--src/System.Private.CoreLib/shared/System/Numerics/ConstantHelper.cs142
-rw-r--r--src/System.Private.CoreLib/shared/System/Numerics/ConstantHelper.tt60
-rw-r--r--src/System.Private.CoreLib/shared/System/Numerics/GenerationConfig.ttinclude173
-rw-r--r--src/System.Private.CoreLib/shared/System/Numerics/Register.cs172
-rw-r--r--src/System.Private.CoreLib/shared/System/Numerics/Register.tt46
-rw-r--r--src/System.Private.CoreLib/shared/System/Numerics/Vector.cs5538
-rw-r--r--src/System.Private.CoreLib/shared/System/Numerics/Vector.tt1834
-rw-r--r--src/System.Private.CoreLib/shared/System/Numerics/Vector_Operations.cs865
-rw-r--r--src/System.Private.CoreLib/shared/System/ObjectDisposedException.cs83
-rw-r--r--src/System.Private.CoreLib/shared/System/ObsoleteAttribute.cs60
-rw-r--r--src/System.Private.CoreLib/shared/System/OperationCanceledException.cs74
-rw-r--r--src/System.Private.CoreLib/shared/System/OverflowException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/ParamArrayAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/ParamsArray.cs82
-rw-r--r--src/System.Private.CoreLib/shared/System/ParseNumbers.cs664
-rw-r--r--src/System.Private.CoreLib/shared/System/PlatformNotSupportedException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/Progress.cs105
-rw-r--r--src/System.Private.CoreLib/shared/System/Random.cs263
-rw-r--r--src/System.Private.CoreLib/shared/System/RankException.cs45
-rw-r--r--src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs359
-rw-r--r--src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs277
-rw-r--r--src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs141
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AmbiguousMatchException.cs35
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs201
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyAlgorithmIdAttribute.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyCompanyAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyConfigurationAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyContentType.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyCopyrightAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyCultureAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyDefaultAliasAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyDelaySignAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyDescriptionAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyFileVersionAttribute.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyFlagsAttribute.cs43
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyInformationalVersionAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyKeyFileAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyKeyNameAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyMetadataAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFlags.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs156
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyProductAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblySignatureKeyAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyTitleAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyTrademarkAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/AssemblyVersionAttribute.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/Binder.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/BindingFlags.cs51
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/CallingConventions.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ConstructorInfo.cs40
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/CustomAttributeFormatException.cs34
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/DefaultMemberAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/EventAttributes.cs22
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/EventInfo.cs115
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ExceptionHandlingClauseOptions.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/FieldAttributes.cs40
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/FieldInfo.cs72
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/GenericParameterAttributes.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ICustomAttributeProvider.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/IReflect.cs76
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/IReflectableType.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ImageFileMachine.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/InterfaceMapping.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/IntrospectionExtensions.cs23
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/InvalidFilterCriteriaException.cs34
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ManifestResourceInfo.cs23
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/MemberFilter.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/MemberInfo.cs77
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/MemberTypes.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/MethodAttributes.cs50
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/MethodBase.cs86
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/MethodImplAttributes.cs37
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/MethodInfo.Internal.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/MethodInfo.cs43
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/Missing.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/Module.cs182
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ModuleResolveEventHandler.cs9
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ObfuscateAssemblyAttribute.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ObfuscationAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ParameterAttributes.cs29
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ParameterInfo.cs110
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ParameterModifier.cs35
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/Pointer.cs51
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/PortableExecutableKinds.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ProcessorArchitecture.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/PropertyAttributes.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/PropertyInfo.cs74
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ReflectionContext.cs24
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ReflectionTypeLoadException.cs73
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ResourceAttributes.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/ResourceLocation.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignatureArrayType.cs46
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignatureByRefType.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignatureConstructedGenericType.cs74
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignatureGenericMethodParameterType.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignatureGenericParameterType.cs46
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignatureHasElementType.cs49
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignaturePointerType.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignatureType.cs140
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/SignatureTypeExtensions.cs228
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/StrongNameKeyPair.cs58
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/TargetException.cs34
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/TargetInvocationException.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/TargetParameterCountException.cs36
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/TypeAttributes.cs63
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/TypeDelegator.cs130
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/TypeFilter.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/Reflection/TypeInfo.cs84
-rw-r--r--src/System.Private.CoreLib/shared/System/ResolveEventArgs.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/ResolveEventHandler.cs10
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/IResourceReader.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/MissingManifestResourceException.cs37
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/MissingSatelliteAssemblyException.cs64
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/NeutralResourcesLanguageAttribute.cs33
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/ResourceFallbackManager.cs87
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/ResourceTypeCode.cs58
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/RuntimeResourceSet.cs434
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/SatelliteContractVersionAttribute.cs31
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/UltimateResourceFallbackLocation.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AccessedThroughPropertyAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilderAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncStateMachineAttribute.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs219
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerFilePathAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerLineNumberAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerMemberNameAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilationRelaxations.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilationRelaxationsAttribute.cs22
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilerGeneratedAttribute.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilerGlobalScopeAttribute.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs236
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CustomConstantAttribute.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DateTimeConstantAttribute.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DecimalConstantAttribute.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DefaultDependencyAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DependencyAttribute.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DisablePrivateReflectionAttribute.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DiscardableAttribute.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ExtensionAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FixedAddressValueTypeAttribute.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FixedBufferAttribute.cs32
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FormattableStringFactory.cs58
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IAsyncStateMachine.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/INotifyCompletion.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ITuple.cs22
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IndexerNameAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/InternalsVisibleToAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IntrinsicAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsByRefLikeAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsConst.cs10
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsReadOnlyAttribute.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsVolatile.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IteratorStateMachineAttribute.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/LoadHint.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodCodeType.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodImplAttribute.cs29
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodImplOptions.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ReferenceAssemblyAttribute.cs32
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeCompatibilityAttribute.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeFeature.cs38
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeWrappedException.cs41
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/SpecialNameAttribute.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StateMachineAttribute.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StringFreezingAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StrongBox.cs59
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/SuppressIldasmAttribute.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TupleElementNamesAttribute.cs57
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedToAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/UnsafeValueTypeAttribute.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs223
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/YieldAwaitable.cs202
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/Cer.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/Consistency.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/CriticalFinalizerObject.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/ReliabilityContractAttribute.cs33
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionNotification.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/HandleProcessCorruptedStateExceptionsAttribute.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/BestFitMappingAttribute.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/CallingConvention.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/CharSet.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/ComVisibleAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DefaultCharSetAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DefaultDllImportSearchPathsAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DllImportAttribute.cs26
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DllImportSearchPath.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/ExternalException.cs89
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/FieldOffsetAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/GuidAttribute.cs17
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/HandleRef.cs47
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/InAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/LayoutKind.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MarshalAsAttribute.cs39
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MarshalDirectiveException.cs46
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs232
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs281
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/OptionalAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/OutAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/PreserveSigAttribute.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/SafeBuffer.cs405
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/StructLayoutAttribute.cs26
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/UnmanagedFunctionPointerAttribute.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/UnmanagedType.cs48
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/InteropServices/VarEnum.cs54
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/IDeserializationCallback.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/IFormatterConverter.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/IObjectReference.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/ISafeSerializationData.cs207
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/ISerializable.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnDeserializedAttribute.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnDeserializingAttribute.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnSerializedAttribute.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnSerializingAttribute.cs11
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/OptionalFieldAttribute.cs25
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/SafeSerializationEventArgs.cs31
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/SerializationException.cs40
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/SerializationInfoEnumerator.cs127
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Serialization/StreamingContext.cs52
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Versioning/NonVersionableAttribute.cs36
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/Versioning/TargetFrameworkAttribute.cs46
-rw-r--r--src/System.Private.CoreLib/shared/System/SByte.cs332
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/AllowPartiallyTrustedCallersAttribute.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/CryptographicException.cs45
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/PartialTrustVisibilityLevel.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SafeBSTRHandle.cs82
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecureString.Unix.cs325
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecureString.Windows.cs308
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecureString.cs180
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecurityCriticalAttribute.cs36
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecurityCriticalScope.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecurityException.cs89
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecurityRuleSet.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecurityRulesAttribute.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecuritySafeCriticalAttribute.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecurityTransparentAttribute.cs19
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SecurityTreatAsSafeAttribute.cs32
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/SuppressUnmanagedCodeSecurityAttribute.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/UnverifiableCodeAttribute.cs15
-rw-r--r--src/System.Private.CoreLib/shared/System/Security/VerificationException.cs36
-rw-r--r--src/System.Private.CoreLib/shared/System/SerializableAttribute.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Single.cs443
-rw-r--r--src/System.Private.CoreLib/shared/System/Span.Fast.cs357
-rw-r--r--src/System.Private.CoreLib/shared/System/Span.cs142
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanDebugView.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.BinarySearch.cs83
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs1092
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs341
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.T.cs686
-rw-r--r--src/System.Private.CoreLib/shared/System/SpanHelpers.cs416
-rw-r--r--src/System.Private.CoreLib/shared/System/StackOverflowException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/String.Comparison.cs1007
-rw-r--r--src/System.Private.CoreLib/shared/System/String.Manipulation.cs1848
-rw-r--r--src/System.Private.CoreLib/shared/System/String.Searching.cs534
-rw-r--r--src/System.Private.CoreLib/shared/System/String.cs766
-rw-r--r--src/System.Private.CoreLib/shared/System/StringComparer.cs387
-rw-r--r--src/System.Private.CoreLib/shared/System/StringComparison.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/StringSplitOptions.cs13
-rw-r--r--src/System.Private.CoreLib/shared/System/SystemException.cs35
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/ASCIIEncoding.cs945
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/Decoder.cs351
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/DecoderBestFitFallback.cs242
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/DecoderExceptionFallback.cs155
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/DecoderFallback.cs216
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/DecoderNLS.cs242
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/DecoderReplacementFallback.cs204
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/Encoder.cs346
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/EncoderBestFitFallback.cs242
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/EncoderExceptionFallback.cs195
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/EncoderFallback.cs202
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/EncoderNLS.cs239
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/EncoderReplacementFallback.cs227
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/Encoding.cs1789
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/EncodingInfo.cs71
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/EncodingNLS.cs298
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/EncodingProvider.cs136
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/Latin1Encoding.cs884
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/NormalizationForm.cs14
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/StringBuilder.Debug.cs37
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs2445
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/UTF32Encoding.cs1213
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/UTF7Encoding.cs978
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/UTF8Encoding.cs2561
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/UnicodeEncoding.cs1985
-rw-r--r--src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs257
-rw-r--r--src/System.Private.CoreLib/shared/System/ThreadAttributes.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/ThreadStaticAttribute.cs27
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/AbandonedMutexException.cs78
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ApartmentState.cs16
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/AsyncLocal.cs499
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/AutoResetEvent.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/DeferredDisposableLifetime.cs116
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/EventResetMode.cs22
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs460
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/LazyInitializer.cs288
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/LazyThreadSafetyMode.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/LockRecursionException.cs32
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ManualResetEvent.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ParameterizedThreadStart.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ReaderWriterLockSlim.cs1682
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/SemaphoreFullException.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/SendOrPostCallback.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/SpinWait.cs359
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/SynchronizationLockException.cs45
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs750
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs82
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskCanceledException.cs99
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskCompletionSource.cs349
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskExtensions.cs48
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskSchedulerException.cs78
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskToApm.cs189
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs849
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ThreadAbortException.cs37
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ThreadPriority.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ThreadStart.cs18
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ThreadStartException.cs30
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ThreadState.cs24
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/ThreadStateException.cs46
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/Timeout.cs20
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/TimeoutHelper.cs54
-rw-r--r--src/System.Private.CoreLib/shared/System/Threading/WaitHandleCannotBeOpenedException.cs32
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeSpan.cs564
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZone.cs280
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZoneInfo.AdjustmentRule.cs248
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZoneInfo.StringSerializer.cs625
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZoneInfo.TransitionTime.cs155
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs1633
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs999
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZoneInfo.cs1993
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeZoneNotFoundException.cs31
-rw-r--r--src/System.Private.CoreLib/shared/System/TimeoutException.cs44
-rw-r--r--src/System.Private.CoreLib/shared/System/Tuple.cs1271
-rw-r--r--src/System.Private.CoreLib/shared/System/TupleExtensions.cs930
-rw-r--r--src/System.Private.CoreLib/shared/System/Type.Enum.cs186
-rw-r--r--src/System.Private.CoreLib/shared/System/Type.Helpers.cs527
-rw-r--r--src/System.Private.CoreLib/shared/System/Type.cs395
-rw-r--r--src/System.Private.CoreLib/shared/System/TypeAccessException.cs37
-rw-r--r--src/System.Private.CoreLib/shared/System/TypeCode.cs47
-rw-r--r--src/System.Private.CoreLib/shared/System/TypeInitializationException.cs80
-rw-r--r--src/System.Private.CoreLib/shared/System/TypeUnloadedException.cs37
-rw-r--r--src/System.Private.CoreLib/shared/System/UInt16.cs289
-rw-r--r--src/System.Private.CoreLib/shared/System/UInt32.cs265
-rw-r--r--src/System.Private.CoreLib/shared/System/UInt64.cs262
-rw-r--r--src/System.Private.CoreLib/shared/System/UIntPtr.cs221
-rw-r--r--src/System.Private.CoreLib/shared/System/UnauthorizedAccessException.cs48
-rw-r--r--src/System.Private.CoreLib/shared/System/UnhandledExceptionEventArgs.cs28
-rw-r--r--src/System.Private.CoreLib/shared/System/UnhandledExceptionEventHandler.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/UnitySerializationHolder.cs62
-rw-r--r--src/System.Private.CoreLib/shared/System/ValueTuple.cs2333
-rw-r--r--src/System.Private.CoreLib/shared/System/Version.cs447
-rw-r--r--src/System.Private.CoreLib/shared/System/Void.cs16
771 files changed, 175584 insertions, 0 deletions
diff --git a/src/System.Private.CoreLib/shared/Internal/IO/File.Unix.cs b/src/System.Private.CoreLib/shared/Internal/IO/File.Unix.cs
new file mode 100644
index 0000000000..50fa0f0d0c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Internal/IO/File.Unix.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Internal.IO
+{
+ internal static partial class File
+ {
+ internal static bool InternalExists(string fullPath)
+ {
+ Interop.Sys.FileStatus fileinfo;
+
+ // First use stat, as we want to follow symlinks. If that fails, it could be because the symlink
+ // is broken, we don't have permissions, etc., in which case fall back to using LStat to evaluate
+ // based on the symlink itself.
+ if (Interop.Sys.Stat(fullPath, out fileinfo) < 0 &&
+ Interop.Sys.LStat(fullPath, out fileinfo) < 0)
+ {
+ return false;
+ }
+
+ return ((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) != Interop.Sys.FileTypes.S_IFDIR);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Internal/IO/File.Windows.cs b/src/System.Private.CoreLib/shared/Internal/IO/File.Windows.cs
new file mode 100644
index 0000000000..0acae3b457
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Internal/IO/File.Windows.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32;
+using Microsoft.Win32.SafeHandles;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Internal.IO
+{
+ internal static partial class File
+ {
+ internal static bool InternalExists(string fullPath)
+ {
+ Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = new Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA();
+ int errorCode = FillAttributeInfo(fullPath, ref data, returnErrorOnNotFound: true);
+
+ return (errorCode == 0) && (data.dwFileAttributes != -1)
+ && ((data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0);
+ }
+
+ /// <summary>
+ /// Returns 0 on success, otherwise a Win32 error code. Note that
+ /// classes should use -1 as the uninitialized state for dataInitialized.
+ /// </summary>
+ /// <param name="returnErrorOnNotFound">Return the error code for not found errors?</param>
+ internal static int FillAttributeInfo(string path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound)
+ {
+ int errorCode = Interop.Errors.ERROR_SUCCESS;
+
+ using (DisableMediaInsertionPrompt.Create())
+ {
+ if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data))
+ {
+ errorCode = Marshal.GetLastWin32Error();
+ if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED)
+ {
+ // Files that are marked for deletion will not let you GetFileAttributes,
+ // ERROR_ACCESS_DENIED is given back without filling out the data struct.
+ // FindFirstFile, however, will. Historically we always gave back attributes
+ // for marked-for-deletion files.
+
+ var findData = new Interop.Kernel32.WIN32_FIND_DATA();
+ using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path, ref findData))
+ {
+ if (handle.IsInvalid)
+ {
+ errorCode = Marshal.GetLastWin32Error();
+ }
+ else
+ {
+ errorCode = Interop.Errors.ERROR_SUCCESS;
+ data.PopulateFrom(ref findData);
+ }
+ }
+ }
+ }
+ }
+
+ if (errorCode != Interop.Errors.ERROR_SUCCESS && !returnErrorOnNotFound)
+ {
+ switch (errorCode)
+ {
+ case Interop.Errors.ERROR_FILE_NOT_FOUND:
+ case Interop.Errors.ERROR_PATH_NOT_FOUND:
+ case Interop.Errors.ERROR_NOT_READY: // Removable media not ready
+ // Return default value for backward compatibility
+ data.dwFileAttributes = -1;
+ return Interop.Errors.ERROR_SUCCESS;
+ }
+ }
+
+ return errorCode;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Internal/IO/File.cs b/src/System.Private.CoreLib/shared/Internal/IO/File.cs
new file mode 100644
index 0000000000..2fcc0f391f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Internal/IO/File.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Security;
+using System.IO;
+
+namespace Internal.IO
+{
+ //
+ // Subsetted clone of System.IO.File for internal runtime use.
+ // Keep in sync with https://github.com/dotnet/corefx/tree/master/src/System.IO.FileSystem.
+ //
+ internal static partial class File
+ {
+ // Tests if a file exists. The result is true if the file
+ // given by the specified path exists; otherwise, the result is
+ // false. Note that if path describes a directory,
+ // Exists will return true.
+ public static bool Exists(string path)
+ {
+ try
+ {
+ if (path == null)
+ return false;
+ if (path.Length == 0)
+ return false;
+
+ path = Path.GetFullPath(path);
+
+ // After normalizing, check whether path ends in directory separator.
+ // Otherwise, FillAttributeInfo removes it and we may return a false positive.
+ // GetFullPath should never return null
+ Debug.Assert(path != null, "File.Exists: GetFullPath returned null");
+ if (path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]))
+ {
+ return false;
+ }
+
+ return InternalExists(path);
+ }
+ catch (ArgumentException) { }
+ catch (NotSupportedException) { } // Security can throw this on ":"
+ catch (SecurityException) { }
+ catch (IOException) { }
+ catch (UnauthorizedAccessException) { }
+
+ return false;
+ }
+
+ public static byte[] ReadAllBytes(string path)
+ {
+ // bufferSize == 1 used to avoid unnecessary buffer in FileStream
+ using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1))
+ {
+ long fileLength = fs.Length;
+ if (fileLength > int.MaxValue)
+ throw new IOException(SR.IO_FileTooLong2GB);
+
+ int index = 0;
+ int count = (int)fileLength;
+ byte[] bytes = new byte[count];
+ while (count > 0)
+ {
+ int n = fs.Read(bytes, index, count);
+ if (n == 0)
+ throw Error.GetEndOfFile();
+ index += n;
+ count -= n;
+ }
+ return bytes;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Internal/Runtime/CompilerServices/Unsafe.cs b/src/System.Private.CoreLib/shared/Internal/Runtime/CompilerServices/Unsafe.cs
new file mode 100644
index 0000000000..aeff3ce2ca
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Internal/Runtime/CompilerServices/Unsafe.cs
@@ -0,0 +1,371 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+
+#if BIT64
+using nuint = System.UInt64;
+using nint = System.Int64;
+#else
+using nuint = System.UInt32;
+using nint = System.Int32;
+#endif
+
+//
+// The implementations of most the methods in this file are provided as intrinsics.
+// In CoreCLR, the body of the functions are replaced by the EE with unsafe code. See see getILIntrinsicImplementationForUnsafe for details.
+// In CoreRT, see Internal.IL.Stubs.UnsafeIntrinsics for details.
+//
+
+namespace Internal.Runtime.CompilerServices
+{
+ //
+ // Subsetted clone of System.Runtime.CompilerServices.Unsafe for internal runtime use.
+ // Keep in sync with https://github.com/dotnet/corefx/tree/master/src/System.Runtime.CompilerServices.Unsafe.
+ //
+
+ /// <summary>
+ /// For internal use only. Contains generic, low-level functionality for manipulating pointers.
+ /// </summary>
+ [CLSCompliant(false)]
+ public static unsafe class Unsafe
+ {
+ /// <summary>
+ /// Returns a pointer to the given by-ref parameter.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void* AsPointer<T>(ref T value)
+ {
+ throw new PlatformNotSupportedException();
+
+ // ldarg.0
+ // conv.u
+ // ret
+ }
+
+ /// <summary>
+ /// Returns the size of an object of the given type parameter.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int SizeOf<T>()
+ {
+#if CORECLR
+ typeof(T).ToString(); // Type token used by the actual method body
+#endif
+ throw new PlatformNotSupportedException();
+
+ // sizeof !!0
+ // ret
+ }
+
+ /// <summary>
+ /// Casts the given object to the specified type, performs no dynamic type checking.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T As<T>(object value) where T : class
+ {
+ throw new PlatformNotSupportedException();
+
+ // ldarg.0
+ // ret
+ }
+
+ /// <summary>
+ /// Reinterprets the given reference as a reference to a value of type <typeparamref name="TTo"/>.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TTo As<TFrom, TTo>(ref TFrom source)
+ {
+ throw new PlatformNotSupportedException();
+
+ // ldarg.0
+ // ret
+ }
+
+ /// <summary>
+ /// Adds an element offset to the given reference.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T Add<T>(ref T source, int elementOffset)
+ {
+#if CORECLR
+ typeof(T).ToString(); // Type token used by the actual method body
+ throw new PlatformNotSupportedException();
+#else
+ return ref AddByteOffset(ref source, (IntPtr)(elementOffset * (nint)SizeOf<T>()));
+#endif
+ }
+
+ /// <summary>
+ /// Adds an element offset to the given reference.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T Add<T>(ref T source, IntPtr elementOffset)
+ {
+#if CORECLR
+ typeof(T).ToString(); // Type token used by the actual method body
+ throw new PlatformNotSupportedException();
+#else
+ return ref AddByteOffset(ref source, (IntPtr)((nint)elementOffset * (nint)SizeOf<T>()));
+#endif
+ }
+
+ /// <summary>
+ /// Adds an element offset to the given pointer.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void* Add<T>(void* source, int elementOffset)
+ {
+#if CORECLR
+ typeof(T).ToString(); // Type token used by the actual method body
+ throw new PlatformNotSupportedException();
+#else
+ return (byte*)source + (elementOffset * (nint)SizeOf<T>());
+#endif
+ }
+
+ /// <summary>
+ /// Adds an element offset to the given reference.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static ref T AddByteOffset<T>(ref T source, nuint byteOffset)
+ {
+ return ref AddByteOffset(ref source, (IntPtr)(void*)byteOffset);
+ }
+
+ /// <summary>
+ /// Determines whether the specified references point to the same location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool AreSame<T>(ref T left, ref T right)
+ {
+ throw new PlatformNotSupportedException();
+
+ // ldarg.0
+ // ldarg.1
+ // ceq
+ // ret
+ }
+
+ /// <summary>
+ /// Determines whether the memory address referenced by <paramref name="left"/> is greater than
+ /// the memory address referenced by <paramref name="right"/>.
+ /// </summary>
+ /// <remarks>
+ /// This check is conceptually similar to "(void*)(&amp;left) &gt; (void*)(&amp;right)".
+ /// </remarks>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsAddressGreaterThan<T>(ref T left, ref T right)
+ {
+ throw new PlatformNotSupportedException();
+
+ // ldarg.0
+ // ldarg.1
+ // cgt.un
+ // ret
+ }
+
+ /// <summary>
+ /// Determines whether the memory address referenced by <paramref name="left"/> is less than
+ /// the memory address referenced by <paramref name="right"/>.
+ /// </summary>
+ /// <remarks>
+ /// This check is conceptually similar to "(void*)(&amp;left) &lt; (void*)(&amp;right)".
+ /// </remarks>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsAddressLessThan<T>(ref T left, ref T right)
+ {
+ throw new PlatformNotSupportedException();
+
+ // ldarg.0
+ // ldarg.1
+ // clt.un
+ // ret
+ }
+
+ /// <summary>
+ /// Initializes a block of memory at the given location with a given initial value
+ /// without assuming architecture dependent alignment of the address.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void InitBlockUnaligned(ref byte startAddress, byte value, uint byteCount)
+ {
+ for (uint i = 0; i < byteCount; i++)
+ AddByteOffset(ref startAddress, i) = value;
+ }
+
+ /// <summary>
+ /// Reads a value of type <typeparamref name="T"/> from the given location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadUnaligned<T>(void* source)
+ {
+#if CORECLR
+ typeof(T).ToString(); // Type token used by the actual method body
+ throw new PlatformNotSupportedException();
+#else
+ return Unsafe.As<byte, T>(ref *(byte*)source);
+#endif
+ }
+
+ /// <summary>
+ /// Reads a value of type <typeparamref name="T"/> from the given location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadUnaligned<T>(ref byte source)
+ {
+#if CORECLR
+ typeof(T).ToString(); // Type token used by the actual method body
+ throw new PlatformNotSupportedException();
+#else
+ return Unsafe.As<byte, T>(ref source);
+#endif
+ }
+
+ /// <summary>
+ /// Writes a value of type <typeparamref name="T"/> to the given location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUnaligned<T>(void* destination, T value)
+ {
+#if CORECLR
+ typeof(T).ToString(); // Type token used by the actual method body
+ throw new PlatformNotSupportedException();
+#else
+ Unsafe.As<byte, T>(ref *(byte*)destination) = value;
+#endif
+ }
+
+ /// <summary>
+ /// Writes a value of type <typeparamref name="T"/> to the given location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUnaligned<T>(ref byte destination, T value)
+ {
+#if CORECLR
+ typeof(T).ToString(); // Type token used by the actual method body
+ throw new PlatformNotSupportedException();
+#else
+ Unsafe.As<byte, T>(ref destination) = value;
+#endif
+ }
+
+ /// <summary>
+ /// Adds an element offset to the given reference.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T AddByteOffset<T>(ref T source, IntPtr byteOffset)
+ {
+ // This method is implemented by the toolchain
+ throw new PlatformNotSupportedException();
+
+ // ldarg.0
+ // ldarg.1
+ // add
+ // ret
+ }
+
+ /// <summary>
+ /// Reads a value of type <typeparamref name="T"/> from the given location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Read<T>(void* source)
+ {
+ return Unsafe.As<byte, T>(ref *(byte*)source);
+ }
+
+ /// <summary>
+ /// Reads a value of type <typeparamref name="T"/> from the given location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Read<T>(ref byte source)
+ {
+ return Unsafe.As<byte, T>(ref source);
+ }
+
+ /// <summary>
+ /// Writes a value of type <typeparamref name="T"/> to the given location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Write<T>(void* destination, T value)
+ {
+ Unsafe.As<byte, T>(ref *(byte*)destination) = value;
+ }
+
+ /// <summary>
+ /// Writes a value of type <typeparamref name="T"/> to the given location.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Write<T>(ref byte destination, T value)
+ {
+ Unsafe.As<byte, T>(ref destination) = value;
+ }
+
+ /// <summary>
+ /// Reinterprets the given location as a reference to a value of type <typeparamref name="T"/>.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T AsRef<T>(void* source)
+ {
+ return ref Unsafe.As<byte, T>(ref *(byte*)source);
+ }
+
+ /// <summary>
+ /// Determines the byte offset from origin to target from the given references.
+ /// </summary>
+ [Intrinsic]
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IntPtr ByteOffset<T>(ref T origin, ref T target)
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/Interop.Errors.cs b/src/System.Private.CoreLib/shared/Interop/Unix/Interop.Errors.cs
new file mode 100644
index 0000000000..9cb05809db
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/Interop.Errors.cs
@@ -0,0 +1,209 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ /// <summary>Common Unix errno error codes.</summary>
+ internal enum Error
+ {
+ // These values were defined in src/Native/System.Native/fxerrno.h
+ //
+ // They compare against values obtained via Interop.Sys.GetLastError() not Marshal.GetLastWin32Error()
+ // which obtains the raw errno that varies between unixes. The strong typing as an enum is meant to
+ // prevent confusing the two. Casting to or from int is suspect. Use GetLastErrorInfo() if you need to
+ // correlate these to the underlying platform values or obtain the corresponding error message.
+ //
+
+ SUCCESS = 0,
+
+ E2BIG = 0x10001, // Argument list too long.
+ EACCES = 0x10002, // Permission denied.
+ EADDRINUSE = 0x10003, // Address in use.
+ EADDRNOTAVAIL = 0x10004, // Address not available.
+ EAFNOSUPPORT = 0x10005, // Address family not supported.
+ EAGAIN = 0x10006, // Resource unavailable, try again (same value as EWOULDBLOCK),
+ EALREADY = 0x10007, // Connection already in progress.
+ EBADF = 0x10008, // Bad file descriptor.
+ EBADMSG = 0x10009, // Bad message.
+ EBUSY = 0x1000A, // Device or resource busy.
+ ECANCELED = 0x1000B, // Operation canceled.
+ ECHILD = 0x1000C, // No child processes.
+ ECONNABORTED = 0x1000D, // Connection aborted.
+ ECONNREFUSED = 0x1000E, // Connection refused.
+ ECONNRESET = 0x1000F, // Connection reset.
+ EDEADLK = 0x10010, // Resource deadlock would occur.
+ EDESTADDRREQ = 0x10011, // Destination address required.
+ EDOM = 0x10012, // Mathematics argument out of domain of function.
+ EDQUOT = 0x10013, // Reserved.
+ EEXIST = 0x10014, // File exists.
+ EFAULT = 0x10015, // Bad address.
+ EFBIG = 0x10016, // File too large.
+ EHOSTUNREACH = 0x10017, // Host is unreachable.
+ EIDRM = 0x10018, // Identifier removed.
+ EILSEQ = 0x10019, // Illegal byte sequence.
+ EINPROGRESS = 0x1001A, // Operation in progress.
+ EINTR = 0x1001B, // Interrupted function.
+ EINVAL = 0x1001C, // Invalid argument.
+ EIO = 0x1001D, // I/O error.
+ EISCONN = 0x1001E, // Socket is connected.
+ EISDIR = 0x1001F, // Is a directory.
+ ELOOP = 0x10020, // Too many levels of symbolic links.
+ EMFILE = 0x10021, // File descriptor value too large.
+ EMLINK = 0x10022, // Too many links.
+ EMSGSIZE = 0x10023, // Message too large.
+ EMULTIHOP = 0x10024, // Reserved.
+ ENAMETOOLONG = 0x10025, // Filename too long.
+ ENETDOWN = 0x10026, // Network is down.
+ ENETRESET = 0x10027, // Connection aborted by network.
+ ENETUNREACH = 0x10028, // Network unreachable.
+ ENFILE = 0x10029, // Too many files open in system.
+ ENOBUFS = 0x1002A, // No buffer space available.
+ ENODEV = 0x1002C, // No such device.
+ ENOENT = 0x1002D, // No such file or directory.
+ ENOEXEC = 0x1002E, // Executable file format error.
+ ENOLCK = 0x1002F, // No locks available.
+ ENOLINK = 0x10030, // Reserved.
+ ENOMEM = 0x10031, // Not enough space.
+ ENOMSG = 0x10032, // No message of the desired type.
+ ENOPROTOOPT = 0x10033, // Protocol not available.
+ ENOSPC = 0x10034, // No space left on device.
+ ENOSYS = 0x10037, // Function not supported.
+ ENOTCONN = 0x10038, // The socket is not connected.
+ ENOTDIR = 0x10039, // Not a directory or a symbolic link to a directory.
+ ENOTEMPTY = 0x1003A, // Directory not empty.
+ ENOTRECOVERABLE = 0x1003B, // State not recoverable.
+ ENOTSOCK = 0x1003C, // Not a socket.
+ ENOTSUP = 0x1003D, // Not supported (same value as EOPNOTSUP).
+ ENOTTY = 0x1003E, // Inappropriate I/O control operation.
+ ENXIO = 0x1003F, // No such device or address.
+ EOVERFLOW = 0x10040, // Value too large to be stored in data type.
+ EOWNERDEAD = 0x10041, // Previous owner died.
+ EPERM = 0x10042, // Operation not permitted.
+ EPIPE = 0x10043, // Broken pipe.
+ EPROTO = 0x10044, // Protocol error.
+ EPROTONOSUPPORT = 0x10045, // Protocol not supported.
+ EPROTOTYPE = 0x10046, // Protocol wrong type for socket.
+ ERANGE = 0x10047, // Result too large.
+ EROFS = 0x10048, // Read-only file system.
+ ESPIPE = 0x10049, // Invalid seek.
+ ESRCH = 0x1004A, // No such process.
+ ESTALE = 0x1004B, // Reserved.
+ ETIMEDOUT = 0x1004D, // Connection timed out.
+ ETXTBSY = 0x1004E, // Text file busy.
+ EXDEV = 0x1004F, // Cross-device link.
+ ESOCKTNOSUPPORT = 0x1005E, // Socket type not supported.
+ EPFNOSUPPORT = 0x10060, // Protocol family not supported.
+ ESHUTDOWN = 0x1006C, // Socket shutdown.
+ EHOSTDOWN = 0x10070, // Host is down.
+ ENODATA = 0x10071, // No data available.
+
+ // POSIX permits these to have the same value and we make them always equal so
+ // that CoreFX cannot introduce a dependency on distinguishing between them that
+ // would not work on all platforms.
+ EOPNOTSUPP = ENOTSUP, // Operation not supported on socket.
+ EWOULDBLOCK = EAGAIN, // Operation would block.
+ }
+
+
+ // Represents a platform-agnostic Error and underlying platform-specific errno
+ internal struct ErrorInfo
+ {
+ private Error _error;
+ private int _rawErrno;
+
+ internal ErrorInfo(int errno)
+ {
+ _error = Interop.Sys.ConvertErrorPlatformToPal(errno);
+ _rawErrno = errno;
+ }
+
+ internal ErrorInfo(Error error)
+ {
+ _error = error;
+ _rawErrno = -1;
+ }
+
+ internal Error Error
+ {
+ get { return _error; }
+ }
+
+ internal int RawErrno
+ {
+ get { return _rawErrno == -1 ? (_rawErrno = Interop.Sys.ConvertErrorPalToPlatform(_error)) : _rawErrno; }
+ }
+
+ internal string GetErrorMessage()
+ {
+ return Interop.Sys.StrError(RawErrno);
+ }
+
+ public override string ToString()
+ {
+ return string.Format(
+ "RawErrno: {0} Error: {1} GetErrorMessage: {2}", // No localization required; text is member names used for debugging purposes
+ RawErrno, Error, GetErrorMessage());
+ }
+ }
+
+ internal partial class Sys
+ {
+ internal static Error GetLastError()
+ {
+ return ConvertErrorPlatformToPal(Marshal.GetLastWin32Error());
+ }
+
+ internal static ErrorInfo GetLastErrorInfo()
+ {
+ return new ErrorInfo(Marshal.GetLastWin32Error());
+ }
+
+ internal static unsafe string StrError(int platformErrno)
+ {
+ int maxBufferLength = 1024; // should be long enough for most any UNIX error
+ byte* buffer = stackalloc byte[maxBufferLength];
+ byte* message = StrErrorR(platformErrno, buffer, maxBufferLength);
+
+ if (message == null)
+ {
+ // This means the buffer was not large enough, but still contains
+ // as much of the error message as possible and is guaranteed to
+ // be null-terminated. We're not currently resizing/retrying because
+ // maxBufferLength is large enough in practice, but we could do
+ // so here in the future if necessary.
+ message = buffer;
+ }
+
+ return Marshal.PtrToStringAnsi((IntPtr)message);
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPlatformToPal")]
+ internal static extern Error ConvertErrorPlatformToPal(int platformErrno);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPalToPlatform")]
+ internal static extern int ConvertErrorPalToPlatform(Error error);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_StrErrorR")]
+ private static extern unsafe byte* StrErrorR(int platformErrno, byte* buffer, int bufferSize);
+ }
+}
+
+// NOTE: extension method can't be nested inside Interop class.
+internal static class InteropErrorExtensions
+{
+ // Intended usage is e.g. Interop.Error.EFAIL.Info() for brevity
+ // vs. new Interop.ErrorInfo(Interop.Error.EFAIL) for synthesizing
+ // errors. Errors originated from the system should be obtained
+ // via GetLastErrorInfo(), not GetLastError().Info() as that will
+ // convert twice, which is not only inefficient but also lossy if
+ // we ever encounter a raw errno that no equivalent in the Error
+ // enum.
+ public static Interop.ErrorInfo Info(this Interop.Error error)
+ {
+ return new Interop.ErrorInfo(error);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/Interop.IOErrors.cs b/src/System.Private.CoreLib/shared/Interop/Unix/Interop.IOErrors.cs
new file mode 100644
index 0000000000..5babcd1d05
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/Interop.IOErrors.cs
@@ -0,0 +1,172 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ private static void ThrowExceptionForIoErrno(ErrorInfo errorInfo, string path, bool isDirectory, Func<ErrorInfo, ErrorInfo> errorRewriter)
+ {
+ Debug.Assert(errorInfo.Error != Error.SUCCESS);
+ Debug.Assert(errorInfo.Error != Error.EINTR, "EINTR errors should be handled by the native shim and never bubble up to managed code");
+
+ if (errorRewriter != null)
+ {
+ errorInfo = errorRewriter(errorInfo);
+ }
+
+ throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirectory);
+ }
+
+ internal static void CheckIo(Error error, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null)
+ {
+ if (error != Interop.Error.SUCCESS)
+ {
+ ThrowExceptionForIoErrno(error.Info(), path, isDirectory, errorRewriter);
+ }
+ }
+
+ /// <summary>
+ /// Validates the result of system call that returns greater than or equal to 0 on success
+ /// and less than 0 on failure, with errno set to the error code.
+ /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded.
+ /// </summary>
+ /// <param name="result">The result of the system call.</param>
+ /// <param name="path">The path with which this error is associated. This may be null.</param>
+ /// <param name="isDirectory">true if the <paramref name="path"/> is known to be a directory; otherwise, false.</param>
+ /// <param name="errorRewriter">Optional function to change an error code prior to processing it.</param>
+ /// <returns>
+ /// On success, returns the non-negative result long that was validated.
+ /// </returns>
+ internal static long CheckIo(long result, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null)
+ {
+ if (result < 0)
+ {
+ ThrowExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirectory, errorRewriter);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Validates the result of system call that returns greater than or equal to 0 on success
+ /// and less than 0 on failure, with errno set to the error code.
+ /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded.
+ /// </summary>
+ /// <returns>
+ /// On success, returns the non-negative result int that was validated.
+ /// </returns>
+ internal static int CheckIo(int result, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null)
+ {
+ CheckIo((long)result, path, isDirectory, errorRewriter);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Validates the result of system call that returns greater than or equal to 0 on success
+ /// and less than 0 on failure, with errno set to the error code.
+ /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded.
+ /// </summary>
+ /// <returns>
+ /// On success, returns the non-negative result IntPtr that was validated.
+ /// </returns>
+ internal static IntPtr CheckIo(IntPtr result, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null)
+ {
+ CheckIo((long)result, path, isDirectory, errorRewriter);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Validates the result of system call that returns greater than or equal to 0 on success
+ /// and less than 0 on failure, with errno set to the error code.
+ /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded.
+ /// </summary>
+ /// <returns>
+ /// On success, returns the valid SafeFileHandle that was validated.
+ /// </returns>
+ internal static TSafeHandle CheckIo<TSafeHandle>(TSafeHandle handle, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null)
+ where TSafeHandle : SafeHandle
+ {
+ if (handle.IsInvalid)
+ {
+ ThrowExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirectory, errorRewriter);
+ }
+
+ return handle;
+ }
+
+ /// <summary>
+ /// Gets an Exception to represent the supplied error info.
+ /// </summary>
+ /// <param name="error">The error info</param>
+ /// <param name="path">The path with which this error is associated. This may be null.</param>
+ /// <param name="isDirectory">true if the <paramref name="path"/> is known to be a directory; otherwise, false.</param>
+ /// <returns></returns>
+ internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string path = null, bool isDirectory = false)
+ {
+ // Translate the errno into a known set of exception types. For cases where multiple errnos map
+ // to the same exception type, include an inner exception with the details.
+ switch (errorInfo.Error)
+ {
+ case Error.ENOENT:
+ if (isDirectory)
+ {
+ return !string.IsNullOrEmpty(path) ?
+ new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, path)) :
+ new DirectoryNotFoundException(SR.IO_PathNotFound_NoPathName);
+ }
+ else
+ {
+ return !string.IsNullOrEmpty(path) ?
+ new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, path), path) :
+ new FileNotFoundException(SR.IO_FileNotFound);
+ }
+
+ case Error.EACCES:
+ case Error.EBADF:
+ case Error.EPERM:
+ Exception inner = GetIOException(errorInfo);
+ return !string.IsNullOrEmpty(path) ?
+ new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path), inner) :
+ new UnauthorizedAccessException(SR.UnauthorizedAccess_IODenied_NoPathName, inner);
+
+ case Error.ENAMETOOLONG:
+ return !string.IsNullOrEmpty(path) ?
+ new PathTooLongException(SR.Format(SR.IO_PathTooLong_Path, path)) :
+ new PathTooLongException(SR.IO_PathTooLong);
+
+ case Error.EWOULDBLOCK:
+ return !string.IsNullOrEmpty(path) ?
+ new IOException(SR.Format(SR.IO_SharingViolation_File, path), errorInfo.RawErrno) :
+ new IOException(SR.IO_SharingViolation_NoFileName, errorInfo.RawErrno);
+
+ case Error.ECANCELED:
+ return new OperationCanceledException();
+
+ case Error.EFBIG:
+ return new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_FileLengthTooBig);
+
+ case Error.EEXIST:
+ if (!string.IsNullOrEmpty(path))
+ {
+ return new IOException(SR.Format(SR.IO_FileExists_Name, path), errorInfo.RawErrno);
+ }
+ goto default;
+
+ default:
+ return GetIOException(errorInfo);
+ }
+ }
+
+ internal static Exception GetIOException(Interop.ErrorInfo errorInfo)
+ {
+ return new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/Interop.Libraries.cs b/src/System.Private.CoreLib/shared/Interop/Unix/Interop.Libraries.cs
new file mode 100644
index 0000000000..02d0092445
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/Interop.Libraries.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal static partial class Interop
+{
+ internal static partial class Libraries
+ {
+ internal const string GlobalizationNative = "System.Globalization.Native";
+ internal const string SystemNative = "System.Native";
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs
new file mode 100644
index 0000000000..55553cc7ea
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Text;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ internal delegate void EnumCalendarInfoCallback(
+ [MarshalAs(UnmanagedType.LPWStr)] string calendarString,
+ IntPtr context);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetCalendars")]
+ internal static extern int GetCalendars(string localeName, CalendarId[] calendars, int calendarsCapacity);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetCalendarInfo")]
+ internal static extern ResultCode GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType calendarDataType, [Out] StringBuilder result, int resultCapacity);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_EnumCalendarInfo")]
+ internal static extern bool EnumCalendarInfo(EnumCalendarInfoCallback callback, string localeName, CalendarId calendarId, CalendarDataType calendarDataType, IntPtr context);
+
+ [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLatestJapaneseEra")]
+ internal static extern int GetLatestJapaneseEra();
+
+ [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetJapaneseEraStartDate")]
+ internal static extern bool GetJapaneseEraStartDate(int era, out int startYear, out int startMonth, out int startDay);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Casing.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Casing.cs
new file mode 100644
index 0000000000..503a864d69
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Casing.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_ChangeCase")]
+ internal static extern unsafe void ChangeCase(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_ChangeCaseInvariant")]
+ internal static extern unsafe void ChangeCaseInvariant(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_ChangeCaseTurkish")]
+ internal static extern unsafe void ChangeCaseTurkish(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs
new file mode 100644
index 0000000000..9942882f1a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Security;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetSortHandle")]
+ internal static extern unsafe ResultCode GetSortHandle(byte[] localeName, out SafeSortHandle sortHandle);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_CloseSortHandle")]
+ internal static extern unsafe void CloseSortHandle(IntPtr handle);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_CompareString")]
+ internal static extern unsafe int CompareString(SafeSortHandle sortHandle, char* lpStr1, int cwStr1Len, char* lpStr2, int cwStr2Len, CompareOptions options);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")]
+ internal static extern unsafe int IndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr);
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")]
+ internal static extern unsafe int IndexOf(SafeSortHandle sortHandle, char* target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_LastIndexOf")]
+ internal static extern unsafe int LastIndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")]
+ internal static extern unsafe int IndexOfOrdinalIgnoreCase(string target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast);
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")]
+ internal static extern unsafe int IndexOfOrdinalIgnoreCase(char* target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_StartsWith")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool StartsWith(SafeSortHandle sortHandle, char* target, int cwTargetLength, char* source, int cwSourceLength, CompareOptions options);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_EndsWith")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool EndsWith(SafeSortHandle sortHandle, char* target, int cwTargetLength, char* source, int cwSourceLength, CompareOptions options);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_StartsWith")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool StartsWith(SafeSortHandle sortHandle, string target, int cwTargetLength, string source, int cwSourceLength, CompareOptions options);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_EndsWith")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool EndsWith(SafeSortHandle sortHandle, string target, int cwTargetLength, string source, int cwSourceLength, CompareOptions options);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetSortKey")]
+ internal static extern unsafe int GetSortKey(SafeSortHandle sortHandle, string str, int strLength, byte* sortKey, int sortKeyLength, CompareOptions options);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_CompareStringOrdinalIgnoreCase")]
+ internal static extern unsafe int CompareStringOrdinalIgnoreCase(char* lpStr1, int cwStr1Len, char* lpStr2, int cwStr2Len);
+
+ [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetSortVersion")]
+ internal static extern int GetSortVersion(SafeSortHandle sortHandle);
+
+ internal class SafeSortHandle : SafeHandle
+ {
+ private SafeSortHandle() :
+ base(IntPtr.Zero, true)
+ {
+ }
+
+ public override bool IsInvalid
+ {
+ get { return handle == IntPtr.Zero; }
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ CloseSortHandle(handle);
+ SetHandle(IntPtr.Zero);
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.ICU.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.ICU.cs
new file mode 100644
index 0000000000..a16c813b2f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.ICU.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_LoadICU")]
+ internal static extern int LoadICU();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Idna.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Idna.cs
new file mode 100644
index 0000000000..89b6c3cebe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Idna.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ internal const int AllowUnassigned = 0x1;
+ internal const int UseStd3AsciiRules = 0x2;
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_ToAscii")]
+ internal static extern unsafe int ToAscii(uint flags, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_ToUnicode")]
+ internal static extern unsafe int ToUnicode(uint flags, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Locale.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Locale.cs
new file mode 100644
index 0000000000..9ef41dedf2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Locale.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleName")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool GetLocaleName(string localeName, [Out] StringBuilder value, int valueLength);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleInfoString")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool GetLocaleInfoString(string localeName, uint localeStringData, [Out] StringBuilder value, int valueLength);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetDefaultLocaleName")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool GetDefaultLocaleName([Out] StringBuilder value, int valueLength);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleTimeFormat")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool GetLocaleTimeFormat(string localeName, bool shortFormat, [Out] StringBuilder value, int valueLength);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleInfoInt")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool GetLocaleInfoInt(string localeName, uint localeNumberData, ref int value);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleInfoGroupingSizes")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern unsafe bool GetLocaleInfoGroupingSizes(string localeName, uint localeGroupingData, ref int primaryGroupSize, ref int secondaryGroupSize);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocales")]
+ internal static extern unsafe int GetLocales([Out] Char[] value, int valueLength);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Normalization.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Normalization.cs
new file mode 100644
index 0000000000..d442da0ea1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Normalization.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IsNormalized")]
+ internal static extern int IsNormalized(NormalizationForm normalizationForm, string src, int srcLen);
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_NormalizeString")]
+ internal static extern int NormalizeString(NormalizationForm normalizationForm, string src, int srcLen, [Out] char[] dstBuffer, int dstBufferCapacity);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.ResultCode.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.ResultCode.cs
new file mode 100644
index 0000000000..4a9933f929
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.ResultCode.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ // needs to be kept in sync with ResultCode in System.Globalization.Native
+ internal enum ResultCode
+ {
+ Success = 0,
+ UnknownError = 1,
+ InsufficentBuffer = 2,
+ OutOfMemory = 3
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs
new file mode 100644
index 0000000000..47cf26662b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using System.Text;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ // needs to be kept in sync with TimeZoneDisplayNameType in System.Globalization.Native
+ internal enum TimeZoneDisplayNameType
+ {
+ Generic = 0,
+ Standard = 1,
+ DaylightSavings = 2,
+ }
+
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetTimeZoneDisplayName")]
+ internal static extern ResultCode GetTimeZoneDisplayName(
+ string localeName,
+ string timeZoneId,
+ TimeZoneDisplayNameType type,
+ [Out] StringBuilder result,
+ int resultLength);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Utils.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Utils.cs
new file mode 100644
index 0000000000..9887bd4f0b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Utils.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+
+internal static partial class Interop
+{
+ /// <summary>
+ /// Helper for making interop calls that return a string, but we don't know
+ /// the correct size of buffer to make. So invoke the interop call with an
+ /// increasing buffer until the size is big enough.
+ /// </summary>
+ internal static bool CallStringMethod<TArg1, TArg2, TArg3>(
+ Func<TArg1, TArg2, TArg3, StringBuilder, Interop.Globalization.ResultCode> interopCall,
+ TArg1 arg1,
+ TArg2 arg2,
+ TArg3 arg3,
+ out string result)
+ {
+ const int initialStringSize = 80;
+ const int maxDoubleAttempts = 5;
+
+ StringBuilder stringBuilder = StringBuilderCache.Acquire(initialStringSize);
+
+ for (int i = 0; i < maxDoubleAttempts; i++)
+ {
+ Interop.Globalization.ResultCode resultCode = interopCall(arg1, arg2, arg3, stringBuilder);
+
+ if (resultCode == Interop.Globalization.ResultCode.Success)
+ {
+ result = StringBuilderCache.GetStringAndRelease(stringBuilder);
+ return true;
+ }
+ else if (resultCode == Interop.Globalization.ResultCode.InsufficentBuffer)
+ {
+ // increase the string size and loop
+ stringBuilder.EnsureCapacity(stringBuilder.Capacity * 2);
+ }
+ else
+ {
+ // if there is an unknown error, don't proceed
+ break;
+ }
+ }
+
+ StringBuilderCache.Release(stringBuilder);
+ result = null;
+ return false;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Close.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Close.cs
new file mode 100644
index 0000000000..8d192398a0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Close.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Close", SetLastError = true)]
+ internal static extern int Close(IntPtr fd);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FLock.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FLock.cs
new file mode 100644
index 0000000000..22934a3e77
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FLock.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ internal enum LockOperations : int
+ {
+ LOCK_SH = 1, /* shared lock */
+ LOCK_EX = 2, /* exclusive lock */
+ LOCK_NB = 4, /* don't block when locking*/
+ LOCK_UN = 8, /* unlock */
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FLock", SetLastError = true)]
+ internal static extern int FLock(SafeFileHandle fd, LockOperations operation);
+
+ /// <summary>
+ /// Exposing this for SafeFileHandle.ReleaseHandle() to call.
+ /// Normal callers should use FLock(SafeFileHandle fd).
+ /// </summary>
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FLock", SetLastError = true)]
+ internal static extern int FLock(IntPtr fd, LockOperations operation);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FSync.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FSync.cs
new file mode 100644
index 0000000000..e3ab970931
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FSync.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FSync", SetLastError = true)]
+ internal static extern int FSync(SafeFileHandle fd);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FTruncate.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FTruncate.cs
new file mode 100644
index 0000000000..5dad650362
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.FTruncate.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FTruncate", SetLastError = true)]
+ internal static extern int FTruncate(SafeFileHandle fd, long length);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.GetCwd.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.GetCwd.cs
new file mode 100644
index 0000000000..a8bef6bde8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.GetCwd.cs
@@ -0,0 +1,74 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Buffers;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetCwd", SetLastError = true)]
+ private static extern unsafe byte* GetCwd(byte* buffer, int bufferLength);
+
+ internal static unsafe string GetCwd()
+ {
+ const int StackLimit = 256;
+
+ // First try to get the path into a buffer on the stack
+ byte* stackBuf = stackalloc byte[StackLimit];
+ string result = GetCwdHelper(stackBuf, StackLimit);
+ if (result != null)
+ {
+ return result;
+ }
+
+ // If that was too small, try increasing large buffer sizes
+ int bufferSize = StackLimit;
+ do
+ {
+ checked { bufferSize *= 2; }
+ byte[] buf = ArrayPool<byte>.Shared.Rent(bufferSize);
+ try
+ {
+ fixed (byte* ptr = &buf[0])
+ {
+ result = GetCwdHelper(ptr, buf.Length);
+ if (result != null)
+ {
+ return result;
+ }
+ }
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buf);
+ }
+ }
+ while (true);
+ }
+
+ private static unsafe string GetCwdHelper(byte* ptr, int bufferSize)
+ {
+ // Call the real getcwd
+ byte* result = GetCwd(ptr, bufferSize);
+
+ // If it returned non-null, the null-terminated path is in the buffer
+ if (result != null)
+ {
+ return Marshal.PtrToStringAnsi((IntPtr)ptr);
+ }
+
+ // Otherwise, if it failed due to the buffer being too small, return null;
+ // for anything else, throw.
+ ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+ if (errorInfo.Error == Interop.Error.ERANGE)
+ {
+ return null;
+ }
+ throw Interop.GetExceptionForIoErrno(errorInfo);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.GetRandomBytes.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.GetRandomBytes.cs
new file mode 100644
index 0000000000..e911b13583
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.GetRandomBytes.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal unsafe partial class Sys
+ {
+ [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetNonCryptographicallySecureRandomBytes")]
+ internal static extern unsafe void GetNonCryptographicallySecureRandomBytes(byte* buffer, int length);
+ }
+
+ internal static unsafe void GetRandomBytes(byte* buffer, int length)
+ {
+ Sys.GetNonCryptographicallySecureRandomBytes(buffer, length);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.LSeek.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.LSeek.cs
new file mode 100644
index 0000000000..7f8df7c6bf
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.LSeek.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ internal enum SeekWhence
+ {
+ SEEK_SET = 0,
+ SEEK_CUR = 1,
+ SEEK_END = 2
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_LSeek", SetLastError = true)]
+ internal static extern long LSeek(SafeFileHandle fd, long offset, SeekWhence whence);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.LockFileRegion.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.LockFileRegion.cs
new file mode 100644
index 0000000000..23b48a4f5d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.LockFileRegion.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ internal enum LockType : short
+ {
+ F_WRLCK = 1, // exclusive or write lock
+ F_UNLCK = 2 // unlock
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_LockFileRegion", SetLastError=true)]
+ internal static extern int LockFileRegion(SafeHandle fd, long offset, long length, LockType lockType);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.MksTemps.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.MksTemps.cs
new file mode 100644
index 0000000000..b8694d9007
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.MksTemps.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MksTemps", SetLastError = true)]
+ internal static extern IntPtr MksTemps(
+ byte[] template,
+ int suffixlen);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Open.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Open.cs
new file mode 100644
index 0000000000..a9a994c78c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Open.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Open", SetLastError = true)]
+ internal static extern SafeFileHandle Open(string filename, OpenFlags flags, int mode);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.OpenFlags.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.OpenFlags.cs
new file mode 100644
index 0000000000..f9e54c3cbc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.OpenFlags.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [Flags]
+ internal enum OpenFlags
+ {
+ // Access modes (mutually exclusive)
+ O_RDONLY = 0x0000,
+ O_WRONLY = 0x0001,
+ O_RDWR = 0x0002,
+
+ // Flags (combinable)
+ O_CLOEXEC = 0x0010,
+ O_CREAT = 0x0020,
+ O_EXCL = 0x0040,
+ O_TRUNC = 0x0080,
+ O_SYNC = 0x0100,
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.PathConf.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.PathConf.cs
new file mode 100644
index 0000000000..7213cb0264
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.PathConf.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ internal enum PathConfName : int
+ {
+ PC_LINK_MAX = 1,
+ PC_MAX_CANON = 2,
+ PC_MAX_INPUT = 3,
+ PC_NAME_MAX = 4,
+ PC_PATH_MAX = 5,
+ PC_PIPE_BUF = 6,
+ PC_CHOWN_RESTRICTED = 7,
+ PC_NO_TRUNC = 8,
+ PC_VDISABLE = 9,
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PathConf", SetLastError = true)]
+ private static extern int PathConf(string path, PathConfName name);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Permissions.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Permissions.cs
new file mode 100644
index 0000000000..f1d13787d2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Permissions.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [Flags]
+ internal enum Permissions
+ {
+ Mask = S_IRWXU | S_IRWXG | S_IRWXO,
+
+ S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR,
+ S_IRUSR = 0x100,
+ S_IWUSR = 0x80,
+ S_IXUSR = 0x40,
+
+ S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP,
+ S_IRGRP = 0x20,
+ S_IWGRP = 0x10,
+ S_IXGRP = 0x8,
+
+ S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH,
+ S_IROTH = 0x4,
+ S_IWOTH = 0x2,
+ S_IXOTH = 0x1,
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.PosixFAdvise.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.PosixFAdvise.cs
new file mode 100644
index 0000000000..ad8b73aed2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.PosixFAdvise.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ internal enum FileAdvice : int
+ {
+ POSIX_FADV_NORMAL = 0, /* no special advice, the default value */
+ POSIX_FADV_RANDOM = 1, /* random I/O access */
+ POSIX_FADV_SEQUENTIAL = 2, /* sequential I/O access */
+ POSIX_FADV_WILLNEED = 3, /* will need specified pages */
+ POSIX_FADV_DONTNEED = 4, /* don't need the specified pages */
+ POSIX_FADV_NOREUSE = 5, /* data will only be accessed once */
+ }
+
+ /// <summary>
+ /// Notifies the OS kernel that the specified file will be accessed in a particular way soon; this allows the kernel to
+ /// potentially optimize the access pattern of the file.
+ /// </summary>
+ /// <param name="fd">The file descriptor of the file</param>
+ /// <param name="offset">The start of the region to advise about</param>
+ /// <param name="length">The number of bytes of the region (until the end of the file if 0)</param>
+ /// <param name="advice">The type of advice to give the kernel about the specified region</param>
+ /// <returns>
+ /// Returns 0 on success; otherwise, the error code is returned
+ /// </returns>
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PosixFAdvise", SetLastError = false /* this is explicitly called out in the man page */)]
+ internal static extern int PosixFAdvise(SafeFileHandle fd, long offset, long length, FileAdvice advice);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Read.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Read.cs
new file mode 100644
index 0000000000..1be5e789c2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Read.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ /// <summary>
+ /// Reads a number of bytes from an open file descriptor into a specified buffer.
+ /// </summary>
+ /// <param name="fd">The open file descriptor to try to read from</param>
+ /// <param name="buffer">The buffer to read info into</param>
+ /// <param name="count">The size of the buffer</param>
+ /// <returns>
+ /// Returns the number of bytes read on success; otherwise, -1 is returned
+ /// Note - on fail. the position of the stream may change depending on the platform; consult man 2 read for more info
+ /// </returns>
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Read", SetLastError = true)]
+ internal static extern unsafe int Read(SafeFileHandle fd, byte* buffer, int count);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.ReadDir.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.ReadDir.cs
new file mode 100644
index 0000000000..d98c4285c0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.ReadDir.cs
@@ -0,0 +1,102 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ private static readonly int s_readBufferSize = GetReadDirRBufferSize();
+
+ internal enum NodeType : int
+ {
+ DT_UNKNOWN = 0,
+ DT_FIFO = 1,
+ DT_CHR = 2,
+ DT_DIR = 4,
+ DT_BLK = 6,
+ DT_REG = 8,
+ DT_LNK = 10,
+ DT_SOCK = 12,
+ DT_WHT = 14
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct InternalDirectoryEntry
+ {
+ internal IntPtr Name;
+ internal int NameLength;
+ internal NodeType InodeType;
+ }
+
+ internal struct DirectoryEntry
+ {
+ internal NodeType InodeType;
+ internal string InodeName;
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_OpenDir", SetLastError = true)]
+ internal static extern Microsoft.Win32.SafeHandles.SafeDirectoryHandle OpenDir(string path);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetReadDirRBufferSize", SetLastError = false)]
+ internal static extern int GetReadDirRBufferSize();
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadDirR", SetLastError = false)]
+ private static extern unsafe int ReadDirR(IntPtr dir, byte* buffer, int bufferSize, out InternalDirectoryEntry outputEntry);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_CloseDir", SetLastError = true)]
+ internal static extern int CloseDir(IntPtr dir);
+
+ // The calling pattern for ReadDir is described in src/Native/System.Native/pal_readdir.cpp
+ internal static int ReadDir(SafeDirectoryHandle dir, out DirectoryEntry outputEntry)
+ {
+ bool addedRef = false;
+ try
+ {
+ // We avoid a native string copy into InternalDirectoryEntry.
+ // - If the platform suppors reading into a buffer, the data is read directly into the buffer. The
+ // data can be read as long as the buffer is valid.
+ // - If the platform does not support reading into a buffer, the information returned in
+ // InternalDirectoryEntry points to native memory owned by the SafeDirectoryHandle. The data is only
+ // valid until the next call to CloseDir/ReadDir. We extend the reference until we have copied all data
+ // to ensure it does not become invalid by a CloseDir; and we copy the data so our caller does not
+ // use the native memory held by the SafeDirectoryHandle.
+ dir.DangerousAddRef(ref addedRef);
+
+ unsafe
+ {
+ // s_readBufferSize is zero when the native implementation does not support reading into a buffer.
+ byte* buffer = stackalloc byte[s_readBufferSize];
+ InternalDirectoryEntry temp;
+ int ret = ReadDirR(dir.DangerousGetHandle(), buffer, s_readBufferSize, out temp);
+ // We copy data into DirectoryEntry to ensure there are no dangling references.
+ outputEntry = ret == 0 ?
+ new DirectoryEntry() { InodeName = GetDirectoryEntryName(temp), InodeType = temp.InodeType } :
+ default(DirectoryEntry);
+
+ return ret;
+ }
+ }
+ finally
+ {
+ if (addedRef)
+ {
+ dir.DangerousRelease();
+ }
+ }
+ }
+
+ private static unsafe string GetDirectoryEntryName(InternalDirectoryEntry dirEnt)
+ {
+ if (dirEnt.NameLength == -1)
+ return Marshal.PtrToStringAnsi(dirEnt.Name);
+ else
+ return Marshal.PtrToStringAnsi(dirEnt.Name, dirEnt.NameLength);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.ReadLink.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.ReadLink.cs
new file mode 100644
index 0000000000..50f1ae545e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.ReadLink.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using System.Buffers;
+using System.Text;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ /// <summary>
+ /// Takes a path to a symbolic link and attempts to place the link target path into the buffer. If the buffer is too
+ /// small, the path will be truncated. No matter what, the buffer will not be null terminated.
+ /// </summary>
+ /// <param name="path">The path to the symlink</param>
+ /// <param name="buffer">The buffer to hold the output path</param>
+ /// <param name="bufferSize">The size of the buffer</param>
+ /// <returns>
+ /// Returns the number of bytes placed into the buffer on success; bufferSize if the buffer is too small; and -1 on error.
+ /// </returns>
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadLink", SetLastError = true)]
+ private static extern unsafe int ReadLink(string path, byte[] buffer, int bufferSize);
+
+ /// <summary>
+ /// Takes a path to a symbolic link and returns the link target path.
+ /// </summary>
+ /// <param name="path">The path to the symlink</param>
+ /// <returns>
+ /// Returns the link to the target path on success; and null otherwise.
+ /// </returns>
+ public static string ReadLink(string path)
+ {
+ int bufferSize = 256;
+ do
+ {
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
+ try
+ {
+ int resultLength = Interop.Sys.ReadLink(path, buffer, buffer.Length);
+ if (resultLength < 0)
+ {
+ // error
+ return null;
+ }
+ else if (resultLength < buffer.Length)
+ {
+ // success
+ return Encoding.UTF8.GetString(buffer, 0, resultLength);
+ }
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
+
+ // buffer was too small, loop around again and try with a larger buffer.
+ bufferSize *= 2;
+ } while (true);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Stat.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Stat.cs
new file mode 100644
index 0000000000..0ca199b70d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Stat.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ // Even though csc will by default use a sequential layout, a CS0649 warning as error
+ // is produced for un-assigned fields when no StructLayout is specified.
+ //
+ // Explicitly saying Sequential disables that warning/error for consumers which only
+ // use Stat in debug builds.
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FileStatus
+ {
+ internal FileStatusFlags Flags;
+ internal int Mode;
+ internal uint Uid;
+ internal uint Gid;
+ internal long Size;
+ internal long ATime;
+ internal long ATimeNsec;
+ internal long MTime;
+ internal long MTimeNsec;
+ internal long CTime;
+ internal long CTimeNsec;
+ internal long BirthTime;
+ internal long BirthTimeNsec;
+ internal long Dev;
+ internal long Ino;
+ }
+
+ internal static class FileTypes
+ {
+ internal const int S_IFMT = 0xF000;
+ internal const int S_IFIFO = 0x1000;
+ internal const int S_IFCHR = 0x2000;
+ internal const int S_IFDIR = 0x4000;
+ internal const int S_IFREG = 0x8000;
+ internal const int S_IFLNK = 0xA000;
+ internal const int S_IFSOCK = 0xC000;
+ }
+
+ [Flags]
+ internal enum FileStatusFlags
+ {
+ None = 0,
+ HasBirthTime = 1,
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FStat2", SetLastError = true)]
+ internal static extern int FStat(SafeFileHandle fd, out FileStatus output);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Stat2", SetLastError = true)]
+ internal static extern int Stat(string path, out FileStatus output);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_LStat2", SetLastError = true)]
+ internal static extern int LStat(string path, out FileStatus output);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.SysLog.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.SysLog.cs
new file mode 100644
index 0000000000..6b6a74b743
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.SysLog.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ internal enum SysLogPriority : int
+ {
+ // Priorities
+ LOG_EMERG = 0, /* system is unusable */
+ LOG_ALERT = 1, /* action must be taken immediately */
+ LOG_CRIT = 2, /* critical conditions */
+ LOG_ERR = 3, /* error conditions */
+ LOG_WARNING = 4, /* warning conditions */
+ LOG_NOTICE = 5, /* normal but significant condition */
+ LOG_INFO = 6, /* informational */
+ LOG_DEBUG = 7, /* debug-level messages */
+ // Facilities
+ LOG_KERN = (0<<3), /* kernel messages */
+ LOG_USER = (1<<3), /* random user-level messages */
+ LOG_MAIL = (2<<3), /* mail system */
+ LOG_DAEMON = (3<<3), /* system daemons */
+ LOG_AUTH = (4<<3), /* authorization messages */
+ LOG_SYSLOG = (5<<3), /* messages generated internally by syslogd */
+ LOG_LPR = (6<<3), /* line printer subsystem */
+ LOG_NEWS = (7<<3), /* network news subsystem */
+ LOG_UUCP = (8<<3), /* UUCP subsystem */
+ LOG_CRON = (9<<3), /* clock daemon */
+ LOG_AUTHPRIV = (10<<3), /* authorization messages (private) */
+ LOG_FTP = (11<<3), /* ftp daemon */
+ // Between FTP and Local is reserved for system use
+ LOG_LOCAL0 = (16<<3), /* reserved for local use */
+ LOG_LOCAL1 = (17<<3), /* reserved for local use */
+ LOG_LOCAL2 = (18<<3), /* reserved for local use */
+ LOG_LOCAL3 = (19<<3), /* reserved for local use */
+ LOG_LOCAL4 = (20<<3), /* reserved for local use */
+ LOG_LOCAL5 = (21<<3), /* reserved for local use */
+ LOG_LOCAL6 = (22<<3), /* reserved for local use */
+ LOG_LOCAL7 = (23<<3), /* reserved for local use */
+ }
+
+ /// <summary>
+ /// Write a message to the system logger, which in turn writes the message to the system console, log files, etc.
+ /// See man 3 syslog for more info
+ /// </summary>
+ /// <param name="priority">
+ /// The OR of a priority and facility in the SysLogPriority enum to declare the priority and facility of the log entry
+ /// </param>
+ /// <param name="message">The message to put in the log entry</param>
+ /// <param name="arg1">Like printf, the argument is passed to the variadic part of the C++ function to wildcards in the message</param>
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SysLog")]
+ internal static extern void SysLog(SysLogPriority priority, string message, string arg1);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Unlink.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Unlink.cs
new file mode 100644
index 0000000000..829210fa7e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Unlink.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Unlink", SetLastError = true)]
+ internal static extern int Unlink(string pathname);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Write.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Write.cs
new file mode 100644
index 0000000000..0636615a8b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Native/Interop.Write.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ /// <summary>
+ /// Writes the specified buffer to the provided open file descriptor
+ /// </summary>
+ /// <param name="fd">The file descriptor to try and write to</param>
+ /// <param name="buffer">The data to attempt to write</param>
+ /// <param name="bufferSize">The amount of data to write, in bytes</param>
+ /// <returns>
+ /// Returns the number of bytes written on success; otherwise, returns -1 and sets errno
+ /// </returns>
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Write", SetLastError = true)]
+ internal static extern unsafe int Write(SafeFileHandle fd, byte* buffer, int bufferSize);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Write", SetLastError = true)]
+ internal static extern unsafe int Write(int fd, byte* buffer, int bufferSize);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs b/src/System.Private.CoreLib/shared/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs
new file mode 100644
index 0000000000..75288accd9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class BCrypt
+ {
+ internal static unsafe int BCryptGenRandom(byte* pbBuffer, int count)
+ {
+ Debug.Assert(pbBuffer != null);
+ Debug.Assert(count >= 0);
+
+ return BCryptGenRandom(IntPtr.Zero, pbBuffer, count, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ }
+
+ private const int BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002;
+ internal const int STATUS_SUCCESS = 0x0;
+ internal const int STATUS_NO_MEMORY = unchecked((int)0xC0000017);
+
+ [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
+ private static extern unsafe int BCryptGenRandom(IntPtr hAlgorithm, byte* pbBuffer, int cbBuffer, int dwFlags);
+ }
+
+ internal static unsafe void GetRandomBytes(byte* buffer, int length)
+ {
+ int status = BCrypt.BCryptGenRandom(buffer, length);
+ if (status != BCrypt.STATUS_SUCCESS)
+ {
+ if (status == BCrypt.STATUS_NO_MEMORY)
+ {
+ throw new OutOfMemoryException();
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Crypt32/Interop.CryptProtectMemory.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Crypt32/Interop.CryptProtectMemory.cs
new file mode 100644
index 0000000000..b10cb6a041
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Crypt32/Interop.CryptProtectMemory.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+
+internal partial class Interop
+{
+ internal partial class Crypt32
+ {
+ internal const uint CRYPTPROTECTMEMORY_BLOCK_SIZE = 16;
+ internal const uint CRYPTPROTECTMEMORY_SAME_PROCESS = 0;
+
+ [DllImport(Libraries.Crypt32, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern bool CryptProtectMemory(SafeBSTRHandle pData, uint cbData, uint dwFlags);
+
+ [DllImport(Libraries.Crypt32, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern bool CryptUnprotectMemory(SafeBSTRHandle pData, uint cbData, uint dwFlags);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Interop.BOOL.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Interop.BOOL.cs
new file mode 100644
index 0000000000..9f4dab8935
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Interop.BOOL.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal partial class Interop
+{
+ /// <summary>
+ /// Blittable version of Windows BOOL type. It is convenient in situations where
+ /// manual marshalling is required, or to avoid overhead of regular bool marshalling.
+ /// </summary>
+ /// <remarks>
+ /// Some Windows APIs return arbitrary integer values although the return type is defined
+ /// as BOOL. It is best to never compare BOOL to TRUE. Always use bResult != BOOL.FALSE
+ /// or bResult == BOOL.FALSE .
+ /// </remarks>
+ internal enum BOOL : int
+ {
+ FALSE = 0,
+ TRUE = 1,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Interop.Errors.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Interop.Errors.cs
new file mode 100644
index 0000000000..7f907fb6ca
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Interop.Errors.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal partial class Interop
+{
+ // As defined in winerror.h and https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx
+ internal partial class Errors
+ {
+ internal const int ERROR_SUCCESS = 0x0;
+ internal const int ERROR_FILE_NOT_FOUND = 0x2;
+ internal const int ERROR_PATH_NOT_FOUND = 0x3;
+ internal const int ERROR_ACCESS_DENIED = 0x5;
+ internal const int ERROR_INVALID_HANDLE = 0x6;
+ internal const int ERROR_NOT_ENOUGH_MEMORY = 0x8;
+ internal const int ERROR_INVALID_DRIVE = 0xF;
+ internal const int ERROR_NO_MORE_FILES = 0x12;
+ internal const int ERROR_NOT_READY = 0x15;
+ internal const int ERROR_SHARING_VIOLATION = 0x20;
+ internal const int ERROR_HANDLE_EOF = 0x26;
+ internal const int ERROR_FILE_EXISTS = 0x50;
+ internal const int ERROR_INVALID_PARAMETER = 0x57;
+ internal const int ERROR_BROKEN_PIPE = 0x6D;
+ internal const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
+ internal const int ERROR_INVALID_NAME = 0x7B;
+ internal const int ERROR_BAD_PATHNAME = 0xA1;
+ internal const int ERROR_ALREADY_EXISTS = 0xB7;
+ internal const int ERROR_ENVVAR_NOT_FOUND = 0xCB;
+ internal const int ERROR_FILENAME_EXCED_RANGE = 0xCE;
+ internal const int ERROR_NO_DATA = 0xE8;
+ internal const int ERROR_MORE_DATA = 0xEA;
+ internal const int ERROR_NO_MORE_ITEMS = 0x103;
+ internal const int ERROR_NOT_OWNER = 0x120;
+ internal const int ERROR_TOO_MANY_POSTS = 0x12A;
+ internal const int ERROR_ARITHMETIC_OVERFLOW = 0x216;
+ internal const int ERROR_MUTANT_LIMIT_EXCEEDED = 0x24B;
+ internal const int ERROR_OPERATION_ABORTED = 0x3E3;
+ internal const int ERROR_IO_PENDING = 0x3E5;
+ internal const int ERROR_NO_UNICODE_TRANSLATION = 0x459;
+ internal const int ERROR_NOT_FOUND = 0x490;
+ internal const int ERROR_BAD_IMPERSONATION_LEVEL = 0x542;
+ internal const int ERROR_NO_SYSTEM_RESOURCES = 0x5AA;
+ internal const int E_FILENOTFOUND = unchecked((int)0x80070002);
+ internal const int ERROR_TIMEOUT = 0x000005B4;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Interop.Libraries.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Interop.Libraries.cs
new file mode 100644
index 0000000000..45d910bfcc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Interop.Libraries.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal static partial class Interop
+{
+ internal static partial class Libraries
+ {
+ internal const string BCrypt = "BCrypt.dll";
+ internal const string Crypt32 = "crypt32.dll";
+ internal const string Kernel32 = "kernel32.dll";
+ internal const string Ole32 = "ole32.dll";
+ internal const string OleAut32 = "oleaut32.dll";
+ internal const string User32 = "user32.dll";
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CREATEFILE2_EXTENDED_PARAMETERS.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CREATEFILE2_EXTENDED_PARAMETERS.cs
new file mode 100644
index 0000000000..16365ee651
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CREATEFILE2_EXTENDED_PARAMETERS.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ internal unsafe struct CREATEFILE2_EXTENDED_PARAMETERS
+ {
+ internal uint dwSize;
+ internal uint dwFileAttributes;
+ internal uint dwFileFlags;
+ internal uint dwSecurityQosFlags;
+ internal SECURITY_ATTRIBUTES* lpSecurityAttributes;
+ internal IntPtr hTemplateFile;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CancelIoEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CancelIoEx.cs
new file mode 100644
index 0000000000..fc99e3052f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CancelIoEx.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern unsafe bool CancelIoEx(SafeHandle handle, NativeOverlapped* lpOverlapped);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CloseHandle.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CloseHandle.cs
new file mode 100644
index 0000000000..96ed922a84
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CloseHandle.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool CloseHandle(IntPtr handle);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CreateFile.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CreateFile.cs
new file mode 100644
index 0000000000..9ee1e16fa6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CreateFile.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ /// <summary>
+ /// WARNING: This method does not implicitly handle long paths. Use CreateFile.
+ /// </summary>
+ [DllImport(Libraries.Kernel32, EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
+ private static extern SafeFileHandle CreateFilePrivate(
+ string lpFileName,
+ int dwDesiredAccess,
+ System.IO.FileShare dwShareMode,
+ ref SECURITY_ATTRIBUTES securityAttrs,
+ System.IO.FileMode dwCreationDisposition,
+ int dwFlagsAndAttributes,
+ IntPtr hTemplateFile);
+
+ internal static SafeFileHandle CreateFile(
+ string lpFileName,
+ int dwDesiredAccess,
+ System.IO.FileShare dwShareMode,
+ ref SECURITY_ATTRIBUTES securityAttrs,
+ System.IO.FileMode dwCreationDisposition,
+ int dwFlagsAndAttributes,
+ IntPtr hTemplateFile)
+ {
+ lpFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(lpFileName);
+ return CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, ref securityAttrs, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CreateFile2.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CreateFile2.cs
new file mode 100644
index 0000000000..ddc18f6c42
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.CreateFile2.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System.IO;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, EntryPoint = "CreateFile2", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern SafeFileHandle CreateFile2Private(
+ string lpFileName,
+ int dwDesiredAccess,
+ FileShare dwShareMode,
+ FileMode dwCreationDisposition,
+ ref Kernel32.CREATEFILE2_EXTENDED_PARAMETERS pCreateExParams);
+
+ internal static SafeFileHandle CreateFile2(
+ string lpFileName,
+ int dwDesiredAccess,
+ FileShare dwShareMode,
+ FileMode dwCreationDisposition,
+ ref Kernel32.CREATEFILE2_EXTENDED_PARAMETERS pCreateExParams)
+ {
+ lpFileName = PathInternal.EnsureExtendedPrefixOverMaxPath(lpFileName);
+ return CreateFile2Private(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, ref pCreateExParams);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FILE_INFO_BY_HANDLE_CLASS.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FILE_INFO_BY_HANDLE_CLASS.cs
new file mode 100644
index 0000000000..e31a453ba9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FILE_INFO_BY_HANDLE_CLASS.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ internal enum FILE_INFO_BY_HANDLE_CLASS : uint
+ {
+ FileBasicInfo = 0x0u,
+ FileStandardInfo = 0x1u,
+ FileNameInfo = 0x2u,
+ FileRenameInfo = 0x3u,
+ FileDispositionInfo = 0x4u,
+ FileAllocationInfo = 0x5u,
+ FileEndOfFileInfo = 0x6u,
+ FileStreamInfo = 0x7u,
+ FileCompressionInfo = 0x8u,
+ FileAttributeTagInfo = 0x9u,
+ FileIdBothDirectoryInfo = 0xAu,
+ FileIdBothDirectoryRestartInfo = 0xBu,
+ FileIoPriorityHintInfo = 0xCu,
+ FileRemoteProtocolInfo = 0xDu,
+ FileFullDirectoryInfo = 0xEu,
+ FileFullDirectoryRestartInfo = 0xFu,
+ FileStorageInfo = 0x10u,
+ FileAlignmentInfo = 0x11u,
+ FileIdInfo = 0x12u,
+ FileIdExtdDirectoryInfo = 0x13u,
+ FileIdExtdDirectoryRestartInfo = 0x14u,
+ MaximumFileInfoByHandleClass = 0x15u,
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FileAttributes.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FileAttributes.cs
new file mode 100644
index 0000000000..725a25a719
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FileAttributes.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ internal partial class FileAttributes
+ {
+ internal const int FILE_ATTRIBUTE_NORMAL = 0x00000080;
+ internal const int FILE_ATTRIBUTE_READONLY = 0x00000001;
+ internal const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
+ internal const int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FileTypes.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FileTypes.cs
new file mode 100644
index 0000000000..9d52f1f4f4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FileTypes.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ internal partial class FileTypes
+ {
+ internal const int FILE_TYPE_UNKNOWN = 0x0000;
+ internal const int FILE_TYPE_DISK = 0x0001;
+ internal const int FILE_TYPE_CHAR = 0x0002;
+ internal const int FILE_TYPE_PIPE = 0x0003;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FindClose.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FindClose.cs
new file mode 100644
index 0000000000..fcf9254aca
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FindClose.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern bool FindClose(IntPtr hFindFile);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FindFirstFileEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FindFirstFileEx.cs
new file mode 100644
index 0000000000..80b1ddd28d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FindFirstFileEx.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ /// <summary>
+ /// WARNING: This method does not implicitly handle long paths. Use FindFirstFile.
+ /// </summary>
+ [DllImport(Libraries.Kernel32, EntryPoint = "FindFirstFileExW", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern SafeFindHandle FindFirstFileExPrivate(string lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, ref WIN32_FIND_DATA lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, IntPtr lpSearchFilter, int dwAdditionalFlags);
+
+ internal static SafeFindHandle FindFirstFile(string fileName, ref WIN32_FIND_DATA data)
+ {
+ fileName = PathInternal.EnsureExtendedPrefixOverMaxPath(fileName);
+
+ // use FindExInfoBasic since we don't care about short name and it has better perf
+ return FindFirstFileExPrivate(fileName, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0);
+ }
+
+ internal enum FINDEX_INFO_LEVELS : uint
+ {
+ FindExInfoStandard = 0x0u,
+ FindExInfoBasic = 0x1u,
+ FindExInfoMaxInfoLevel = 0x2u,
+ }
+
+ internal enum FINDEX_SEARCH_OPS : uint
+ {
+ FindExSearchNameMatch = 0x0u,
+ FindExSearchLimitToDirectories = 0x1u,
+ FindExSearchLimitToDevices = 0x2u,
+ FindExSearchMaxSearchOp = 0x3u,
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FlushFileBuffers.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FlushFileBuffers.cs
new file mode 100644
index 0000000000..e10a2279cf
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FlushFileBuffers.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool FlushFileBuffers(SafeHandle hHandle);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs
new file mode 100644
index 0000000000..94722b6c8b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs
@@ -0,0 +1,112 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ private const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
+ private const int FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
+ private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
+ private const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
+
+
+ private const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
+
+ [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, EntryPoint = "FormatMessageW", SetLastError = true, BestFitMapping = true)]
+ private static extern int FormatMessage(
+ int dwFlags,
+ IntPtr lpSource,
+ uint dwMessageId,
+ int dwLanguageId,
+ [Out] StringBuilder lpBuffer,
+ int nSize,
+ IntPtr[] arguments);
+
+ /// <summary>
+ /// Returns a string message for the specified Win32 error code.
+ /// </summary>
+ internal static string GetMessage(int errorCode)
+ {
+ return GetMessage(IntPtr.Zero, errorCode);
+ }
+
+ internal static string GetMessage(IntPtr moduleHandle, int errorCode)
+ {
+ var sb = new StringBuilder(InitialBufferSize);
+ do
+ {
+ string errorMsg;
+ if (TryGetErrorMessage(moduleHandle, errorCode, sb, out errorMsg))
+ {
+ return errorMsg;
+ }
+ else
+ {
+ // increase the capacity of the StringBuilder.
+ sb.Capacity *= BufferSizeIncreaseFactor;
+ }
+ }
+ while (sb.Capacity < MaxAllowedBufferSize);
+
+ // If you come here then a size as large as 65K is also not sufficient and so we give the generic errorMsg.
+ return string.Format("Unknown error (0x{0:x})", errorCode);
+ }
+
+ private static bool TryGetErrorMessage(IntPtr moduleHandle, int errorCode, StringBuilder sb, out string errorMsg)
+ {
+ errorMsg = "";
+
+ int flags = FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY;
+ if (moduleHandle != IntPtr.Zero)
+ {
+ flags |= FORMAT_MESSAGE_FROM_HMODULE;
+ }
+
+ int result = FormatMessage(flags, moduleHandle, (uint)errorCode, 0, sb, sb.Capacity, null);
+ if (result != 0)
+ {
+ int i = sb.Length;
+ while (i > 0)
+ {
+ char ch = sb[i - 1];
+ if (ch > 32 && ch != '.') break;
+ i--;
+ }
+ errorMsg = sb.ToString(0, i);
+ }
+ else if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
+ {
+ return false;
+ }
+ else
+ {
+ errorMsg = string.Format("Unknown error (0x{0:x})", errorCode);
+ }
+
+ return true;
+ }
+
+ // Windows API FormatMessage lets you format a message string given an errorcode.
+ // Unlike other APIs this API does not support a way to query it for the total message size.
+ //
+ // So the API can only be used in one of these two ways.
+ // a. You pass a buffer of appropriate size and get the resource.
+ // b. Windows creates a buffer and passes the address back and the onus of releasing the buffer lies on the caller.
+ //
+ // Since the error code is coming from the user, it is not possible to know the size in advance.
+ // Unfortunately we can't use option b. since the buffer can only be freed using LocalFree and it is a private API on onecore.
+ // Also, using option b is ugly for the managed code and could cause memory leak in situations where freeing is unsuccessful.
+ //
+ // As a result we use the following approach.
+ // We initially call the API with a buffer size of 256 and then gradually increase the size in case of failure until we reach the maximum allowed limit of 65K.
+ private const int InitialBufferSize = 256;
+ private const int BufferSizeIncreaseFactor = 4;
+ private const int MaxAllowedBufferSize = 65 * 1024;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FreeEnvironmentStrings.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FreeEnvironmentStrings.cs
new file mode 100644
index 0000000000..c372a8553e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FreeEnvironmentStrings.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, EntryPoint = "FreeEnvironmentStringsW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
+ internal static extern unsafe bool FreeEnvironmentStrings(char* lpszEnvironmentBlock);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FreeLibrary.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FreeLibrary.cs
new file mode 100644
index 0000000000..c70865350a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FreeLibrary.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
+ internal static extern bool FreeLibrary(IntPtr hModule);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetCPInfo.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetCPInfo.cs
new file mode 100644
index 0000000000..1665119420
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetCPInfo.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Text;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ internal unsafe struct CPINFO
+ {
+ internal int MaxCharSize;
+
+ internal fixed byte DefaultChar[2 /* MAX_DEFAULTCHAR */];
+ internal fixed byte LeadByte[12 /* MAX_LEADBYTES */];
+ }
+
+ [DllImport(Libraries.Kernel32)]
+ internal static extern unsafe int GetCPInfo(uint codePage, CPINFO* lpCpInfo);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetEnvironmentStrings.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetEnvironmentStrings.cs
new file mode 100644
index 0000000000..29a686026a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetEnvironmentStrings.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, EntryPoint = "GetEnvironmentStringsW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
+ internal static extern unsafe char* GetEnvironmentStrings();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileAttributesEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileAttributesEx.cs
new file mode 100644
index 0000000000..181fb10105
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileAttributesEx.cs
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ /// <summary>
+ /// WARNING: This method does not implicitly handle long paths. Use GetFileAttributesEx.
+ /// </summary>
+ [DllImport(Libraries.Kernel32, EntryPoint = "GetFileAttributesExW", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern bool GetFileAttributesExPrivate(string name, GET_FILEEX_INFO_LEVELS fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);
+
+ internal static bool GetFileAttributesEx(string name, GET_FILEEX_INFO_LEVELS fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation)
+ {
+ name = PathInternal.EnsureExtendedPrefixOverMaxPath(name);
+ return GetFileAttributesExPrivate(name, fileInfoLevel, ref lpFileInformation);
+ }
+
+ internal enum GET_FILEEX_INFO_LEVELS : uint
+ {
+ GetFileExInfoStandard = 0x0u,
+ GetFileExMaxInfoLevel = 0x1u,
+ }
+
+ internal struct WIN32_FILE_ATTRIBUTE_DATA
+ {
+ internal int dwFileAttributes;
+ internal uint ftCreationTimeLow;
+ internal uint ftCreationTimeHigh;
+ internal uint ftLastAccessTimeLow;
+ internal uint ftLastAccessTimeHigh;
+ internal uint ftLastWriteTimeLow;
+ internal uint ftLastWriteTimeHigh;
+ internal uint fileSizeHigh;
+ internal uint fileSizeLow;
+
+ internal void PopulateFrom(ref WIN32_FIND_DATA findData)
+ {
+ // Copy the information to data
+ dwFileAttributes = (int)findData.dwFileAttributes;
+ ftCreationTimeLow = findData.ftCreationTime.dwLowDateTime;
+ ftCreationTimeHigh = findData.ftCreationTime.dwHighDateTime;
+ ftLastAccessTimeLow = findData.ftLastAccessTime.dwLowDateTime;
+ ftLastAccessTimeHigh = findData.ftLastAccessTime.dwHighDateTime;
+ ftLastWriteTimeLow = findData.ftLastWriteTime.dwLowDateTime;
+ ftLastWriteTimeHigh = findData.ftLastWriteTime.dwHighDateTime;
+ fileSizeHigh = findData.nFileSizeHigh;
+ fileSizeLow = findData.nFileSizeLow;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ [BestFitMapping(false)]
+ internal unsafe struct WIN32_FIND_DATA
+ {
+ internal uint dwFileAttributes;
+ internal FILE_TIME ftCreationTime;
+ internal FILE_TIME ftLastAccessTime;
+ internal FILE_TIME ftLastWriteTime;
+ internal uint nFileSizeHigh;
+ internal uint nFileSizeLow;
+ internal uint dwReserved0;
+ internal uint dwReserved1;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
+ internal string cFileName;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
+ internal string cAlternateFileName;
+ }
+
+ internal struct FILE_TIME
+ {
+ internal uint dwLowDateTime;
+ internal uint dwHighDateTime;
+
+ internal FILE_TIME(long fileTime)
+ {
+ dwLowDateTime = (uint)fileTime;
+ dwHighDateTime = (uint)(fileTime >> 32);
+ }
+
+ internal long ToTicks()
+ {
+ return ((long)dwHighDateTime << 32) + dwLowDateTime;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileInformationByHandleEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileInformationByHandleEx.cs
new file mode 100644
index 0000000000..1106cff1c5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileInformationByHandleEx.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern bool GetFileInformationByHandleEx(SafeFileHandle hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, out FILE_STANDARD_INFO lpFileInformation, uint dwBufferSize);
+
+ internal struct FILE_STANDARD_INFO
+ {
+ internal long AllocationSize;
+ internal long EndOfFile;
+ internal uint NumberOfLinks;
+ internal BOOL DeletePending;
+ internal BOOL Directory;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileType_SafeHandle.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileType_SafeHandle.cs
new file mode 100644
index 0000000000..faa57cc2f1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFileType_SafeHandle.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern int GetFileType(SafeHandle hFile);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs
new file mode 100644
index 0000000000..197b0a9be5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetFullPathNameW.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ /// <summary>
+ /// WARNING: This method does not implicitly handle long paths. Use GetFullPathName or PathHelper.
+ /// </summary>
+ [DllImport(Libraries.Kernel32, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
+ internal static extern uint GetFullPathNameW(ref char lpFileName, uint nBufferLength, ref char lpBuffer, IntPtr lpFilePart);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs
new file mode 100644
index 0000000000..81b4d096f5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetLongPathNameW.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ /// <summary>
+ /// WARNING: This method does not implicitly handle long paths. Use GetFullPath/PathHelper.
+ /// </summary>
+ [DllImport(Libraries.Kernel32, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
+ internal static extern uint GetLongPathNameW(ref char lpszShortPath, ref char lpszLongPath, uint cchBuffer);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetTempFileNameW.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetTempFileNameW.cs
new file mode 100644
index 0000000000..97e1d82847
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetTempFileNameW.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)]
+ internal static extern uint GetTempFileNameW(ref char lpPathName, string lpPrefixString, uint uUnique, ref char lpTempFileName);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetTempPathW.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetTempPathW.cs
new file mode 100644
index 0000000000..7f7bb775c8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetTempPathW.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, BestFitMapping = false)]
+ internal static extern uint GetTempPathW(int bufferLen, ref char buffer);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.Globalization.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.Globalization.cs
new file mode 100644
index 0000000000..2227d59b0e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.Globalization.cs
@@ -0,0 +1,133 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static unsafe partial class Kernel32
+ {
+ internal const int LOCALE_NAME_MAX_LENGTH = 85;
+ internal const uint LOCALE_ALLOW_NEUTRAL_NAMES = 0x08000000; // Flag to allow returning neutral names/lcids for name conversion
+ internal const uint LOCALE_SUPPLEMENTAL = 0x00000002;
+ internal const uint LOCALE_REPLACEMENT = 0x00000008;
+ internal const uint LOCALE_NEUTRALDATA = 0x00000010;
+ internal const uint LOCALE_SPECIFICDATA = 0x00000020;
+ internal const int COMPARE_STRING = 0x0001;
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern unsafe int LCIDToLocaleName(int locale, char *pLocaleName, int cchName, uint dwFlags);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int LocaleNameToLCID(string lpName, uint dwFlags);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern unsafe int LCMapStringEx(
+ string lpLocaleName,
+ uint dwMapFlags,
+ char* lpSrcStr,
+ int cchSrc,
+ void* lpDestStr,
+ int cchDest,
+ void* lpVersionInformation,
+ void* lpReserved,
+ IntPtr sortHandle);
+
+ [DllImport("kernel32.dll", EntryPoint = "FindNLSStringEx")]
+ internal static extern unsafe int FindNLSStringEx(
+ char* lpLocaleName,
+ uint dwFindNLSStringFlags,
+ char* lpStringSource,
+ int cchSource,
+ char* lpStringValue,
+ int cchValue,
+ int* pcchFound,
+ void* lpVersionInformation,
+ void* lpReserved,
+ IntPtr sortHandle);
+
+ [DllImport("kernel32.dll", EntryPoint = "CompareStringEx")]
+ internal static extern unsafe int CompareStringEx(
+ char* lpLocaleName,
+ uint dwCmpFlags,
+ char* lpString1,
+ int cchCount1,
+ char* lpString2,
+ int cchCount2,
+ void* lpVersionInformation,
+ void* lpReserved,
+ IntPtr lParam);
+
+ [DllImport("kernel32.dll", EntryPoint = "CompareStringOrdinal")]
+ internal static extern unsafe int CompareStringOrdinal(
+ char* lpString1,
+ int cchCount1,
+ char* lpString2,
+ int cchCount2,
+ bool bIgnoreCase);
+
+ [DllImport("kernel32.dll", EntryPoint = "FindStringOrdinal")]
+ internal static extern unsafe int FindStringOrdinal(
+ uint dwFindStringOrdinalFlags,
+ char* lpStringSource,
+ int cchSource,
+ char* lpStringValue,
+ int cchValue,
+ int bIgnoreCase);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern unsafe bool IsNLSDefinedString(
+ int Function,
+ uint dwFlags,
+ IntPtr lpVersionInformation,
+ char* lpString,
+ int cchStr);
+
+#if !ENABLE_WINRT
+ [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
+ internal static extern bool GetUserPreferredUILanguages(uint dwFlags, out uint pulNumLanguages, char [] pwszLanguagesBuffer, ref uint pcchLanguagesBuffer);
+#endif //!ENABLE_WINRT
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int GetLocaleInfoEx(string lpLocaleName, uint LCType, void* lpLCData, int cchData);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern bool EnumSystemLocalesEx(EnumLocalesProcEx lpLocaleEnumProcEx, uint dwFlags, void* lParam, IntPtr reserved);
+
+ internal delegate BOOL EnumLocalesProcEx(char* lpLocaleString, uint dwFlags, void* lParam);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int ResolveLocaleName(string lpNameToResolve, char* lpLocaleName, int cchLocaleName);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern bool EnumTimeFormatsEx(EnumTimeFormatsProcEx lpTimeFmtEnumProcEx, string lpLocaleName, uint dwFlags, void* lParam);
+
+ internal delegate BOOL EnumTimeFormatsProcEx(char* lpTimeFormatString, void* lParam);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int GetCalendarInfoEx(string lpLocaleName, uint Calendar, IntPtr lpReserved, uint CalType, IntPtr lpCalData, int cchData, out int lpValue);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int GetCalendarInfoEx(string lpLocaleName, uint Calendar, IntPtr lpReserved, uint CalType, IntPtr lpCalData, int cchData, IntPtr lpValue);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern bool EnumCalendarInfoExEx(EnumCalendarInfoProcExEx pCalInfoEnumProcExEx, string lpLocaleName, uint Calendar, string lpReserved, uint CalType, void* lParam);
+
+ internal delegate BOOL EnumCalendarInfoProcExEx(char* lpCalendarInfoString, uint Calendar, IntPtr lpReserved, void* lParam);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NlsVersionInfoEx
+ {
+ internal int dwNLSVersionInfoSize;
+ internal int dwNLSVersion;
+ internal int dwDefinedVersion;
+ internal int dwEffectiveId;
+ internal Guid guidCustomVersion;
+ }
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ internal static extern unsafe bool GetNLSVersionEx(int function, string localeName, NlsVersionInfoEx* lpVersionInformation);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.LoadLibraryEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.LoadLibraryEx.cs
new file mode 100644
index 0000000000..4eef5852fe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.LoadLibraryEx.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ internal const int LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
+
+ [DllImport(Libraries.Kernel32, EntryPoint = "LoadLibraryExW", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern SafeLibraryHandle LoadLibraryEx(string libFilename, IntPtr reserved, int flags);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.LockFile.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.LockFile.cs
new file mode 100644
index 0000000000..a21d00f4f6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.LockFile.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern bool LockFile(SafeFileHandle handle, int offsetLow, int offsetHigh, int countLow, int countHigh);
+
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern bool UnlockFile(SafeFileHandle handle, int offsetLow, int offsetHigh, int countLow, int countHigh);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MAX_PATH.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MAX_PATH.cs
new file mode 100644
index 0000000000..f7fa32669b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MAX_PATH.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ internal const int MAX_PATH = 260;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MUI.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MUI.cs
new file mode 100644
index 0000000000..6ed7aa2dc4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MUI.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+internal static partial class Interop
+{
+ internal static partial class Kernel32
+ {
+ internal const uint MUI_PREFERRED_UI_LANGUAGES = 0x10;
+
+ [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
+ internal static extern bool GetFileMUIPath(uint flags, String filePath, [Out] StringBuilder language, ref int languageLength, [Out] StringBuilder fileMuiPath, ref int fileMuiPathLength, ref Int64 enumerator);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MultiByteToWideChar.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MultiByteToWideChar.cs
new file mode 100644
index 0000000000..158e4db3fd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MultiByteToWideChar.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32)]
+ internal static extern unsafe int MultiByteToWideChar(
+ uint CodePage, uint dwFlags,
+ byte* lpMultiByteStr, int cbMultiByte,
+ char* lpWideCharStr, int cchWideChar);
+
+ internal const uint MB_PRECOMPOSED = 0x00000001;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.OutputDebugString.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.OutputDebugString.cs
new file mode 100644
index 0000000000..8da50ff8ab
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.OutputDebugString.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Interop.Libraries.Kernel32, CharSet = CharSet.Unicode, EntryPoint = "OutputDebugStringW", ExactSpelling = true)]
+ internal static extern void OutputDebugString(string message);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.ReadFile_SafeHandle_IntPtr.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.ReadFile_SafeHandle_IntPtr.cs
new file mode 100644
index 0000000000..076f7f136f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.ReadFile_SafeHandle_IntPtr.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern unsafe int ReadFile(
+ SafeHandle handle,
+ byte* bytes,
+ int numBytesToRead,
+ out int numBytesRead,
+ IntPtr mustBeZero);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.ReadFile_SafeHandle_NativeOverlapped.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.ReadFile_SafeHandle_NativeOverlapped.cs
new file mode 100644
index 0000000000..3ae65a8806
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.ReadFile_SafeHandle_NativeOverlapped.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern unsafe int ReadFile(
+ SafeHandle handle,
+ byte* bytes,
+ int numBytesToRead,
+ IntPtr numBytesRead_mustBeZero,
+ NativeOverlapped* overlapped);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SECURITY_ATTRIBUTES.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SECURITY_ATTRIBUTES.cs
new file mode 100644
index 0000000000..8d31f8622f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SECURITY_ATTRIBUTES.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SECURITY_ATTRIBUTES
+ {
+ internal uint nLength;
+ internal IntPtr lpSecurityDescriptor;
+ internal BOOL bInheritHandle;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SecurityOptions.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SecurityOptions.cs
new file mode 100644
index 0000000000..4a4402484f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SecurityOptions.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ internal partial class SecurityOptions
+ {
+ internal const int SECURITY_SQOS_PRESENT = 0x00100000;
+ internal const int SECURITY_ANONYMOUS = 0 << 16;
+ internal const int SECURITY_IDENTIFICATION = 1 << 16;
+ internal const int SECURITY_IMPERSONATION = 2 << 16;
+ internal const int SECURITY_DELEGATION = 3 << 16;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetEndOfFile.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetEndOfFile.cs
new file mode 100644
index 0000000000..e5d60041a8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetEndOfFile.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern bool SetEndOfFile(SafeFileHandle hFile);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetEnvironmentVariable.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetEnvironmentVariable.cs
new file mode 100644
index 0000000000..cff406fca5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetEnvironmentVariable.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, EntryPoint = "SetEnvironmentVariableW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
+ internal static extern bool SetEnvironmentVariable(string lpName, string lpValue);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetFilePointerEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetFilePointerEx.cs
new file mode 100644
index 0000000000..c0e5247a52
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetFilePointerEx.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern bool SetFilePointerEx(SafeFileHandle hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetThreadErrorMode.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetThreadErrorMode.cs
new file mode 100644
index 0000000000..123eb75d7b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.SetThreadErrorMode.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32, SetLastError = true, ExactSpelling = true)]
+ internal static extern bool SetThreadErrorMode(uint dwNewMode, out uint lpOldMode);
+
+ internal const uint SEM_FAILCRITICALERRORS = 1;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.TimeZone.Registry.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.TimeZone.Registry.cs
new file mode 100644
index 0000000000..062d1caeba
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.TimeZone.Registry.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Kernel32
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct REG_TZI_FORMAT
+ {
+ internal int Bias;
+ internal int StandardBias;
+ internal int DaylightBias;
+ internal SYSTEMTIME StandardDate;
+ internal SYSTEMTIME DaylightDate;
+
+ internal REG_TZI_FORMAT(in TIME_ZONE_INFORMATION tzi)
+ {
+ Bias = tzi.Bias;
+ StandardDate = tzi.StandardDate;
+ StandardBias = tzi.StandardBias;
+ DaylightDate = tzi.DaylightDate;
+ DaylightBias = tzi.DaylightBias;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.TimeZone.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.TimeZone.cs
new file mode 100644
index 0000000000..68d4583b54
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.TimeZone.cs
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Kernel32
+ {
+ internal struct SYSTEMTIME
+ {
+ internal ushort Year;
+ internal ushort Month;
+ internal ushort DayOfWeek;
+ internal ushort Day;
+ internal ushort Hour;
+ internal ushort Minute;
+ internal ushort Second;
+ internal ushort Milliseconds;
+
+ internal bool Equals(in SYSTEMTIME other) =>
+ Year == other.Year &&
+ Month == other.Month &&
+ DayOfWeek == other.DayOfWeek &&
+ Day == other.Day &&
+ Hour == other.Hour &&
+ Minute == other.Minute &&
+ Second == other.Second &&
+ Milliseconds == other.Milliseconds;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal unsafe struct TIME_DYNAMIC_ZONE_INFORMATION
+ {
+ internal int Bias;
+ internal fixed char StandardName[32];
+ internal SYSTEMTIME StandardDate;
+ internal int StandardBias;
+ internal fixed char DaylightName[32];
+ internal SYSTEMTIME DaylightDate;
+ internal int DaylightBias;
+ internal fixed char TimeZoneKeyName[128];
+ internal byte DynamicDaylightTimeDisabled;
+
+ internal string GetTimeZoneKeyName()
+ {
+ fixed (char* p = TimeZoneKeyName)
+ return new string(p);
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal unsafe struct TIME_ZONE_INFORMATION
+ {
+ internal int Bias;
+ internal fixed char StandardName[32];
+ internal SYSTEMTIME StandardDate;
+ internal int StandardBias;
+ internal fixed char DaylightName[32];
+ internal SYSTEMTIME DaylightDate;
+ internal int DaylightBias;
+
+ internal TIME_ZONE_INFORMATION(in TIME_DYNAMIC_ZONE_INFORMATION dtzi)
+ {
+ // The start of TIME_DYNAMIC_ZONE_INFORMATION has identical layout as TIME_ZONE_INFORMATION
+ fixed (TIME_ZONE_INFORMATION* pTo = &this)
+ fixed (TIME_DYNAMIC_ZONE_INFORMATION* pFrom = &dtzi)
+ *pTo = *(TIME_ZONE_INFORMATION*)pFrom;
+ }
+
+ internal string GetStandardName()
+ {
+ fixed (char* p = StandardName)
+ return new string(p);
+ }
+
+ internal string GetDaylightName()
+ {
+ fixed (char* p = DaylightName)
+ return new string(p);
+ }
+ }
+
+ internal const uint TIME_ZONE_ID_INVALID = unchecked((uint)-1);
+
+ [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
+ internal static extern uint GetDynamicTimeZoneInformation(out TIME_DYNAMIC_ZONE_INFORMATION pTimeZoneInformation);
+
+ [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
+ internal static extern uint GetTimeZoneInformation(out TIME_ZONE_INFORMATION lpTimeZoneInformation);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WideCharToMultiByte.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WideCharToMultiByte.cs
new file mode 100644
index 0000000000..a06c9153f4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WideCharToMultiByte.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ [DllImport(Libraries.Kernel32)]
+ internal static extern unsafe int WideCharToMultiByte(
+ uint CodePage, uint dwFlags,
+ char* lpWideCharStr, int cchWideChar,
+ byte* lpMultiByteStr, int cbMultiByte,
+ IntPtr lpDefaultChar, IntPtr lpUsedDefaultChar);
+
+ internal const uint CP_ACP = 0;
+ internal const uint WC_NO_BEST_FIT_CHARS = 0x00000400;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WriteFile_SafeHandle_IntPtr.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WriteFile_SafeHandle_IntPtr.cs
new file mode 100644
index 0000000000..69651ca1ca
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WriteFile_SafeHandle_IntPtr.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ // Note there are two different WriteFile prototypes - this is to use
+ // the type system to force you to not trip across a "feature" in
+ // Win32's async IO support. You can't do the following three things
+ // simultaneously: overlapped IO, free the memory for the overlapped
+ // struct in a callback (or an EndWrite method called by that callback),
+ // and pass in an address for the numBytesRead parameter.
+
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern unsafe int WriteFile(SafeHandle handle, byte* bytes, int numBytesToWrite, out int numBytesWritten, IntPtr mustBeZero);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WriteFile_SafeHandle_NativeOverlapped.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WriteFile_SafeHandle_NativeOverlapped.cs
new file mode 100644
index 0000000000..dc1e97555b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.WriteFile_SafeHandle_NativeOverlapped.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+internal partial class Interop
+{
+ internal partial class Kernel32
+ {
+ // Note there are two different WriteFile prototypes - this is to use
+ // the type system to force you to not trip across a "feature" in
+ // Win32's async IO support. You can't do the following three things
+ // simultaneously: overlapped IO, free the memory for the overlapped
+ // struct in a callback (or an EndWrite method called by that callback),
+ // and pass in an address for the numBytesRead parameter.
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
+ internal static extern unsafe int WriteFile(SafeHandle handle, byte* bytes, int numBytesToWrite, IntPtr numBytesWritten_mustBeZero, NativeOverlapped* lpOverlapped);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Normaliz/Interop.Idna.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Normaliz/Interop.Idna.cs
new file mode 100644
index 0000000000..f49ce8dd61
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Normaliz/Interop.Idna.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Normaliz
+ {
+ //
+ // Idn APIs
+ //
+
+ [DllImport("Normaliz.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern unsafe int IdnToAscii(
+ uint dwFlags,
+ char* lpUnicodeCharStr,
+ int cchUnicodeChar,
+ char* lpASCIICharStr,
+ int cchASCIIChar);
+
+ [DllImport("Normaliz.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern unsafe int IdnToUnicode(
+ uint dwFlags,
+ char* lpASCIICharStr,
+ int cchASCIIChar,
+ char* lpUnicodeCharStr,
+ int cchUnicodeChar);
+
+ internal const int IDN_ALLOW_UNASSIGNED = 0x1;
+ internal const int IDN_USE_STD3_ASCII_RULES = 0x2;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Normaliz/Interop.Normalization.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Normaliz/Interop.Normalization.cs
new file mode 100644
index 0000000000..3e49f1f64c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Normaliz/Interop.Normalization.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class Normaliz
+ {
+ [DllImport("Normaliz.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern bool IsNormalizedString(int normForm, string source, int length);
+
+ [DllImport("Normaliz.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern int NormalizeString(
+ int normForm,
+ string source,
+ int sourceLength,
+ [System.Runtime.InteropServices.OutAttribute()]
+ char[] destination,
+ int destinationLength);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Ole32/Interop.CoCreateGuid.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Ole32/Interop.CoCreateGuid.cs
new file mode 100644
index 0000000000..57accbe7c0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/Ole32/Interop.CoCreateGuid.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Ole32
+ {
+ [DllImport(Interop.Libraries.Ole32)]
+ internal static extern int CoCreateGuid(out Guid guid);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysAllocStringLen.cs b/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysAllocStringLen.cs
new file mode 100644
index 0000000000..1af2b3fce9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysAllocStringLen.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+
+internal partial class Interop
+{
+ internal partial class OleAut32
+ {
+ [DllImport(Libraries.OleAut32, CharSet = CharSet.Unicode)]
+ internal static extern SafeBSTRHandle SysAllocStringLen(IntPtr src, uint len);
+
+ [DllImport(Libraries.OleAut32, CharSet = CharSet.Unicode)]
+ internal static extern IntPtr SysAllocStringLen(String src, int len);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysFreeString.cs b/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysFreeString.cs
new file mode 100644
index 0000000000..8673cc6724
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysFreeString.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class OleAut32
+ {
+ [DllImport(Libraries.OleAut32)]
+ internal static extern void SysFreeString(IntPtr bstr);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysStringLen.cs b/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysStringLen.cs
new file mode 100644
index 0000000000..cf65d6b086
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/OleAut32/Interop.SysStringLen.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+
+internal partial class Interop
+{
+ internal partial class OleAut32
+ {
+ [DllImport(Libraries.OleAut32)]
+ internal static extern uint SysStringLen(SafeBSTRHandle bstr);
+
+ [DllImport(Libraries.OleAut32)]
+ internal static extern uint SysStringLen(IntPtr bstr);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.Constants.cs b/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.Constants.cs
new file mode 100644
index 0000000000..f1f09740e9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.Constants.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal partial class Interop
+{
+ internal partial class User32
+ {
+ internal const int HWND_BROADCAST = 0xffff;
+ internal const int WM_SETTINGCHANGE = 0x001A;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.LoadString.cs b/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.LoadString.cs
new file mode 100644
index 0000000000..d3d575e221
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.LoadString.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.Win32.SafeHandles;
+
+internal partial class Interop
+{
+ internal partial class User32
+ {
+ [DllImport(Libraries.User32, SetLastError = true, EntryPoint = "LoadStringW", CharSet = CharSet.Unicode)]
+ internal static extern int LoadString(SafeLibraryHandle handle, int id, [Out] StringBuilder buffer, int bufferLength);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.SendMessageTimeout.cs b/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.SendMessageTimeout.cs
new file mode 100644
index 0000000000..c8a97e6b9d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.SendMessageTimeout.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class User32
+ {
+ [DllImport(Libraries.User32, EntryPoint = "SendMessageTimeoutW", CharSet = CharSet.Unicode)]
+ public static extern IntPtr SendMessageTimeout(IntPtr hWnd, int msg, IntPtr wParam, string lParam, int flags, int timeout, IntPtr pdwResult);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/CriticalHandleMinusOneIsInvalid.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/CriticalHandleMinusOneIsInvalid.cs
new file mode 100644
index 0000000000..a76c51d966
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/CriticalHandleMinusOneIsInvalid.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ // Class of critical handle which uses only -1 as an invalid handle.
+ public abstract class CriticalHandleMinusOneIsInvalid : CriticalHandle
+ {
+ protected CriticalHandleMinusOneIsInvalid()
+ : base(new IntPtr(-1))
+ {
+ }
+
+ public override bool IsInvalid => handle == new IntPtr(-1);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/CriticalHandleZeroOrMinusOneIsInvalid.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/CriticalHandleZeroOrMinusOneIsInvalid.cs
new file mode 100644
index 0000000000..28d0219489
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/CriticalHandleZeroOrMinusOneIsInvalid.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ // Class of critical handle which uses 0 or -1 as an invalid handle.
+ public abstract class CriticalHandleZeroOrMinusOneIsInvalid : CriticalHandle
+ {
+ protected CriticalHandleZeroOrMinusOneIsInvalid()
+ : base(IntPtr.Zero)
+ {
+ }
+
+ public override bool IsInvalid => handle == IntPtr.Zero || handle == new IntPtr(-1);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeDirectoryHandle.Unix.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeDirectoryHandle.Unix.cs
new file mode 100644
index 0000000000..f7435eaae1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeDirectoryHandle.Unix.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ internal sealed class SafeDirectoryHandle : SafeHandle
+ {
+ private SafeDirectoryHandle() : base(IntPtr.Zero, true)
+ {
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ return Interop.Sys.CloseDir(handle) == 0;
+ }
+
+ public override bool IsInvalid
+ {
+ get { return handle == IntPtr.Zero; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
new file mode 100644
index 0000000000..52973f2abb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
@@ -0,0 +1,145 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ private SafeFileHandle() : this(ownsHandle: true)
+ {
+ }
+
+ private SafeFileHandle(bool ownsHandle)
+ : base(ownsHandle)
+ {
+ SetHandle(new IntPtr(-1));
+ }
+
+ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : this(ownsHandle)
+ {
+ SetHandle(preexistingHandle);
+ }
+
+ internal bool? IsAsync { get; set; }
+
+ /// <summary>Opens the specified file with the requested flags and mode.</summary>
+ /// <param name="path">The path to the file.</param>
+ /// <param name="flags">The flags with which to open the file.</param>
+ /// <param name="mode">The mode for opening the file.</param>
+ /// <returns>A SafeFileHandle for the opened file.</returns>
+ internal static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int mode)
+ {
+ Debug.Assert(path != null);
+ SafeFileHandle handle = Interop.Sys.Open(path, flags, mode);
+
+ if (handle.IsInvalid)
+ {
+ handle.Dispose();
+ Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
+
+ // If we fail to open the file due to a path not existing, we need to know whether to blame
+ // the file itself or its directory. If we're creating the file, then we blame the directory,
+ // otherwise we blame the file.
+ //
+ // When opening, we need to align with Windows, which considers a missing path to be
+ // FileNotFound only if the containing directory exists.
+
+ bool isDirectory = (error.Error == Interop.Error.ENOENT) &&
+ ((flags & Interop.Sys.OpenFlags.O_CREAT) != 0
+ || !DirectoryExists(Path.GetDirectoryName(PathInternal.TrimEndingDirectorySeparator(path))));
+
+ Interop.CheckIo(
+ error.Error,
+ path,
+ isDirectory,
+ errorRewriter: e => (e.Error == Interop.Error.EISDIR) ? Interop.Error.EACCES.Info() : e);
+ }
+
+ // Make sure it's not a directory; we do this after opening it once we have a file descriptor
+ // to avoid race conditions.
+ Interop.Sys.FileStatus status;
+ if (Interop.Sys.FStat(handle, out status) != 0)
+ {
+ handle.Dispose();
+ throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), path);
+ }
+ if ((status.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR)
+ {
+ handle.Dispose();
+ throw Interop.GetExceptionForIoErrno(Interop.Error.EACCES.Info(), path, isDirectory: true);
+ }
+
+ return handle;
+ }
+
+ private static bool DirectoryExists(string fullPath)
+ {
+ Interop.Sys.FileStatus fileinfo;
+
+ // First use stat, as we want to follow symlinks. If that fails, it could be because the symlink
+ // is broken, we don't have permissions, etc., in which case fall back to using LStat to evaluate
+ // based on the symlink itself.
+ if (Interop.Sys.Stat(fullPath, out fileinfo) < 0 &&
+ Interop.Sys.LStat(fullPath, out fileinfo) < 0)
+ {
+ return false;
+ }
+
+ return ((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR);
+ }
+
+ /// <summary>Opens a SafeFileHandle for a file descriptor created by a provided delegate.</summary>
+ /// <param name="fdFunc">
+ /// The function that creates the file descriptor. Returns the file descriptor on success, or an invalid
+ /// file descriptor on error with Marshal.GetLastWin32Error() set to the error code.
+ /// </param>
+ /// <returns>The created SafeFileHandle.</returns>
+ internal static SafeFileHandle Open(Func<SafeFileHandle> fdFunc)
+ {
+ SafeFileHandle handle = Interop.CheckIo(fdFunc());
+
+ Debug.Assert(!handle.IsInvalid, "File descriptor is invalid");
+ return handle;
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ // When the SafeFileHandle was opened, we likely issued an flock on the created descriptor in order to add
+ // an advisory lock. This lock should be removed via closing the file descriptor, but close can be
+ // interrupted, and we don't retry closes. As such, we could end up leaving the file locked,
+ // which could prevent subsequent usage of the file until this process dies. To avoid that, we proactively
+ // try to release the lock before we close the handle. (If it's not locked, there's no behavioral
+ // problem trying to unlock it.)
+ Interop.Sys.FLock(handle, Interop.Sys.LockOperations.LOCK_UN); // ignore any errors
+
+ // Close the descriptor. Although close is documented to potentially fail with EINTR, we never want
+ // to retry, as the descriptor could actually have been closed, been subsequently reassigned, and
+ // be in use elsewhere in the process. Instead, we simply check whether the call was successful.
+ int result = Interop.Sys.Close(handle);
+#if DEBUG
+ if (result != 0)
+ {
+ Debug.Fail(string.Format(
+ "Close failed with result {0} and error {1}",
+ result, Interop.Sys.GetLastErrorInfo()));
+ }
+#endif
+ return result == 0;
+ }
+
+ public override bool IsInvalid
+ {
+ get
+ {
+ long h = (long)handle;
+ return h < 0 || h > int.MaxValue;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
new file mode 100644
index 0000000000..4eabe8f08c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Security;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.Win32;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ private bool? _isAsync;
+
+ private SafeFileHandle() : base(true)
+ {
+ _isAsync = null;
+ }
+
+ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle)
+ {
+ SetHandle(preexistingHandle);
+
+ _isAsync = null;
+ }
+
+ internal bool? IsAsync
+ {
+ get
+ {
+ return _isAsync;
+ }
+
+ set
+ {
+ _isAsync = value;
+ }
+ }
+
+ internal ThreadPoolBoundHandle ThreadPoolBinding { get; set; }
+
+ override protected bool ReleaseHandle()
+ {
+ return Interop.Kernel32.CloseHandle(handle);
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs
new file mode 100644
index 0000000000..4ba05409fd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Security;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using Microsoft.Win32;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ internal SafeFindHandle() : base(true) { }
+
+ override protected bool ReleaseHandle()
+ {
+ return Interop.Kernel32.FindClose(handle);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeHandleMinusOneIsInvalid.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeHandleMinusOneIsInvalid.cs
new file mode 100644
index 0000000000..5415f2c35d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeHandleMinusOneIsInvalid.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ // Class of safe handle which uses only -1 as an invalid handle.
+ public abstract class SafeHandleMinusOneIsInvalid : SafeHandle
+ {
+ protected SafeHandleMinusOneIsInvalid(bool ownsHandle) : base(new IntPtr(-1), ownsHandle)
+ {
+ }
+
+ public override bool IsInvalid => handle == new IntPtr(-1);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs
new file mode 100644
index 0000000000..8d0220bf90
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ // Class of safe handle which uses 0 or -1 as an invalid handle.
+ public abstract class SafeHandleZeroOrMinusOneIsInvalid : SafeHandle
+ {
+ protected SafeHandleZeroOrMinusOneIsInvalid(bool ownsHandle) : base(IntPtr.Zero, ownsHandle)
+ {
+ }
+
+ public override bool IsInvalid => handle == IntPtr.Zero || handle == new IntPtr(-1);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeLibraryHandle.cs b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeLibraryHandle.cs
new file mode 100644
index 0000000000..3be2e354ab
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/Microsoft/Win32/SafeHandles/SafeLibraryHandle.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Win32.SafeHandles
+{
+ sealed internal class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ internal SafeLibraryHandle() : base(true) { }
+
+ override protected bool ReleaseHandle()
+ {
+ return Interop.Kernel32.FreeLibrary(handle);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/README.md b/src/System.Private.CoreLib/shared/README.md
new file mode 100644
index 0000000000..beda969ff7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/README.md
@@ -0,0 +1,19 @@
+# System.Private.CoreLib Shared Sources
+
+This directory contains the shared sources for System.Private.CoreLib. These are shared between [dotnet/corert](https://github.com/dotnet/corert/tree/master/src/System.Private.CoreLib/shared), [dotnet/coreclr](https://github.com/dotnet/coreclr/tree/master/src/mscorlib/shared) and [dotnet/corefx](https://github.com/dotnet/corefx/tree/master/src/Common/src/CoreLib).
+
+The sources are synchronized with a mirroring tool that watches for new commits on either side and creates new pull requests (as @dotnet-bot) in the other repository.
+
+## Conventions
+
+Code in the shared directory should have no code specific to CoreCLR, CoreRT or CoreFX. Parts of classes that need to have different implementations on different runtimes should use partial classes and &#42;.CoreRT.cs/&#42;.CoreCLR.cs/&#42;.CoreFX.cs files in the non shared portion. Code that is different based on platform (Windows/Unix) is fine to leave in the shared portion. Remember to follow the [style guidelines](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md).
+
+## Getting clean CI and merging the mirror PRs
+
+Once the mirror PR is created there is a chance that the new code will require changes to get a clean CI. Any changes can be added to the PR by checking out the PR branch and adding new commits. Please follow the following guidelines for modifying these PRs.
+
+ - **DO NOT** modify the commits made by @dotnet-bot in any way.
+ - **TRY** to only make changes outside of shared.
+ - Changes made in the shared folder in additional commits will get mirrored properly if the mirror PR is merged with a **REBASE**
+ - **ALWAYS** Merge the mirror PR with the **REBASE** option.
+ - Using one of the other options will cause the mirror to miss commits
diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
new file mode 100644
index 0000000000..ca804e3b7a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
@@ -0,0 +1,827 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+ <HasSharedItems>true</HasSharedItems>
+ <SharedGUID>c5ed3c1d-b572-46f1-8f96-522a85ce1179</SharedGUID>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration">
+ <Import_RootNamespace />
+ </PropertyGroup>
+ <PropertyGroup>
+ <TargetsWindows Condition="'$(TargetsWindows)' != 'true'">false</TargetsWindows>
+ <TargetsUnix Condition="'$(TargetsUnix)' != 'true'">false</TargetsUnix>
+ <TargetsOSX Condition="'$(TargetsOSX)' != 'true'">false</TargetsOSX>
+ </PropertyGroup>
+ <ItemDefinitionGroup>
+ <Compile>
+ <Visible>true</Visible>
+ </Compile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildThisFileDirectory)Internal\IO\File.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Internal\Runtime\CompilerServices\Unsafe.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\CriticalHandleMinusOneIsInvalid.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\CriticalHandleZeroOrMinusOneIsInvalid.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeHandleMinusOneIsInvalid.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeHandleZeroOrMinusOneIsInvalid.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Action.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\AccessViolationException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ApplicationException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\AggregateException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ArgumentException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ArgumentNullException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ArgumentOutOfRangeException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ArithmeticException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ArraySegment.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ArrayTypeMismatchException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\AssemblyLoadEventArgs.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\AssemblyLoadEventHandler.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\AsyncCallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\AttributeTargets.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\AttributeUsageAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\BadImageFormatException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\BitConverter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Boolean.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\ArrayPool.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\ArrayPoolEventSource.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\ConfigurableArrayPool.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\IMemoryOwner.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\IPinnable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\MemoryHandle.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\MemoryManager.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\TlsOverPerCoreLockedStacksArrayPool.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Utilities.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\FormattingHelpers.CountDigits.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Byte.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Char.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\CharEnumerator.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\CLSCompliantAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Comparer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\DictionaryEntry.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\Dictionary.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\ICollection.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\ICollectionDebugView.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IComparer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IDictionary.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IDictionaryDebugView.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IEnumerable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IEnumerator.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IEqualityComparer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IList.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IReadOnlyCollection.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IReadOnlyDictionary.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\IReadOnlyList.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\KeyNotFoundException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\KeyValuePair.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\NonRandomizedStringEqualityComparer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\ValueListBuilder.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Generic\List.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\CompatibleComparer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\Hashtable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\HashHelpers.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\HashHelpers.SerializationInfoTable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\ICollection.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IComparer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IDictionary.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IDictionaryEnumerator.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IEnumerable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IEnumerator.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IEqualityComparer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IHashCodeProvider.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IList.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IStructuralComparable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\IStructuralEquatable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\KeyValuePairs.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\ListDictionaryInternal.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\ObjectModel\Collection.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Collections\ObjectModel\ReadOnlyCollection.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ComponentModel\DefaultValueAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ComponentModel\EditorBrowsableAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Configuration\Assemblies\AssemblyHashAlgorithm.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Configuration\Assemblies\AssemblyVersionCompatibility.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Convert.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Convert.Base64.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\CurrentSystemTimeZone.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DataMisalignedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DateTime.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DateTimeKind.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DateTimeOffset.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DayOfWeek.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DBNull.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DefaultBinder.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\CodeAnalysis\SuppressMessageAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\ConditionalAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Debug.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggableAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerBrowsableAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerDisplayAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerHiddenAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerNonUserCodeAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerStepThroughAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerStepperBoundaryAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerTypeProxyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerVisualizerAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\StackTraceHiddenAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DivideByZeroException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DllNotFoundException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Double.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\DuplicateWaitObjectException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Empty.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\EntryPointNotFoundException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\EventArgs.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\EventHandler.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ExecutionEngineException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\FieldAccessException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\FlagsAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\FormatException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\FormattableString.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Gen2GcCallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\BidiCategory.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\Calendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarAlgorithmType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarData.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarWeekRule.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendricalCalculationsHelper.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CharUnicodeInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\ChineseLunisolarCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareInfo.Invariant.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureNotFoundException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureTypes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DateTimeFormat.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DateTimeFormatInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DateTimeFormatInfoScanner.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DateTimeParse.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DateTimeStyles.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DaylightTime.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\DigitShapes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\EastAsianLunisolarCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GregorianCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GregorianCalendarHelper.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GregorianCalendarTypes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HebrewCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HebrewNumber.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\IdnMapping.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\InternalGlobalizationHelper.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseLunisolarCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JulianCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\KoreanCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\KoreanLunisolarCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\NumberFormatInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\NumberStyles.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\PersianCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\RegionInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\SortKey.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\SortVersion.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\StringInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TaiwanCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TaiwanLunisolarCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextElementEnumerator.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\ThaiBuddhistCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TimeSpanFormat.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TimeSpanParse.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TimeSpanStyles.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\UmAlQuraCalendar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\UnicodeCategory.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Guid.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\HashCode.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\HResults.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IAsyncResult.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ICloneable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IComparable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IConvertible.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ICustomFormatter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IDisposable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IEquatable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IFormatProvider.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IFormattable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IndexOutOfRangeException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\InsufficientExecutionStackException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\InvalidCastException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\InvalidOperationException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\InvalidProgramException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\InvalidTimeZoneException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\BinaryWriter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\DirectoryNotFoundException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\EncodingCache.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\EndOfStreamException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\Error.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileAccess.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileLoadException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileMode.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileNotFoundException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileOptions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileShare.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\IOException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\MemoryStream.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathTooLongException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\PinnedBufferMemoryStream.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\SeekOrigin.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\StreamHelpers.CopyValidation.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\StreamReader.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\StreamWriter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\TextReader.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\TextWriter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryAccessor.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryStream.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryStreamWrapper.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IObservable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IObserver.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IProgress.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ISpanFormattable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Int16.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Int32.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Int64.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IntPtr.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Lazy.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Marvin.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Math.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MathF.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MarshalByRefObject.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MemberAccessException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Memory.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MemoryDebugView.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.Fast.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MethodAccessException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MidpointRounding.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MissingMethodException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\MulticastNotSupportedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\NotFiniteNumberException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\NotImplementedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\NonSerializedAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\NotSupportedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Nullable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Number.Formatting.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Number.NumberBuffer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Number.Parsing.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\NullReferenceException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ObjectDisposedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ObsoleteAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\OperationCanceledException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\OverflowException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ParamArrayAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ParamsArray.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ParseNumbers.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\PlatformNotSupportedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Progress.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Random.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\RankException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ReadOnlySpan.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ReadOnlySpan.Fast.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ReadOnlyMemory.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AmbiguousMatchException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Assembly.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyAlgorithmIdAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyCompanyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyConfigurationAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyContentType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyCopyrightAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyCultureAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyDefaultAliasAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyDelaySignAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyDescriptionAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyFileVersionAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyFlagsAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyInformationalVersionAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyKeyFileAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyKeyNameAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyMetadataAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyNameFlags.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyNameFormatter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyProductAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblySignatureKeyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyTitleAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyTrademarkAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\AssemblyVersionAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Binder.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\BindingFlags.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\CallingConventions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ConstructorInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\CustomAttributeFormatException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\DefaultMemberAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\EventAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\EventInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ExceptionHandlingClauseOptions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\FieldAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\FieldInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\GenericParameterAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ICustomAttributeProvider.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ImageFileMachine.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\InterfaceMapping.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\IntrospectionExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\InvalidFilterCriteriaException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\IReflect.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\IReflectableType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ManifestResourceInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\MemberFilter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\MemberInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\MemberTypes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\MethodAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\MethodBase.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\MethodImplAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\MethodInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\MethodInfo.Internal.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Missing.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Module.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ModuleResolveEventHandler.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ObfuscateAssemblyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ObfuscationAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ParameterAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ParameterInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ParameterModifier.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Pointer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\PortableExecutableKinds.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ProcessorArchitecture.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\PropertyAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\PropertyInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ReflectionContext.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ReflectionTypeLoadException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ResourceAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\ResourceLocation.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignatureArrayType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignatureByRefType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignatureConstructedGenericType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignatureGenericMethodParameterType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignatureGenericParameterType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignatureHasElementType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignaturePointerType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignatureType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\SignatureTypeExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\StrongNameKeyPair.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TargetException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TargetInvocationException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TargetParameterCountException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TypeAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TypeDelegator.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TypeFilter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TypeInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ResolveEventArgs.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ResolveEventHandler.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\IResourceReader.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\MissingManifestResourceException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\MissingSatelliteAssemblyException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\NeutralResourcesLanguageAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\ResourceFallbackManager.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\ResourceTypeCode.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\RuntimeResourceSet.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\SatelliteContractVersionAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\UltimateResourceFallbackLocation.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AccessedThroughPropertyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncMethodBuilderAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncStateMachineAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncValueTaskMethodBuilder.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\CallerFilePathAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\CallerLineNumberAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\CallerMemberNameAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\CompilationRelaxations.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\CompilationRelaxationsAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\CompilerGeneratedAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\CompilerGlobalScopeAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ConfiguredValueTaskAwaitable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\CustomConstantAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\DateTimeConstantAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\DecimalConstantAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\DefaultDependencyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\DependencyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\DisablePrivateReflectionAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\DiscardableAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ExtensionAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\FixedAddressValueTypeAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\FixedBufferAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\FormattableStringFactory.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IAsyncStateMachine.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IndexerNameAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\INotifyCompletion.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\InternalsVisibleToAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IntrinsicAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IsConst.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IsByRefLikeAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IsVolatile.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IteratorStateMachineAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ITuple.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\LoadHint.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MethodCodeType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MethodImplAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MethodImplOptions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\IsReadOnlyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ReferenceAssemblyAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\RuntimeCompatibilityAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\RuntimeFeature.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\RuntimeWrappedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\SpecialNameAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\StateMachineAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\StringFreezingAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\StrongBox.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\SuppressIldasmAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\TupleElementNamesAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\TypeForwardedFromAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\TypeForwardedToAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\UnsafeValueTypeAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ValueTaskAwaiter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\YieldAwaitable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ConstrainedExecution\Cer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ConstrainedExecution\Consistency.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ConstrainedExecution\CriticalFinalizerObject.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ConstrainedExecution\ReliabilityContractAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ExceptionServices\ExceptionNotification.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ExceptionServices\HandleProcessCorruptedStateExceptionsAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\BestFitMappingAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CallingConvention.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CharSet.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ComVisibleAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\DefaultCharSetAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\DefaultDllImportSearchPathsAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\DllImportAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\DllImportSearchPath.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ExternalException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\FieldOffsetAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\GuidAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\HandleRef.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\InAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\LayoutKind.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MarshalAsAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MarshalDirectiveException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MemoryMarshal.Fast.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MemoryMarshal.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\OptionalAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\OutAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\PreserveSigAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\SafeBuffer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\StructLayoutAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedFunctionPointerAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\VarEnum.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\IDeserializationCallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\IFormatterConverter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\IObjectReference.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\ISafeSerializationData.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\ISerializable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\OnDeserializedAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\OnDeserializingAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\OnSerializedAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\OnSerializingAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\OptionalFieldAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\SafeSerializationEventArgs.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\SerializationException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\SerializationInfoEnumerator.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Serialization\StreamingContext.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Versioning\NonVersionableAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Versioning\TargetFrameworkAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SByte.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\AllowPartiallyTrustedCallersAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\CryptographicException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\PartialTrustVisibilityLevel.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecurityCriticalAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecurityCriticalScope.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecurityException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecurityRulesAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecurityRuleSet.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecuritySafeCriticalAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecurityTransparentAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecurityTreatAsSafeAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SuppressUnmanagedCodeSecurityAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\UnverifiableCodeAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\VerificationException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SerializableAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Single.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Span.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Span.Fast.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanDebugView.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.BinarySearch.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.Byte.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.Char.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.T.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\String.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\String.Comparison.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\String.Manipulation.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\String.Searching.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\StackOverflowException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\StringComparer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\StringComparison.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\StringSplitOptions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\SystemException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\ASCIIEncoding.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\Decoder.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\DecoderNLS.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\DecoderBestFitFallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\DecoderExceptionFallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\DecoderFallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\DecoderReplacementFallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\Encoder.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\EncoderNLS.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\EncoderBestFitFallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\EncoderExceptionFallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\EncoderFallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\EncoderReplacementFallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\Encoding.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\EncodingInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\EncodingNLS.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\EncodingProvider.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\Latin1Encoding.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\NormalizationForm.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\StringBuilder.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\StringBuilder.Debug.cs" Condition="'$(Configuration)' == 'Debug'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\UnicodeEncoding.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\UTF32Encoding.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\UTF7Encoding.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\UTF8Encoding.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Text\ValueStringBuilder.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeSpan.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ThreadAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\AbandonedMutexException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ApartmentState.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\AsyncLocal.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\AutoResetEvent.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\DeferredDisposableLifetime.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\EventResetMode.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ExecutionContext.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\LazyInitializer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\LazyThreadSafetyMode.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\LockRecursionException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ManualResetEvent.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ParameterizedThreadStart.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ReaderWriterLockSlim.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\SemaphoreFullException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\SendOrPostCallback.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\SpinWait.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\SynchronizationLockException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\ConcurrentExclusiveSchedulerPair.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskCanceledException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskCompletionSource.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskToApm.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskSchedulerException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\ValueTask.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\Sources\IValueTaskSource.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadAbortException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPriority.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadStart.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadStartException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadState.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadStateException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Timeout.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\TimeoutHelper.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitHandleCannotBeOpenedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\ThreadStaticAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeoutException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeZone.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.AdjustmentRule.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.StringSerializer.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.TransitionTime.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneNotFoundException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Tuple.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TupleExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Type.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Type.Enum.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Type.Helpers.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TypeAccessException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TypeCode.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TypeInitializationException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TypeUnloadedException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\UInt16.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\UInt32.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\UInt64.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\UIntPtr.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\UnauthorizedAccessException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\UnhandledExceptionEventArgs.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\UnhandledExceptionEventHandler.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\UnitySerializationHolder.cs"/>
+ <Compile Include="$(MSBuildThisFileDirectory)System\ValueTuple.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Version.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Void.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\ActivityTracker.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventActivityOptions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventDescriptor.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventProvider.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventSource.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventSourceException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\IEventProvider.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\StubEnvironment.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\Winmeta.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\ArrayTypeInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\ConcurrentSet.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\ConcurrentSetItem.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\DataCollector.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EmptyStruct.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EnumerableTypeInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EnumHelper.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EventDataAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EventFieldAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EventFieldFormat.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EventIgnoreAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EventPayload.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EventSourceActivity.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\EventSourceOptions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\FieldMetadata.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\InvokeTypeInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\NameInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\PropertyAnalysis.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\PropertyValue.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\SimpleEventTypes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\SimpleTypeInfos.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\Statics.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\TraceLoggingDataCollector.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\TraceLoggingDataType.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\TraceLoggingEventSource.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\TraceLoggingEventTraits.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\TraceLoggingEventTypes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\TraceLoggingMetadataCollector.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\TraceLoggingTypeInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\TypeAnalysis.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildThisFileDirectory)System\Numerics\ConstantHelper.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>ConstantHelper.tt</DependentUpon>
+ </Compile>
+ <Content Include="$(MSBuildThisFileDirectory)System\Numerics\ConstantHelper.tt">
+ <Generator>TextTemplatingFileGenerator</Generator>
+ <LastGenOutput>ConstantHelper.cs</LastGenOutput>
+ </Content>
+ <None Include="$(MSBuildThisFileDirectory)System\Numerics\GenerationConfig.ttinclude" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Numerics\Register.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Register.tt</DependentUpon>
+ </Compile>
+ <Content Include="$(MSBuildThisFileDirectory)System\Numerics\Register.tt">
+ <Generator>TextTemplatingFileGenerator</Generator>
+ <LastGenOutput>Register.cs</LastGenOutput>
+ </Content>
+ <Compile Include="$(MSBuildThisFileDirectory)System\Numerics\Vector.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Vector.tt</DependentUpon>
+ </Compile>
+ <Content Include="$(MSBuildThisFileDirectory)System\Numerics\Vector.tt">
+ <Generator>TextTemplatingFileGenerator</Generator>
+ <LastGenOutput>Vector.cs</LastGenOutput>
+ </Content>
+ <Compile Include="$(MSBuildThisFileDirectory)System\Numerics\Vector_Operations.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="$(TargetsWindows)">
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Crypt32\Interop.CryptProtectMemory.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Interop.BOOL.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Interop.Libraries.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CancelIoEx.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CloseHandle.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FileAttributes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FILE_INFO_BY_HANDLE_CLASS.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FileTypes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FindClose.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FindFirstFileEx.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FlushFileBuffers.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FreeEnvironmentStrings.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetCPInfo.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetEnvironmentStrings.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFileAttributesEx.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFileType_SafeHandle.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFullPathNameW.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetLongPathNameW.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetTempFileNameW.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetTempPathW.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.Globalization.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.LockFile.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.MAX_PATH.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.MultiByteToWideChar.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.OutputDebugString.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.ReadFile_SafeHandle_IntPtr.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.ReadFile_SafeHandle_NativeOverlapped.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SecurityOptions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetEndOfFile.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetEnvironmentVariable.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetThreadErrorMode.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetFilePointerEx.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WideCharToMultiByte.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_IntPtr.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_NativeOverlapped.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Normaliz\Interop.Idna.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Normaliz\Interop.Normalization.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.TimeZone.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Ole32\Interop.CoCreateGuid.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\OleAut32\Interop.SysAllocStringLen.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\OleAut32\Interop.SysFreeString.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\OleAut32\Interop.SysStringLen.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Internal\IO\File.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFileHandle.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFindHandle.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarData.Windows.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareInfo.Windows.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Windows.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Win32.cs" Condition="'$(EnableWinRT)' != 'true' and '$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.WinRT.cs" Condition="'$(EnableWinRT)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\IdnMapping.Windows.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.Win32.cs" Condition="'$(EnableWinRT)' != 'true' and '$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.WinRT.cs" Condition="'$(EnableWinRT)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\Normalization.Windows.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.Windows.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Guid.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStreamCompletionSource.Win32.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathHelper.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\DisableMediaInsertionPrompt.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SafeBSTRHandle.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Windows.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="$(TargetsWindows) and '$(EnableWinRT)' != 'true'">
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Win32.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Win32.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeLibraryHandle.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.LoadLibraryEx.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FreeLibrary.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CreateFile.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.MUI.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.TimeZone.Registry.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\User32\Interop.Constants.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\User32\Interop.SendMessageTimeout.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\User32\Interop.LoadString.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="$(TargetsWindows) and '$(EnableWinRT)' == 'true'">
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.WinRT.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CreateFile2.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CREATEFILE2_EXTENDED_PARAMETERS.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="$(TargetsWindows) or '$(FeaturePal)'=='true'">
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Interop.Errors.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FormatMessage.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\Win32Marshal.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="$(TargetsUnix)">
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\Interop.Errors.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\Interop.IOErrors.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\Interop.Libraries.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.Calendar.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.Casing.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.Collation.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.ICU.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.Idna.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.Locale.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.Normalization.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.ResultCode.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.TimeZoneInfo.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Globalization.Native\Interop.Utils.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.Close.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.FLock.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.FSync.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.FTruncate.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetCwd.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetRandomBytes.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.LockFileRegion.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.LSeek.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.MksTemps.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.Open.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.OpenFlags.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.PathConf.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.Permissions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.PosixFAdvise.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.Read.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.ReadDir.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.ReadLink.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.Stat.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.SysLog.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.Unlink.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.Write.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Internal\IO\File.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeDirectoryHandle.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Debug.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarData.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareInfo.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\IdnMapping.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\LocaleData.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\Normalization.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.Unix.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Guid.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.OSX.cs" Condition="'$(TargetsOSX)' == 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Linux.cs" Condition="'$(TargetsOSX)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Unix.cs" />
+ </ItemGroup>
+</Project>
diff --git a/src/System.Private.CoreLib/shared/System/AccessViolationException.cs b/src/System.Private.CoreLib/shared/System/AccessViolationException.cs
new file mode 100644
index 0000000000..280d9b8a97
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/AccessViolationException.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class representing an AV that was deemed unsafe and may have corrupted the application.
+**
+**
+=============================================================================*/
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class AccessViolationException : SystemException
+ {
+ public AccessViolationException()
+ : base(SR.Arg_AccessViolationException)
+ {
+ HResult = HResults.E_POINTER;
+ }
+
+ public AccessViolationException(String message)
+ : base(message)
+ {
+ HResult = HResults.E_POINTER;
+ }
+
+ public AccessViolationException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.E_POINTER;
+ }
+
+ protected AccessViolationException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+#pragma warning disable 169 // Field is not used from managed.
+ private IntPtr _ip; // Address of faulting instruction.
+ private IntPtr _target; // Address that could not be accessed.
+ private int _accessType; // 0:read, 1:write
+#pragma warning restore 169
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Action.cs b/src/System.Private.CoreLib/shared/System/Action.cs
new file mode 100644
index 0000000000..6e3ccff48c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Action.cs
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public delegate void Action();
+ public delegate void Action<in T>(T obj);
+ public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
+ public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
+ public delegate void Action<in T1, in T2, in T3, in T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
+ public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
+
+ public delegate TResult Func<out TResult>();
+ public delegate TResult Func<in T, out TResult>(T arg);
+ public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
+ public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3);
+ public delegate TResult Func<in T1, in T2, in T3, in T4, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
+ public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
+
+ public delegate int Comparison<in T>(T x, T y);
+
+ public delegate TOutput Converter<in TInput, out TOutput>(TInput input);
+
+ public delegate bool Predicate<in T>(T obj);
+}
+
+namespace System.Buffers
+{
+ public delegate void SpanAction<T, in TArg>(Span<T> span, TArg arg);
+ public delegate void ReadOnlySpanAction<T, in TArg>(ReadOnlySpan<T> span, TArg arg);
+}
diff --git a/src/System.Private.CoreLib/shared/System/AggregateException.cs b/src/System.Private.CoreLib/shared/System/AggregateException.cs
new file mode 100644
index 0000000000..99ba703a52
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/AggregateException.cs
@@ -0,0 +1,484 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.ExceptionServices;
+using System.Runtime.Serialization;
+using System.Security;
+using System.Text;
+using System.Threading;
+
+namespace System
+{
+ /// <summary>Represents one or more errors that occur during application execution.</summary>
+ /// <remarks>
+ /// <see cref="AggregateException"/> is used to consolidate multiple failures into a single, throwable
+ /// exception object.
+ /// </remarks>
+ [Serializable]
+ [DebuggerDisplay("Count = {InnerExceptionCount}")]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class AggregateException : Exception
+ {
+ private ReadOnlyCollection<Exception> m_innerExceptions; // Complete set of exceptions. Do not rename (binary serialization)
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class.
+ /// </summary>
+ public AggregateException()
+ : base(SR.AggregateException_ctor_DefaultMessage)
+ {
+ m_innerExceptions = new ReadOnlyCollection<Exception>(Array.Empty<Exception>());
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with
+ /// a specified error message.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ public AggregateException(string message)
+ : base(message)
+ {
+ m_innerExceptions = new ReadOnlyCollection<Exception>(Array.Empty<Exception>());
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
+ /// message and a reference to the inner exception that is the cause of this exception.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerException">The exception that is the cause of the current exception.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerException"/> argument
+ /// is null.</exception>
+ public AggregateException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ if (innerException == null)
+ {
+ throw new ArgumentNullException(nameof(innerException));
+ }
+
+ m_innerExceptions = new ReadOnlyCollection<Exception>(new Exception[] { innerException });
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with
+ /// references to the inner exceptions that are the cause of this exception.
+ /// </summary>
+ /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
+ /// is null.</exception>
+ /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
+ /// null.</exception>
+ public AggregateException(IEnumerable<Exception> innerExceptions) :
+ this(SR.AggregateException_ctor_DefaultMessage, innerExceptions)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with
+ /// references to the inner exceptions that are the cause of this exception.
+ /// </summary>
+ /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
+ /// is null.</exception>
+ /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
+ /// null.</exception>
+ public AggregateException(params Exception[] innerExceptions) :
+ this(SR.AggregateException_ctor_DefaultMessage, innerExceptions)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
+ /// message and references to the inner exceptions that are the cause of this exception.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
+ /// is null.</exception>
+ /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
+ /// null.</exception>
+ public AggregateException(string message, IEnumerable<Exception> innerExceptions)
+ // If it's already an IList, pass that along (a defensive copy will be made in the delegated ctor). If it's null, just pass along
+ // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
+ : this(message, innerExceptions as IList<Exception> ?? (innerExceptions == null ? (List<Exception>)null : new List<Exception>(innerExceptions)))
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
+ /// message and references to the inner exceptions that are the cause of this exception.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
+ /// is null.</exception>
+ /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
+ /// null.</exception>
+ public AggregateException(string message, params Exception[] innerExceptions) :
+ this(message, (IList<Exception>)innerExceptions)
+ {
+ }
+
+ /// <summary>
+ /// Allocates a new aggregate exception with the specified message and list of inner exceptions.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
+ /// is null.</exception>
+ /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
+ /// null.</exception>
+ private AggregateException(string message, IList<Exception> innerExceptions)
+ : base(message, innerExceptions != null && innerExceptions.Count > 0 ? innerExceptions[0] : null)
+ {
+ if (innerExceptions == null)
+ {
+ throw new ArgumentNullException(nameof(innerExceptions));
+ }
+
+ // Copy exceptions to our internal array and validate them. We must copy them,
+ // because we're going to put them into a ReadOnlyCollection which simply reuses
+ // the list passed in to it. We don't want callers subsequently mutating.
+ Exception[] exceptionsCopy = new Exception[innerExceptions.Count];
+
+ for (int i = 0; i < exceptionsCopy.Length; i++)
+ {
+ exceptionsCopy[i] = innerExceptions[i];
+
+ if (exceptionsCopy[i] == null)
+ {
+ throw new ArgumentException(SR.AggregateException_ctor_InnerExceptionNull);
+ }
+ }
+
+ m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with
+ /// references to the inner exception dispatch info objects that represent the cause of this exception.
+ /// </summary>
+ /// <param name="innerExceptionInfos">
+ /// Information about the exceptions that are the cause of the current exception.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
+ /// is null.</exception>
+ /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
+ /// null.</exception>
+ internal AggregateException(IEnumerable<ExceptionDispatchInfo> innerExceptionInfos) :
+ this(SR.AggregateException_ctor_DefaultMessage, innerExceptionInfos)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
+ /// message and references to the inner exception dispatch info objects that represent the cause of
+ /// this exception.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerExceptionInfos">
+ /// Information about the exceptions that are the cause of the current exception.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
+ /// is null.</exception>
+ /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
+ /// null.</exception>
+ internal AggregateException(string message, IEnumerable<ExceptionDispatchInfo> innerExceptionInfos)
+ // If it's already an IList, pass that along (a defensive copy will be made in the delegated ctor). If it's null, just pass along
+ // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
+ : this(message, innerExceptionInfos as IList<ExceptionDispatchInfo> ??
+ (innerExceptionInfos == null ?
+ (List<ExceptionDispatchInfo>)null :
+ new List<ExceptionDispatchInfo>(innerExceptionInfos)))
+ {
+ }
+
+ /// <summary>
+ /// Allocates a new aggregate exception with the specified message and list of inner
+ /// exception dispatch info objects.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerExceptionInfos">
+ /// Information about the exceptions that are the cause of the current exception.
+ /// </param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
+ /// is null.</exception>
+ /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
+ /// null.</exception>
+ private AggregateException(string message, IList<ExceptionDispatchInfo> innerExceptionInfos)
+ : base(message, innerExceptionInfos != null && innerExceptionInfos.Count > 0 && innerExceptionInfos[0] != null ?
+ innerExceptionInfos[0].SourceException : null)
+ {
+ if (innerExceptionInfos == null)
+ {
+ throw new ArgumentNullException(nameof(innerExceptionInfos));
+ }
+
+ // Copy exceptions to our internal array and validate them. We must copy them,
+ // because we're going to put them into a ReadOnlyCollection which simply reuses
+ // the list passed in to it. We don't want callers subsequently mutating.
+ Exception[] exceptionsCopy = new Exception[innerExceptionInfos.Count];
+
+ for (int i = 0; i < exceptionsCopy.Length; i++)
+ {
+ var edi = innerExceptionInfos[i];
+ if (edi != null) exceptionsCopy[i] = edi.SourceException;
+
+ if (exceptionsCopy[i] == null)
+ {
+ throw new ArgumentException(SR.AggregateException_ctor_InnerExceptionNull);
+ }
+ }
+
+ m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregateException"/> class with serialized data.
+ /// </summary>
+ /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds
+ /// the serialized object data about the exception being thrown.</param>
+ /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that
+ /// contains contextual information about the source or destination. </param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
+ /// <exception cref="T:System.Runtime.Serialization.SerializationException">The exception could not be deserialized correctly.</exception>
+ protected AggregateException(SerializationInfo info, StreamingContext context) :
+ base(info, context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ Exception[] innerExceptions = info.GetValue("InnerExceptions", typeof(Exception[])) as Exception[];
+ if (innerExceptions == null)
+ {
+ throw new SerializationException(SR.AggregateException_DeserializationFailure);
+ }
+
+ m_innerExceptions = new ReadOnlyCollection<Exception>(innerExceptions);
+ }
+
+ /// <summary>
+ /// Sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about
+ /// the exception.
+ /// </summary>
+ /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds
+ /// the serialized object data about the exception being thrown.</param>
+ /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that
+ /// contains contextual information about the source or destination. </param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+
+ Exception[] innerExceptions = new Exception[m_innerExceptions.Count];
+ m_innerExceptions.CopyTo(innerExceptions, 0);
+ info.AddValue("InnerExceptions", innerExceptions, typeof(Exception[]));
+ }
+
+ /// <summary>
+ /// Returns the <see cref="System.AggregateException"/> that is the root cause of this exception.
+ /// </summary>
+ public override Exception GetBaseException()
+ {
+ // Returns the first inner AggregateException that contains more or less than one inner exception
+
+ // Recursively traverse the inner exceptions as long as the inner exception of type AggregateException and has only one inner exception
+ Exception back = this;
+ AggregateException backAsAggregate = this;
+ while (backAsAggregate != null && backAsAggregate.InnerExceptions.Count == 1)
+ {
+ back = back.InnerException;
+ backAsAggregate = back as AggregateException;
+ }
+ return back;
+ }
+
+ /// <summary>
+ /// Gets a read-only collection of the <see cref="T:System.Exception"/> instances that caused the
+ /// current exception.
+ /// </summary>
+ public ReadOnlyCollection<Exception> InnerExceptions
+ {
+ get { return m_innerExceptions; }
+ }
+
+
+ /// <summary>
+ /// Invokes a handler on each <see cref="T:System.Exception"/> contained by this <see
+ /// cref="AggregateException"/>.
+ /// </summary>
+ /// <param name="predicate">The predicate to execute for each exception. The predicate accepts as an
+ /// argument the <see cref="T:System.Exception"/> to be processed and returns a Boolean to indicate
+ /// whether the exception was handled.</param>
+ /// <remarks>
+ /// Each invocation of the <paramref name="predicate"/> returns true or false to indicate whether the
+ /// <see cref="T:System.Exception"/> was handled. After all invocations, if any exceptions went
+ /// unhandled, all unhandled exceptions will be put into a new <see cref="AggregateException"/>
+ /// which will be thrown. Otherwise, the <see cref="Handle"/> method simply returns. If any
+ /// invocations of the <paramref name="predicate"/> throws an exception, it will halt the processing
+ /// of any more exceptions and immediately propagate the thrown exception as-is.
+ /// </remarks>
+ /// <exception cref="AggregateException">An exception contained by this <see
+ /// cref="AggregateException"/> was not handled.</exception>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="predicate"/> argument is
+ /// null.</exception>
+ public void Handle(Func<Exception, bool> predicate)
+ {
+ if (predicate == null)
+ {
+ throw new ArgumentNullException(nameof(predicate));
+ }
+
+ List<Exception> unhandledExceptions = null;
+ for (int i = 0; i < m_innerExceptions.Count; i++)
+ {
+ // If the exception was not handled, lazily allocate a list of unhandled
+ // exceptions (to be rethrown later) and add it.
+ if (!predicate(m_innerExceptions[i]))
+ {
+ if (unhandledExceptions == null)
+ {
+ unhandledExceptions = new List<Exception>();
+ }
+
+ unhandledExceptions.Add(m_innerExceptions[i]);
+ }
+ }
+
+ // If there are unhandled exceptions remaining, throw them.
+ if (unhandledExceptions != null)
+ {
+ throw new AggregateException(Message, unhandledExceptions);
+ }
+ }
+
+
+ /// <summary>
+ /// Flattens the inner instances of <see cref="AggregateException"/> by expanding its contained <see cref="Exception"/> instances
+ /// into a new <see cref="AggregateException"/>
+ /// </summary>
+ /// <returns>A new, flattened <see cref="AggregateException"/>.</returns>
+ /// <remarks>
+ /// If any inner exceptions are themselves instances of
+ /// <see cref="AggregateException"/>, this method will recursively flatten all of them. The
+ /// inner exceptions returned in the new <see cref="AggregateException"/>
+ /// will be the union of all of the inner exceptions from exception tree rooted at the provided
+ /// <see cref="AggregateException"/> instance.
+ /// </remarks>
+ public AggregateException Flatten()
+ {
+ // Initialize a collection to contain the flattened exceptions.
+ List<Exception> flattenedExceptions = new List<Exception>();
+
+ // Create a list to remember all aggregates to be flattened, this will be accessed like a FIFO queue
+ List<AggregateException> exceptionsToFlatten = new List<AggregateException>();
+ exceptionsToFlatten.Add(this);
+ int nDequeueIndex = 0;
+
+ // Continue removing and recursively flattening exceptions, until there are no more.
+ while (exceptionsToFlatten.Count > nDequeueIndex)
+ {
+ // dequeue one from exceptionsToFlatten
+ IList<Exception> currentInnerExceptions = exceptionsToFlatten[nDequeueIndex++].InnerExceptions;
+
+ for (int i = 0; i < currentInnerExceptions.Count; i++)
+ {
+ Exception currentInnerException = currentInnerExceptions[i];
+
+ if (currentInnerException == null)
+ {
+ continue;
+ }
+
+ AggregateException currentInnerAsAggregate = currentInnerException as AggregateException;
+
+ // If this exception is an aggregate, keep it around for later. Otherwise,
+ // simply add it to the list of flattened exceptions to be returned.
+ if (currentInnerAsAggregate != null)
+ {
+ exceptionsToFlatten.Add(currentInnerAsAggregate);
+ }
+ else
+ {
+ flattenedExceptions.Add(currentInnerException);
+ }
+ }
+ }
+
+
+ return new AggregateException(Message, flattenedExceptions);
+ }
+
+ /// <summary>Gets a message that describes the exception.</summary>
+ public override string Message
+ {
+ get
+ {
+ if (m_innerExceptions.Count == 0)
+ {
+ return base.Message;
+ }
+
+ StringBuilder sb = StringBuilderCache.Acquire();
+ sb.Append(base.Message);
+ sb.Append(' ');
+ for (int i = 0; i < m_innerExceptions.Count; i++)
+ {
+ sb.Append('(');
+ sb.Append(m_innerExceptions[i].Message);
+ sb.Append(") ");
+ }
+ sb.Length -= 1;
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+ }
+
+ /// <summary>
+ /// Creates and returns a string representation of the current <see cref="AggregateException"/>.
+ /// </summary>
+ /// <returns>A string representation of the current exception.</returns>
+ public override string ToString()
+ {
+ StringBuilder text = new StringBuilder();
+ text.Append(base.ToString());
+
+ for (int i = 0; i < m_innerExceptions.Count; i++)
+ {
+ text.AppendLine();
+ text.Append("---> ");
+ text.AppendFormat(CultureInfo.InvariantCulture, SR.AggregateException_InnerException, i);
+ text.Append(m_innerExceptions[i].ToString());
+ text.Append("<---");
+ text.AppendLine();
+ }
+
+ return text.ToString();
+ }
+
+ /// <summary>
+ /// This helper property is used by the DebuggerDisplay.
+ ///
+ /// Note that we don't want to remove this property and change the debugger display to {InnerExceptions.Count}
+ /// because DebuggerDisplay should be a single property access or parameterless method call, so that the debugger
+ /// can use a fast path without using the expression evaluator.
+ ///
+ /// See http://msdn.microsoft.com/en-us/library/x810d419.aspx
+ /// </summary>
+ private int InnerExceptionCount
+ {
+ get
+ {
+ return InnerExceptions.Count;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ApplicationException.cs b/src/System.Private.CoreLib/shared/System/ApplicationException.cs
new file mode 100644
index 0000000000..f36e2c1274
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ApplicationException.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: The base class for all "less serious" exceptions that must be
+** declared or caught.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The ApplicationException is the base class for nonfatal,
+ // application errors that occur. These exceptions are generated
+ // (i.e., thrown) by an application, not the Runtime. Applications that need
+ // to create their own exceptions do so by extending this class.
+ // ApplicationException extends but adds no new functionality to
+ // RecoverableException.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ApplicationException : Exception
+ {
+ // Creates a new ApplicationException with its message string set to
+ // the empty string, its HRESULT set to COR_E_APPLICATION,
+ // and its ExceptionInfo reference set to null.
+ public ApplicationException()
+ : base(SR.Arg_ApplicationException)
+ {
+ HResult = HResults.COR_E_APPLICATION;
+ }
+
+ // Creates a new ApplicationException with its message string set to
+ // message, its HRESULT set to COR_E_APPLICATION,
+ // and its ExceptionInfo reference set to null.
+ //
+ public ApplicationException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_APPLICATION;
+ }
+
+ public ApplicationException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_APPLICATION;
+ }
+
+ protected ApplicationException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ArgumentException.cs b/src/System.Private.CoreLib/shared/System/ArgumentException.cs
new file mode 100644
index 0000000000..8a8fe3e5e4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ArgumentException.cs
@@ -0,0 +1,97 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for invalid arguments to a method.
+**
+**
+=============================================================================*/
+
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The ArgumentException is thrown when an argument does not meet
+ // the contract of the method. Ideally it should give a meaningful error
+ // message describing what was wrong and which parameter is incorrect.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ArgumentException : SystemException
+ {
+ private String _paramName;
+
+ // Creates a new ArgumentException with its message
+ // string set to the empty string.
+ public ArgumentException()
+ : base(SR.Arg_ArgumentException)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ // Creates a new ArgumentException with its message
+ // string set to message.
+ //
+ public ArgumentException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ public ArgumentException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ public ArgumentException(String message, String paramName, Exception innerException)
+ : base(message, innerException)
+ {
+ _paramName = paramName;
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ public ArgumentException(String message, String paramName)
+ : base(message)
+ {
+ _paramName = paramName;
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ protected ArgumentException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ _paramName = info.GetString("ParamName");
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("ParamName", _paramName, typeof(string));
+ }
+
+ public override String Message
+ {
+ get
+ {
+ String s = base.Message;
+ if (!String.IsNullOrEmpty(_paramName))
+ {
+ String resourceString = SR.Format(SR.Arg_ParamName_Name, _paramName);
+ return s + Environment.NewLine + resourceString;
+ }
+ else
+ return s;
+ }
+ }
+
+ public virtual String ParamName
+ {
+ get { return _paramName; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ArgumentNullException.cs b/src/System.Private.CoreLib/shared/System/ArgumentNullException.cs
new file mode 100644
index 0000000000..80e43cc265
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ArgumentNullException.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for null arguments to a method.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The ArgumentException is thrown when an argument
+ // is null when it shouldn't be.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ArgumentNullException : ArgumentException
+ {
+ // Creates a new ArgumentNullException with its message
+ // string set to a default message explaining an argument was null.
+ public ArgumentNullException()
+ : base(SR.ArgumentNull_Generic)
+ {
+ // Use E_POINTER - COM used that for null pointers. Description is "invalid pointer"
+ HResult = HResults.E_POINTER;
+ }
+
+ public ArgumentNullException(String paramName)
+ : base(SR.ArgumentNull_Generic, paramName)
+ {
+ HResult = HResults.E_POINTER;
+ }
+
+ public ArgumentNullException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.E_POINTER;
+ }
+
+ public ArgumentNullException(String paramName, String message)
+ : base(message, paramName)
+ {
+ HResult = HResults.E_POINTER;
+ }
+
+ protected ArgumentNullException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ArgumentOutOfRangeException.cs b/src/System.Private.CoreLib/shared/System/ArgumentOutOfRangeException.cs
new file mode 100644
index 0000000000..604caa8ee8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ArgumentOutOfRangeException.cs
@@ -0,0 +1,100 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for method arguments outside of the legal range.
+**
+**
+=============================================================================*/
+
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The ArgumentOutOfRangeException is thrown when an argument
+ // is outside the legal range for that argument.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ArgumentOutOfRangeException : ArgumentException
+ {
+ private Object _actualValue;
+
+ // Creates a new ArgumentOutOfRangeException with its message
+ // string set to a default message explaining an argument was out of range.
+ public ArgumentOutOfRangeException()
+ : base(SR.Arg_ArgumentOutOfRangeException)
+ {
+ HResult = HResults.COR_E_ARGUMENTOUTOFRANGE;
+ }
+
+ public ArgumentOutOfRangeException(String paramName)
+ : base(SR.Arg_ArgumentOutOfRangeException, paramName)
+ {
+ HResult = HResults.COR_E_ARGUMENTOUTOFRANGE;
+ }
+
+ public ArgumentOutOfRangeException(String paramName, String message)
+ : base(message, paramName)
+ {
+ HResult = HResults.COR_E_ARGUMENTOUTOFRANGE;
+ }
+
+ public ArgumentOutOfRangeException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_ARGUMENTOUTOFRANGE;
+ }
+
+ // We will not use this in the classlibs, but we'll provide it for
+ // anyone that's really interested so they don't have to stick a bunch
+ // of printf's in their code.
+ public ArgumentOutOfRangeException(String paramName, Object actualValue, String message)
+ : base(message, paramName)
+ {
+ _actualValue = actualValue;
+ HResult = HResults.COR_E_ARGUMENTOUTOFRANGE;
+ }
+
+ protected ArgumentOutOfRangeException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ _actualValue = info.GetValue("ActualValue", typeof(object));
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("ActualValue", _actualValue, typeof(object));
+ }
+
+ public override String Message
+ {
+ get
+ {
+ String s = base.Message;
+ if (_actualValue != null)
+ {
+ String valueMessage = SR.Format(SR.ArgumentOutOfRange_ActualValue, _actualValue.ToString());
+ if (s == null)
+ return valueMessage;
+ return s + Environment.NewLine + valueMessage;
+ }
+ return s;
+ }
+ }
+
+ // Gets the value of the argument that caused the exception.
+ // Note - we don't set this anywhere in the class libraries in
+ // version 1, but it might come in handy for other developers who
+ // want to avoid sticking printf's in their code.
+ public virtual Object ActualValue
+ {
+ get { return _actualValue; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ArithmeticException.cs b/src/System.Private.CoreLib/shared/System/ArithmeticException.cs
new file mode 100644
index 0000000000..606f1debfd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ArithmeticException.cs
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for bad arithmetic conditions!
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The ArithmeticException is thrown when overflow or underflow
+ // occurs.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ArithmeticException : SystemException
+ {
+ // Creates a new ArithmeticException with its message string set to
+ // the empty string, its HRESULT set to COR_E_ARITHMETIC,
+ // and its ExceptionInfo reference set to null.
+ public ArithmeticException()
+ : base(SR.Arg_ArithmeticException)
+ {
+ HResult = HResults.COR_E_ARITHMETIC;
+ }
+
+ // Creates a new ArithmeticException with its message string set to
+ // message, its HRESULT set to COR_E_ARITHMETIC,
+ // and its ExceptionInfo reference set to null.
+ //
+ public ArithmeticException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ARITHMETIC;
+ }
+
+ public ArithmeticException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_ARITHMETIC;
+ }
+
+ protected ArithmeticException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ArraySegment.cs b/src/System.Private.CoreLib/shared/System/ArraySegment.cs
new file mode 100644
index 0000000000..3a13595e83
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ArraySegment.cs
@@ -0,0 +1,368 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Convenient wrapper for an array, an offset, and
+** a count. Ideally used in streams & collections.
+** Net Classes will consume an array of these.
+**
+**
+===========================================================*/
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System
+{
+ // Note: users should make sure they copy the fields out of an ArraySegment onto their stack
+ // then validate that the fields describe valid bounds within the array. This must be done
+ // because assignments to value types are not atomic, and also because one thread reading
+ // three fields from an ArraySegment may not see the same ArraySegment from one call to another
+ // (ie, users could assign a new value to the old location).
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public readonly struct ArraySegment<T> : IList<T>, IReadOnlyList<T>
+ {
+ // Do not replace the array allocation with Array.Empty. We don't want to have the overhead of
+ // instantiating another generic type in addition to ArraySegment<T> for new type parameters.
+ public static ArraySegment<T> Empty { get; } = new ArraySegment<T>(new T[0]);
+
+ private readonly T[] _array; // Do not rename (binary serialization)
+ private readonly int _offset; // Do not rename (binary serialization)
+ private readonly int _count; // Do not rename (binary serialization)
+
+ public ArraySegment(T[] array)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+ _array = array;
+ _offset = 0;
+ _count = array.Length;
+ }
+
+ public ArraySegment(T[] array, int offset, int count)
+ {
+ // Validate arguments, check is minimal instructions with reduced branching for inlinable fast-path
+ // Negative values discovered though conversion to high values when converted to unsigned
+ // Failure should be rare and location determination and message is delegated to failure functions
+ if (array == null || (uint)offset > (uint)array.Length || (uint)count > (uint)(array.Length - offset))
+ ThrowHelper.ThrowArraySegmentCtorValidationFailedExceptions(array, offset, count);
+
+ _array = array;
+ _offset = offset;
+ _count = count;
+ }
+
+ public T[] Array => _array;
+
+ public int Offset => _offset;
+
+ public int Count => _count;
+
+ public T this[int index]
+ {
+ get
+ {
+ if ((uint)index >= (uint)_count)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+
+ return _array[_offset + index];
+ }
+ set
+ {
+ if ((uint)index >= (uint)_count)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+
+ _array[_offset + index] = value;
+ }
+ }
+
+ public Enumerator GetEnumerator()
+ {
+ ThrowInvalidOperationIfDefault();
+ return new Enumerator(this);
+ }
+
+ public override int GetHashCode()
+ {
+ if (_array == null)
+ {
+ return 0;
+ }
+
+ int hash = 5381;
+ hash = System.Numerics.Hashing.HashHelpers.Combine(hash, _offset);
+ hash = System.Numerics.Hashing.HashHelpers.Combine(hash, _count);
+
+ // The array hash is expected to be an evenly-distributed mixture of bits,
+ // so rather than adding the cost of another rotation we just xor it.
+ hash ^= _array.GetHashCode();
+ return hash;
+ }
+
+ public void CopyTo(T[] destination) => CopyTo(destination, 0);
+
+ public void CopyTo(T[] destination, int destinationIndex)
+ {
+ ThrowInvalidOperationIfDefault();
+ System.Array.Copy(_array, _offset, destination, destinationIndex, _count);
+ }
+
+ public void CopyTo(ArraySegment<T> destination)
+ {
+ ThrowInvalidOperationIfDefault();
+ destination.ThrowInvalidOperationIfDefault();
+
+ if (_count > destination._count)
+ {
+ ThrowHelper.ThrowArgumentException_DestinationTooShort();
+ }
+
+ System.Array.Copy(_array, _offset, destination._array, destination._offset, _count);
+ }
+
+ public override bool Equals(Object obj)
+ {
+ if (obj is ArraySegment<T>)
+ return Equals((ArraySegment<T>)obj);
+ else
+ return false;
+ }
+
+ public bool Equals(ArraySegment<T> obj)
+ {
+ return obj._array == _array && obj._offset == _offset && obj._count == _count;
+ }
+
+ public ArraySegment<T> Slice(int index)
+ {
+ ThrowInvalidOperationIfDefault();
+
+ if ((uint)index > (uint)_count)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+
+ return new ArraySegment<T>(_array, _offset + index, _count - index);
+ }
+
+ public ArraySegment<T> Slice(int index, int count)
+ {
+ ThrowInvalidOperationIfDefault();
+
+ if ((uint)index > (uint)_count || (uint)count > (uint)(_count - index))
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+
+ return new ArraySegment<T>(_array, _offset + index, count);
+ }
+
+ public T[] ToArray()
+ {
+ ThrowInvalidOperationIfDefault();
+
+ if (_count == 0)
+ {
+ return Empty._array;
+ }
+
+ var array = new T[_count];
+ System.Array.Copy(_array, _offset, array, 0, _count);
+ return array;
+ }
+
+ public static bool operator ==(ArraySegment<T> a, ArraySegment<T> b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(ArraySegment<T> a, ArraySegment<T> b)
+ {
+ return !(a == b);
+ }
+
+ public static implicit operator ArraySegment<T>(T[] array) => array != null ? new ArraySegment<T>(array) : default;
+
+ #region IList<T>
+ T IList<T>.this[int index]
+ {
+ get
+ {
+ ThrowInvalidOperationIfDefault();
+ if (index < 0 || index >= _count)
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+
+ return _array[_offset + index];
+ }
+
+ set
+ {
+ ThrowInvalidOperationIfDefault();
+ if (index < 0 || index >= _count)
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+
+ _array[_offset + index] = value;
+ }
+ }
+
+ int IList<T>.IndexOf(T item)
+ {
+ ThrowInvalidOperationIfDefault();
+
+ int index = System.Array.IndexOf<T>(_array, item, _offset, _count);
+
+ Debug.Assert(index == -1 ||
+ (index >= _offset && index < _offset + _count));
+
+ return index >= 0 ? index - _offset : -1;
+ }
+
+ void IList<T>.Insert(int index, T item)
+ {
+ ThrowHelper.ThrowNotSupportedException();
+ }
+
+ void IList<T>.RemoveAt(int index)
+ {
+ ThrowHelper.ThrowNotSupportedException();
+ }
+ #endregion
+
+ #region IReadOnlyList<T>
+ T IReadOnlyList<T>.this[int index]
+ {
+ get
+ {
+ ThrowInvalidOperationIfDefault();
+ if (index < 0 || index >= _count)
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+
+ return _array[_offset + index];
+ }
+ }
+ #endregion IReadOnlyList<T>
+
+ #region ICollection<T>
+ bool ICollection<T>.IsReadOnly
+ {
+ get
+ {
+ // the indexer setter does not throw an exception although IsReadOnly is true.
+ // This is to match the behavior of arrays.
+ return true;
+ }
+ }
+
+ void ICollection<T>.Add(T item)
+ {
+ ThrowHelper.ThrowNotSupportedException();
+ }
+
+ void ICollection<T>.Clear()
+ {
+ ThrowHelper.ThrowNotSupportedException();
+ }
+
+ bool ICollection<T>.Contains(T item)
+ {
+ ThrowInvalidOperationIfDefault();
+
+ int index = System.Array.IndexOf<T>(_array, item, _offset, _count);
+
+ Debug.Assert(index == -1 ||
+ (index >= _offset && index < _offset + _count));
+
+ return index >= 0;
+ }
+
+ bool ICollection<T>.Remove(T item)
+ {
+ ThrowHelper.ThrowNotSupportedException();
+ return default(bool);
+ }
+ #endregion
+
+ #region IEnumerable<T>
+
+ IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
+ #endregion
+
+ #region IEnumerable
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ #endregion
+
+ private void ThrowInvalidOperationIfDefault()
+ {
+ if (_array == null)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NullArray);
+ }
+ }
+
+ public struct Enumerator : IEnumerator<T>
+ {
+ private readonly T[] _array;
+ private readonly int _start;
+ private readonly int _end; // cache Offset + Count, since it's a little slow
+ private int _current;
+
+ internal Enumerator(ArraySegment<T> arraySegment)
+ {
+ Debug.Assert(arraySegment.Array != null);
+ Debug.Assert(arraySegment.Offset >= 0);
+ Debug.Assert(arraySegment.Count >= 0);
+ Debug.Assert(arraySegment.Offset + arraySegment.Count <= arraySegment.Array.Length);
+
+ _array = arraySegment.Array;
+ _start = arraySegment.Offset;
+ _end = arraySegment.Offset + arraySegment.Count;
+ _current = arraySegment.Offset - 1;
+ }
+
+ public bool MoveNext()
+ {
+ if (_current < _end)
+ {
+ _current++;
+ return (_current < _end);
+ }
+ return false;
+ }
+
+ public T Current
+ {
+ get
+ {
+ if (_current < _start)
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumNotStarted();
+ if (_current >= _end)
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumEnded();
+ return _array[_current];
+ }
+ }
+
+ object IEnumerator.Current => Current;
+
+ void IEnumerator.Reset()
+ {
+ _current = _start - 1;
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ArrayTypeMismatchException.cs b/src/System.Private.CoreLib/shared/System/ArrayTypeMismatchException.cs
new file mode 100644
index 0000000000..49820f58f5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ArrayTypeMismatchException.cs
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: The arrays are of different primitive types.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The ArrayMismatchException is thrown when an attempt to store
+ // an object of the wrong type within an array occurs.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ArrayTypeMismatchException : SystemException
+ {
+ // Creates a new ArrayMismatchException with its message string set to
+ // the empty string, its HRESULT set to COR_E_ARRAYTYPEMISMATCH,
+ // and its ExceptionInfo reference set to null.
+ public ArrayTypeMismatchException()
+ : base(SR.Arg_ArrayTypeMismatchException)
+ {
+ HResult = HResults.COR_E_ARRAYTYPEMISMATCH;
+ }
+
+ // Creates a new ArrayMismatchException with its message string set to
+ // message, its HRESULT set to COR_E_ARRAYTYPEMISMATCH,
+ // and its ExceptionInfo reference set to null.
+ //
+ public ArrayTypeMismatchException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ARRAYTYPEMISMATCH;
+ }
+
+ public ArrayTypeMismatchException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_ARRAYTYPEMISMATCH;
+ }
+
+ protected ArrayTypeMismatchException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/AssemblyLoadEventArgs.cs b/src/System.Private.CoreLib/shared/System/AssemblyLoadEventArgs.cs
new file mode 100644
index 0000000000..d7e5249693
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/AssemblyLoadEventArgs.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+
+namespace System
+{
+ public class AssemblyLoadEventArgs : EventArgs
+ {
+ public AssemblyLoadEventArgs(Assembly loadedAssembly)
+ {
+ LoadedAssembly = loadedAssembly;
+ }
+
+ public Assembly LoadedAssembly { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/AssemblyLoadEventHandler.cs b/src/System.Private.CoreLib/shared/System/AssemblyLoadEventHandler.cs
new file mode 100644
index 0000000000..752379fdc7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/AssemblyLoadEventHandler.cs
@@ -0,0 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public delegate void AssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs args);
+}
diff --git a/src/System.Private.CoreLib/shared/System/AsyncCallback.cs b/src/System.Private.CoreLib/shared/System/AsyncCallback.cs
new file mode 100644
index 0000000000..036d44a4b9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/AsyncCallback.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Interface: AsyncCallbackDelegate
+**
+** Purpose: Type of callback for async operations
+**
+===========================================================*/
+
+namespace System
+{
+ public delegate void AsyncCallback(IAsyncResult ar);
+}
diff --git a/src/System.Private.CoreLib/shared/System/AttributeTargets.cs b/src/System.Private.CoreLib/shared/System/AttributeTargets.cs
new file mode 100644
index 0000000000..c33d19e85e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/AttributeTargets.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+namespace System
+{
+ // Enum used to indicate all the elements of the
+ // VOS it is valid to attach this element to.
+ [Flags]
+ public enum AttributeTargets
+ {
+ Assembly = 0x0001,
+ Module = 0x0002,
+ Class = 0x0004,
+ Struct = 0x0008,
+ Enum = 0x0010,
+ Constructor = 0x0020,
+ Method = 0x0040,
+ Property = 0x0080,
+ Field = 0x0100,
+ Event = 0x0200,
+ Interface = 0x0400,
+ Parameter = 0x0800,
+ Delegate = 0x1000,
+ ReturnValue = 0x2000,
+ GenericParameter = 0x4000,
+
+ All = Assembly | Module | Class | Struct | Enum | Constructor |
+ Method | Property | Field | Event | Interface | Parameter |
+ Delegate | ReturnValue | GenericParameter
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/AttributeUsageAttribute.cs b/src/System.Private.CoreLib/shared/System/AttributeUsageAttribute.cs
new file mode 100644
index 0000000000..219dc43e15
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/AttributeUsageAttribute.cs
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: The class denotes how to specify the usage of an attribute
+**
+**
+===========================================================*/
+
+using System.Reflection;
+
+namespace System
+{
+ /* By default, attributes are inherited and multiple attributes are not allowed */
+ [AttributeUsage(AttributeTargets.Class, Inherited = true)]
+ public sealed class AttributeUsageAttribute : Attribute
+ {
+ private AttributeTargets _attributeTarget = AttributeTargets.All; // Defaults to all
+ private bool _allowMultiple = false; // Defaults to false
+ private bool _inherited = true; // Defaults to true
+
+ internal static AttributeUsageAttribute Default = new AttributeUsageAttribute(AttributeTargets.All);
+
+ //Constructors
+ public AttributeUsageAttribute(AttributeTargets validOn)
+ {
+ _attributeTarget = validOn;
+ }
+ internal AttributeUsageAttribute(AttributeTargets validOn, bool allowMultiple, bool inherited)
+ {
+ _attributeTarget = validOn;
+ _allowMultiple = allowMultiple;
+ _inherited = inherited;
+ }
+
+ public AttributeTargets ValidOn
+ {
+ get { return _attributeTarget; }
+ }
+
+ public bool AllowMultiple
+ {
+ get { return _allowMultiple; }
+ set { _allowMultiple = value; }
+ }
+
+ public bool Inherited
+ {
+ get { return _inherited; }
+ set { _inherited = value; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/BadImageFormatException.cs b/src/System.Private.CoreLib/shared/System/BadImageFormatException.cs
new file mode 100644
index 0000000000..1743075a6f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/BadImageFormatException.cs
@@ -0,0 +1,129 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Exception to an invalid dll or executable format.
+**
+**
+===========================================================*/
+
+using System.Globalization;
+using System.IO;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public partial class BadImageFormatException : SystemException
+ {
+ private String _fileName; // The name of the corrupt PE file.
+ private String _fusionLog = null; // fusion log (when applicable)
+
+ public BadImageFormatException()
+ : base(SR.Arg_BadImageFormatException)
+ {
+ HResult = HResults.COR_E_BADIMAGEFORMAT;
+ }
+
+ public BadImageFormatException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_BADIMAGEFORMAT;
+ }
+
+ public BadImageFormatException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_BADIMAGEFORMAT;
+ }
+
+ public BadImageFormatException(String message, String fileName) : base(message)
+ {
+ HResult = HResults.COR_E_BADIMAGEFORMAT;
+ _fileName = fileName;
+ }
+
+ public BadImageFormatException(String message, String fileName, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_BADIMAGEFORMAT;
+ _fileName = fileName;
+ }
+
+ protected BadImageFormatException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ _fileName = info.GetString("BadImageFormat_FileName");
+ _fusionLog = info.GetString("BadImageFormat_FusionLog");
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("BadImageFormat_FileName", _fileName, typeof(string));
+ info.AddValue("BadImageFormat_FusionLog", _fusionLog, typeof(string));
+ }
+
+ public override String Message
+ {
+ get
+ {
+ SetMessageField();
+ return _message;
+ }
+ }
+
+ private void SetMessageField()
+ {
+ if (_message == null)
+ {
+ if ((_fileName == null) &&
+ (HResult == HResults.COR_E_EXCEPTION))
+ _message = SR.Arg_BadImageFormatException;
+
+ else
+ _message = FileLoadException.FormatFileLoadExceptionMessage(_fileName, HResult);
+ }
+ }
+
+ public String FileName
+ {
+ get { return _fileName; }
+ }
+
+ public override String ToString()
+ {
+ String s = GetType().ToString() + ": " + Message;
+
+ if (_fileName != null && _fileName.Length != 0)
+ s += Environment.NewLine + SR.Format(SR.IO_FileName_Name, _fileName);
+
+ if (InnerException != null)
+ s = s + " ---> " + InnerException.ToString();
+
+ if (StackTrace != null)
+ s += Environment.NewLine + StackTrace;
+
+ if (_fusionLog != null)
+ {
+ if (s == null)
+ s = " ";
+ s += Environment.NewLine;
+ s += Environment.NewLine;
+ s += _fusionLog;
+ }
+
+ return s;
+ }
+
+ public String FusionLog
+ {
+ get { return _fusionLog; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/BitConverter.cs b/src/System.Private.CoreLib/shared/System/BitConverter.cs
new file mode 100644
index 0000000000..e3cf20eb6a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/BitConverter.cs
@@ -0,0 +1,474 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ // The BitConverter class contains methods for
+ // converting an array of bytes to one of the base data
+ // types, as well as for converting a base data type to an
+ // array of bytes.
+ public static class BitConverter
+ {
+ // This field indicates the "endianess" of the architecture.
+ // The value is set to true if the architecture is
+ // little endian; false if it is big endian.
+#if BIGENDIAN
+ public static readonly bool IsLittleEndian /* = false */;
+#else
+ public static readonly bool IsLittleEndian = true;
+#endif
+
+ // Converts a Boolean into an array of bytes with length one.
+ public static byte[] GetBytes(bool value)
+ {
+ byte[] r = new byte[1];
+ r[0] = (value ? (byte)1 : (byte)0);
+ return r;
+ }
+
+ // Converts a Boolean into a Span of bytes with length one.
+ public static bool TryWriteBytes(Span<byte> destination, bool value)
+ {
+ if (destination.Length < sizeof(byte))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value ? (byte)1 : (byte)0);
+ return true;
+ }
+
+ // Converts a char into an array of bytes with length two.
+ public static byte[] GetBytes(char value)
+ {
+ byte[] bytes = new byte[sizeof(char)];
+ Unsafe.As<byte, char>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts a char into a Span
+ public static bool TryWriteBytes(Span<byte> destination, char value)
+ {
+ if (destination.Length < sizeof(char))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts a short into an array of bytes with length
+ // two.
+ public static byte[] GetBytes(short value)
+ {
+ byte[] bytes = new byte[sizeof(short)];
+ Unsafe.As<byte, short>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts a short into a Span
+ public static bool TryWriteBytes(Span<byte> destination, short value)
+ {
+ if (destination.Length < sizeof(short))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts an int into an array of bytes with length
+ // four.
+ public static byte[] GetBytes(int value)
+ {
+ byte[] bytes = new byte[sizeof(int)];
+ Unsafe.As<byte, int>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts an int into a Span
+ public static bool TryWriteBytes(Span<byte> destination, int value)
+ {
+ if (destination.Length < sizeof(int))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts a long into an array of bytes with length
+ // eight.
+ public static byte[] GetBytes(long value)
+ {
+ byte[] bytes = new byte[sizeof(long)];
+ Unsafe.As<byte, long>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts a long into a Span
+ public static bool TryWriteBytes(Span<byte> destination, long value)
+ {
+ if (destination.Length < sizeof(long))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts an ushort into an array of bytes with
+ // length two.
+ [CLSCompliant(false)]
+ public static byte[] GetBytes(ushort value)
+ {
+ byte[] bytes = new byte[sizeof(ushort)];
+ Unsafe.As<byte, ushort>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts a ushort into a Span
+ [CLSCompliant(false)]
+ public static bool TryWriteBytes(Span<byte> destination, ushort value)
+ {
+ if (destination.Length < sizeof(ushort))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts an uint into an array of bytes with
+ // length four.
+ [CLSCompliant(false)]
+ public static byte[] GetBytes(uint value)
+ {
+ byte[] bytes = new byte[sizeof(uint)];
+ Unsafe.As<byte, uint>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts a uint into a Span
+ [CLSCompliant(false)]
+ public static bool TryWriteBytes(Span<byte> destination, uint value)
+ {
+ if (destination.Length < sizeof(uint))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts an unsigned long into an array of bytes with
+ // length eight.
+ [CLSCompliant(false)]
+ public static byte[] GetBytes(ulong value)
+ {
+ byte[] bytes = new byte[sizeof(ulong)];
+ Unsafe.As<byte, ulong>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts a ulong into a Span
+ [CLSCompliant(false)]
+ public static bool TryWriteBytes(Span<byte> destination, ulong value)
+ {
+ if (destination.Length < sizeof(ulong))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts a float into an array of bytes with length
+ // four.
+ public static byte[] GetBytes(float value)
+ {
+ byte[] bytes = new byte[sizeof(float)];
+ Unsafe.As<byte, float>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts a float into a Span
+ public static bool TryWriteBytes(Span<byte> destination, float value)
+ {
+ if (destination.Length < sizeof(float))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts a double into an array of bytes with length
+ // eight.
+ public static byte[] GetBytes(double value)
+ {
+ byte[] bytes = new byte[sizeof(double)];
+ Unsafe.As<byte, double>(ref bytes[0]) = value;
+ return bytes;
+ }
+
+ // Converts a double into a Span
+ public static bool TryWriteBytes(Span<byte> destination, double value)
+ {
+ if (destination.Length < sizeof(double))
+ return false;
+
+ Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
+ return true;
+ }
+
+ // Converts an array of bytes into a char.
+ public static char ToChar(byte[] value, int startIndex) => unchecked((char)ToInt16(value, startIndex));
+
+ // Converts a Span into a char
+ public static char ToChar(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(char))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<char>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into a short.
+ public static short ToInt16(byte[] value, int startIndex)
+ {
+ if (value == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
+ if (unchecked((uint)startIndex) >= unchecked((uint)value.Length))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
+ if (startIndex > value.Length - sizeof(short))
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall, ExceptionArgument.value);
+
+ return Unsafe.ReadUnaligned<short>(ref value[startIndex]);
+ }
+
+ // Converts a Span into a short
+ public static short ToInt16(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(short))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<short>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into an int.
+ public static int ToInt32(byte[] value, int startIndex)
+ {
+ if (value == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
+ if (unchecked((uint)startIndex) >= unchecked((uint)value.Length))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
+ if (startIndex > value.Length - sizeof(int))
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall, ExceptionArgument.value);
+
+ return Unsafe.ReadUnaligned<int>(ref value[startIndex]);
+ }
+
+ // Converts a Span into an int
+ public static int ToInt32(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(int))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<int>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into a long.
+ public static long ToInt64(byte[] value, int startIndex)
+ {
+ if (value == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
+ if (unchecked((uint)startIndex) >= unchecked((uint)value.Length))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
+ if (startIndex > value.Length - sizeof(long))
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall, ExceptionArgument.value);
+
+ return Unsafe.ReadUnaligned<long>(ref value[startIndex]);
+ }
+
+ // Converts a Span into a long
+ public static long ToInt64(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(long))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<long>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into an ushort.
+ //
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(byte[] value, int startIndex) => unchecked((ushort)ToInt16(value, startIndex));
+
+ // Converts a Span into a ushort
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(ushort))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<ushort>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into an uint.
+ //
+ [CLSCompliant(false)]
+ public static uint ToUInt32(byte[] value, int startIndex) => unchecked((uint)ToInt32(value, startIndex));
+
+ // Convert a Span into a uint
+ [CLSCompliant(false)]
+ public static uint ToUInt32(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(uint))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<uint>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into an unsigned long.
+ //
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(byte[] value, int startIndex) => unchecked((ulong)ToInt64(value, startIndex));
+
+ // Converts a Span into an unsigned long
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(ulong))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<ulong>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into a float.
+ public static float ToSingle(byte[] value, int startIndex) => Int32BitsToSingle(ToInt32(value, startIndex));
+
+ // Converts a Span into a float
+ public static float ToSingle(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(float))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into a double.
+ public static double ToDouble(byte[] value, int startIndex) => Int64BitsToDouble(ToInt64(value, startIndex));
+
+ // Converts a Span into a double
+ public static double ToDouble(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(double))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<double>(ref MemoryMarshal.GetReference(value));
+ }
+
+ // Converts an array of bytes into a String.
+ public static string ToString(byte[] value, int startIndex, int length)
+ {
+ if (value == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
+ if (startIndex < 0 || startIndex >= value.Length && startIndex > 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_GenericPositive);
+ if (startIndex > value.Length - length)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall, ExceptionArgument.value);
+
+ if (length == 0)
+ {
+ return string.Empty;
+ }
+
+ if (length > (int.MaxValue / 3))
+ {
+ // (Int32.MaxValue / 3) == 715,827,882 Bytes == 699 MB
+ throw new ArgumentOutOfRangeException(nameof(length), SR.Format(SR.ArgumentOutOfRange_LengthTooLarge, (int.MaxValue / 3)));
+ }
+
+ return string.Create(length * 3 - 1, (value, startIndex, length), (dst, state) =>
+ {
+ const string HexValues = "0123456789ABCDEF";
+
+ var src = new ReadOnlySpan<byte>(state.value, state.startIndex, state.length);
+
+ int i = 0;
+ int j = 0;
+
+ byte b = src[i++];
+ dst[j++] = HexValues[b >> 4];
+ dst[j++] = HexValues[b & 0xF];
+
+ while (i < src.Length)
+ {
+ b = src[i++];
+ dst[j++] = '-';
+ dst[j++] = HexValues[b >> 4];
+ dst[j++] = HexValues[b & 0xF];
+ }
+ });
+ }
+
+ // Converts an array of bytes into a String.
+ public static string ToString(byte[] value)
+ {
+ if (value == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
+ return ToString(value, 0, value.Length);
+ }
+
+ // Converts an array of bytes into a String.
+ public static string ToString(byte[] value, int startIndex)
+ {
+ if (value == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
+ return ToString(value, startIndex, value.Length - startIndex);
+ }
+
+ /*==================================ToBoolean===================================
+ **Action: Convert an array of bytes to a boolean value. We treat this array
+ ** as if the first 4 bytes were an Int4 an operate on this value.
+ **Returns: True if the Int4 value of the first 4 bytes is non-zero.
+ **Arguments: value -- The byte array
+ ** startIndex -- The position within the array.
+ **Exceptions: See ToInt4.
+ ==============================================================================*/
+ // Converts an array of bytes into a boolean.
+ public static bool ToBoolean(byte[] value, int startIndex)
+ {
+ if (value == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
+ if (startIndex < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
+ if (startIndex > value.Length - 1)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index); // differs from other overloads, which throw base ArgumentException
+
+ return value[startIndex] != 0;
+ }
+
+ public static bool ToBoolean(ReadOnlySpan<byte> value)
+ {
+ if (value.Length < sizeof(byte))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
+ return Unsafe.ReadUnaligned<byte>(ref MemoryMarshal.GetReference(value)) != 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe long DoubleToInt64Bits(double value)
+ {
+ return *((long*)&value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe double Int64BitsToDouble(long value)
+ {
+ return *((double*)&value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe int SingleToInt32Bits(float value)
+ {
+ return *((int*)&value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe float Int32BitsToSingle(int value)
+ {
+ return *((float*)&value);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Boolean.cs b/src/System.Private.CoreLib/shared/System/Boolean.cs
new file mode 100644
index 0000000000..fdbc905332
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Boolean.cs
@@ -0,0 +1,349 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: The boolean class serves as a wrapper for the primitive
+** type boolean.
+**
+**
+===========================================================*/
+
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Boolean : IComparable, IConvertible, IComparable<Boolean>, IEquatable<Boolean>
+ {
+ //
+ // Member Variables
+ //
+ private bool m_value; // Do not rename (binary serialization)
+
+ // The true value.
+ //
+ internal const int True = 1;
+
+ // The false value.
+ //
+ internal const int False = 0;
+
+
+ //
+ // Internal Constants are real consts for performance.
+ //
+
+ // The internal string representation of true.
+ //
+ internal const String TrueLiteral = "True";
+
+ // The internal string representation of false.
+ //
+ internal const String FalseLiteral = "False";
+
+
+ //
+ // Public Constants
+ //
+
+ // The public string representation of true.
+ //
+ public static readonly String TrueString = TrueLiteral;
+
+ // The public string representation of false.
+ //
+ public static readonly String FalseString = FalseLiteral;
+
+ //
+ // Overriden Instance Methods
+ //
+ /*=================================GetHashCode==================================
+ **Args: None
+ **Returns: 1 or 0 depending on whether this instance represents true or false.
+ **Exceptions: None
+ **Overriden From: Value
+ ==============================================================================*/
+ // Provides a hash code for this instance.
+ public override int GetHashCode()
+ {
+ return (m_value) ? True : False;
+ }
+
+ /*===================================ToString===================================
+ **Args: None
+ **Returns: "True" or "False" depending on the state of the boolean.
+ **Exceptions: None.
+ ==============================================================================*/
+ // Converts the boolean value of this instance to a String.
+ public override String ToString()
+ {
+ if (false == m_value)
+ {
+ return FalseLiteral;
+ }
+ return TrueLiteral;
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return ToString();
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten)
+ {
+ string s = m_value ? TrueLiteral : FalseLiteral;
+
+ if (s.AsSpan().TryCopyTo(destination))
+ {
+ charsWritten = s.Length;
+ return true;
+ }
+ else
+ {
+ charsWritten = 0;
+ return false;
+ }
+ }
+
+ // Determines whether two Boolean objects are equal.
+ public override bool Equals(Object obj)
+ {
+ //If it's not a boolean, we're definitely not equal
+ if (!(obj is Boolean))
+ {
+ return false;
+ }
+
+ return (m_value == ((Boolean)obj).m_value);
+ }
+
+ [NonVersionable]
+ public bool Equals(Boolean obj)
+ {
+ return m_value == obj;
+ }
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship. For booleans, false sorts before true.
+ // null is considered to be less than any instance.
+ // If object is not of type boolean, this method throws an ArgumentException.
+ //
+ // Returns a value less than zero if this object
+ //
+ public int CompareTo(Object obj)
+ {
+ if (obj == null)
+ {
+ return 1;
+ }
+ if (!(obj is Boolean))
+ {
+ throw new ArgumentException(SR.Arg_MustBeBoolean);
+ }
+
+ if (m_value == ((Boolean)obj).m_value)
+ {
+ return 0;
+ }
+ else if (m_value == false)
+ {
+ return -1;
+ }
+ return 1;
+ }
+
+ public int CompareTo(Boolean value)
+ {
+ if (m_value == value)
+ {
+ return 0;
+ }
+ else if (m_value == false)
+ {
+ return -1;
+ }
+ return 1;
+ }
+
+ //
+ // Static Methods
+ //
+
+ // Determines whether a String represents true or false.
+ //
+ public static Boolean Parse(String value)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ return Parse(value.AsSpan());
+ }
+
+ public static bool Parse(ReadOnlySpan<char> value) =>
+ TryParse(value, out bool result) ? result : throw new FormatException(SR.Format(SR.Format_BadBoolean, new string(value)));
+
+ // Determines whether a String represents true or false.
+ //
+ public static Boolean TryParse(String value, out Boolean result)
+ {
+ if (value == null)
+ {
+ result = false;
+ return false;
+ }
+
+ return TryParse(value.AsSpan(), out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> value, out bool result)
+ {
+ ReadOnlySpan<char> trueSpan = TrueLiteral.AsSpan();
+ if (trueSpan.EqualsOrdinalIgnoreCase(value))
+ {
+ result = true;
+ return true;
+ }
+
+ ReadOnlySpan<char> falseSpan = FalseLiteral.AsSpan();
+ if (falseSpan.EqualsOrdinalIgnoreCase(value))
+ {
+ result = false;
+ return true;
+ }
+
+ // Special case: Trim whitespace as well as null characters.
+ value = TrimWhiteSpaceAndNull(value);
+
+ if (trueSpan.EqualsOrdinalIgnoreCase(value))
+ {
+ result = true;
+ return true;
+ }
+
+ if (falseSpan.EqualsOrdinalIgnoreCase(value))
+ {
+ result = false;
+ return true;
+ }
+
+ result = false;
+ return false;
+ }
+
+ private static ReadOnlySpan<char> TrimWhiteSpaceAndNull(ReadOnlySpan<char> value)
+ {
+ const char nullChar = (char)0x0000;
+
+ int start = 0;
+ while (start < value.Length)
+ {
+ if (!Char.IsWhiteSpace(value[start]) && value[start] != nullChar)
+ {
+ break;
+ }
+ start++;
+ }
+
+ int end = value.Length - 1;
+ while (end >= start)
+ {
+ if (!Char.IsWhiteSpace(value[end]) && value[end] != nullChar)
+ {
+ break;
+ }
+ end--;
+ }
+
+ return value.Slice(start, end - start + 1);
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Boolean;
+ }
+
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Boolean", "Char"));
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Boolean", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/ArrayPool.cs b/src/System.Private.CoreLib/shared/System/Buffers/ArrayPool.cs
new file mode 100644
index 0000000000..22ad7821f0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/ArrayPool.cs
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers
+{
+ /// <summary>
+ /// Provides a resource pool that enables reusing instances of type <see cref="T:T[]"/>.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Renting and returning buffers with an <see cref="ArrayPool{T}"/> can increase performance
+ /// in situations where arrays are created and destroyed frequently, resulting in significant
+ /// memory pressure on the garbage collector.
+ /// </para>
+ /// <para>
+ /// This class is thread-safe. All members may be used by multiple threads concurrently.
+ /// </para>
+ /// </remarks>
+ public abstract class ArrayPool<T>
+ {
+ /// <summary>
+ /// Retrieves a shared <see cref="ArrayPool{T}"/> instance.
+ /// </summary>
+ /// <remarks>
+ /// The shared pool provides a default implementation of <see cref="ArrayPool{T}"/>
+ /// that's intended for general applicability. It maintains arrays of multiple sizes, and
+ /// may hand back a larger array than was actually requested, but will never hand back a smaller
+ /// array than was requested. Renting a buffer from it with <see cref="Rent"/> will result in an
+ /// existing buffer being taken from the pool if an appropriate buffer is available or in a new
+ /// buffer being allocated if one is not available.
+ /// byte[] and char[] are the most commonly pooled array types. For these we use a special pool type
+ /// optimized for very fast access speeds, at the expense of more memory consumption.
+ /// The shared pool instance is created lazily on first access.
+ /// </remarks>
+ public static ArrayPool<T> Shared { get; } = new TlsOverPerCoreLockedStacksArrayPool<T>();
+
+ /// <summary>
+ /// Creates a new <see cref="ArrayPool{T}"/> instance using default configuration options.
+ /// </summary>
+ /// <returns>A new <see cref="ArrayPool{T}"/> instance.</returns>
+ public static ArrayPool<T> Create() => new ConfigurableArrayPool<T>();
+
+ /// <summary>
+ /// Creates a new <see cref="ArrayPool{T}"/> instance using custom configuration options.
+ /// </summary>
+ /// <param name="maxArrayLength">The maximum length of array instances that may be stored in the pool.</param>
+ /// <param name="maxArraysPerBucket">
+ /// The maximum number of array instances that may be stored in each bucket in the pool. The pool
+ /// groups arrays of similar lengths into buckets for faster access.
+ /// </param>
+ /// <returns>A new <see cref="ArrayPool{T}"/> instance with the specified configuration options.</returns>
+ /// <remarks>
+ /// The created pool will group arrays into buckets, with no more than <paramref name="maxArraysPerBucket"/>
+ /// in each bucket and with those arrays not exceeding <paramref name="maxArrayLength"/> in length.
+ /// </remarks>
+ public static ArrayPool<T> Create(int maxArrayLength, int maxArraysPerBucket) =>
+ new ConfigurableArrayPool<T>(maxArrayLength, maxArraysPerBucket);
+
+ /// <summary>
+ /// Retrieves a buffer that is at least the requested length.
+ /// </summary>
+ /// <param name="minimumLength">The minimum length of the array needed.</param>
+ /// <returns>
+ /// An <see cref="T:T[]"/> that is at least <paramref name="minimumLength"/> in length.
+ /// </returns>
+ /// <remarks>
+ /// This buffer is loaned to the caller and should be returned to the same pool via
+ /// <see cref="Return"/> so that it may be reused in subsequent usage of <see cref="Rent"/>.
+ /// It is not a fatal error to not return a rented buffer, but failure to do so may lead to
+ /// decreased application performance, as the pool may need to create a new buffer to replace
+ /// the one lost.
+ /// </remarks>
+ public abstract T[] Rent(int minimumLength);
+
+ /// <summary>
+ /// Returns to the pool an array that was previously obtained via <see cref="Rent"/> on the same
+ /// <see cref="ArrayPool{T}"/> instance.
+ /// </summary>
+ /// <param name="array">
+ /// The buffer previously obtained from <see cref="Rent"/> to return to the pool.
+ /// </param>
+ /// <param name="clearArray">
+ /// If <c>true</c> and if the pool will store the buffer to enable subsequent reuse, <see cref="Return"/>
+ /// will clear <paramref name="array"/> of its contents so that a subsequent consumer via <see cref="Rent"/>
+ /// will not see the previous consumer's content. If <c>false</c> or if the pool will release the buffer,
+ /// the array's contents are left unchanged.
+ /// </param>
+ /// <remarks>
+ /// Once a buffer has been returned to the pool, the caller gives up all ownership of the buffer
+ /// and must not use it. The reference returned from a given call to <see cref="Rent"/> must only be
+ /// returned via <see cref="Return"/> once. The default <see cref="ArrayPool{T}"/>
+ /// may hold onto the returned buffer in order to rent it again, or it may release the returned buffer
+ /// if it's determined that the pool already has enough buffers stored.
+ /// </remarks>
+ public abstract void Return(T[] array, bool clearArray = false);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/ArrayPoolEventSource.cs b/src/System.Private.CoreLib/shared/System/Buffers/ArrayPoolEventSource.cs
new file mode 100644
index 0000000000..d0563c4977
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/ArrayPoolEventSource.cs
@@ -0,0 +1,104 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+
+namespace System.Buffers
+{
+ [EventSource(Guid = "0866B2B8-5CEF-5DB9-2612-0C0FFD814A44", Name = "System.Buffers.ArrayPoolEventSource")]
+ internal sealed class ArrayPoolEventSource : EventSource
+ {
+ internal readonly static ArrayPoolEventSource Log = new ArrayPoolEventSource();
+
+ /// <summary>The reason for a BufferAllocated event.</summary>
+ internal enum BufferAllocatedReason : int
+ {
+ /// <summary>The pool is allocating a buffer to be pooled in a bucket.</summary>
+ Pooled,
+ /// <summary>The requested buffer size was too large to be pooled.</summary>
+ OverMaximumSize,
+ /// <summary>The pool has already allocated for pooling as many buffers of a particular size as it's allowed.</summary>
+ PoolExhausted
+ }
+
+ // The ArrayPoolEventSource GUID is {0866b2b8-5cef-5db9-2612-0c0ffd814a44}
+ private ArrayPoolEventSource() : base(new Guid(0x0866b2b8, 0x5cef, 0x5db9, 0x26, 0x12, 0x0c, 0x0f, 0xfd, 0x81, 0x4a, 0x44), "System.Buffers.ArrayPoolEventSource") { }
+
+ /// <summary>
+ /// Event for when a buffer is rented. This is invoked once for every successful call to Rent,
+ /// regardless of whether a buffer is allocated or a buffer is taken from the pool. In a
+ /// perfect situation where all rented buffers are returned, we expect to see the number
+ /// of BufferRented events exactly match the number of BuferReturned events, with the number
+ /// of BufferAllocated events being less than or equal to those numbers (ideally significantly
+ /// less than).
+ /// </summary>
+ [Event(1, Level = EventLevel.Verbose)]
+ internal unsafe void BufferRented(int bufferId, int bufferSize, int poolId, int bucketId)
+ {
+ EventData* payload = stackalloc EventData[4];
+ payload[0].Size = sizeof(int);
+ payload[0].DataPointer = ((IntPtr)(&bufferId));
+ payload[0].Reserved = 0;
+ payload[1].Size = sizeof(int);
+ payload[1].DataPointer = ((IntPtr)(&bufferSize));
+ payload[1].Reserved = 0;
+ payload[2].Size = sizeof(int);
+ payload[2].DataPointer = ((IntPtr)(&poolId));
+ payload[2].Reserved = 0;
+ payload[3].Size = sizeof(int);
+ payload[3].DataPointer = ((IntPtr)(&bucketId));
+ payload[3].Reserved = 0;
+ WriteEventCore(1, 4, payload);
+ }
+
+ /// <summary>
+ /// Event for when a buffer is allocated by the pool. In an ideal situation, the number
+ /// of BufferAllocated events is significantly smaller than the number of BufferRented and
+ /// BufferReturned events.
+ /// </summary>
+ [Event(2, Level = EventLevel.Informational)]
+ internal unsafe void BufferAllocated(int bufferId, int bufferSize, int poolId, int bucketId, BufferAllocatedReason reason)
+ {
+ EventData* payload = stackalloc EventData[5];
+ payload[0].Size = sizeof(int);
+ payload[0].DataPointer = ((IntPtr)(&bufferId));
+ payload[0].Reserved = 0;
+ payload[1].Size = sizeof(int);
+ payload[1].DataPointer = ((IntPtr)(&bufferSize));
+ payload[1].Reserved = 0;
+ payload[2].Size = sizeof(int);
+ payload[2].DataPointer = ((IntPtr)(&poolId));
+ payload[2].Reserved = 0;
+ payload[3].Size = sizeof(int);
+ payload[3].DataPointer = ((IntPtr)(&bucketId));
+ payload[3].Reserved = 0;
+ payload[4].Size = sizeof(BufferAllocatedReason);
+ payload[4].DataPointer = ((IntPtr)(&reason));
+ payload[4].Reserved = 0;
+ WriteEventCore(2, 5, payload);
+ }
+
+ /// <summary>
+ /// Event raised when a buffer is returned to the pool. This event is raised regardless of whether
+ /// the returned buffer is stored or dropped. In an ideal situation, the number of BufferReturned
+ /// events exactly matches the number of BufferRented events.
+ /// </summary>
+ [Event(3, Level = EventLevel.Verbose)]
+ internal void BufferReturned(int bufferId, int bufferSize, int poolId) => WriteEvent(3, bufferId, bufferSize, poolId);
+
+ /// <summary>
+ /// Event raised when we attempt to free a buffer due to inactivity or memory pressure (by no longer
+ /// referencing it). It is possible (although not commmon) this buffer could be rented as we attempt
+ /// to free it. A rent event before or after this event for the same ID, is a rare, but expected case.
+ /// </summary>
+ [Event(4, Level = EventLevel.Informational)]
+ internal void BufferTrimmed(int bufferId, int bufferSize, int poolId) => WriteEvent(4, bufferId, bufferSize, poolId);
+
+ /// <summary>
+ /// Event raised when we check to trim buffers.
+ /// </summary>
+ [Event(5, Level = EventLevel.Informational)]
+ internal void BufferTrimPoll(int milliseconds, int pressure) => WriteEvent(5, milliseconds, pressure);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs b/src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs
new file mode 100644
index 0000000000..f7b6034d20
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs
@@ -0,0 +1,265 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Threading;
+
+namespace System.Buffers
+{
+ internal sealed partial class ConfigurableArrayPool<T> : ArrayPool<T>
+ {
+ /// <summary>The default maximum length of each array in the pool (2^20).</summary>
+ private const int DefaultMaxArrayLength = 1024 * 1024;
+ /// <summary>The default maximum number of arrays per bucket that are available for rent.</summary>
+ private const int DefaultMaxNumberOfArraysPerBucket = 50;
+
+ private readonly Bucket[] _buckets;
+
+ internal ConfigurableArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket)
+ {
+ }
+
+ internal ConfigurableArrayPool(int maxArrayLength, int maxArraysPerBucket)
+ {
+ if (maxArrayLength <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(maxArrayLength));
+ }
+ if (maxArraysPerBucket <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(maxArraysPerBucket));
+ }
+
+ // Our bucketing algorithm has a min length of 2^4 and a max length of 2^30.
+ // Constrain the actual max used to those values.
+ const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000;
+ if (maxArrayLength > MaximumArrayLength)
+ {
+ maxArrayLength = MaximumArrayLength;
+ }
+ else if (maxArrayLength < MinimumArrayLength)
+ {
+ maxArrayLength = MinimumArrayLength;
+ }
+
+ // Create the buckets.
+ int poolId = Id;
+ int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength);
+ var buckets = new Bucket[maxBuckets + 1];
+ for (int i = 0; i < buckets.Length; i++)
+ {
+ buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
+ }
+ _buckets = buckets;
+ }
+
+ /// <summary>Gets an ID for the pool to use with events.</summary>
+ private int Id => GetHashCode();
+
+ public override T[] Rent(int minimumLength)
+ {
+ // Arrays can't be smaller than zero. We allow requesting zero-length arrays (even though
+ // pooling such an array isn't valuable) as it's a valid length array, and we want the pool
+ // to be usable in general instead of using `new`, even for computed lengths.
+ if (minimumLength < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(minimumLength));
+ }
+ else if (minimumLength == 0)
+ {
+ // No need for events with the empty array. Our pool is effectively infinite
+ // and we'll never allocate for rents and never store for returns.
+ return Array.Empty<T>();
+ }
+
+ var log = ArrayPoolEventSource.Log;
+ T[] buffer = null;
+
+ int index = Utilities.SelectBucketIndex(minimumLength);
+ if (index < _buckets.Length)
+ {
+ // Search for an array starting at the 'index' bucket. If the bucket is empty, bump up to the
+ // next higher bucket and try that one, but only try at most a few buckets.
+ const int MaxBucketsToTry = 2;
+ int i = index;
+ do
+ {
+ // Attempt to rent from the bucket. If we get a buffer from it, return it.
+ buffer = _buckets[i].Rent();
+ if (buffer != null)
+ {
+ if (log.IsEnabled())
+ {
+ log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id);
+ }
+ return buffer;
+ }
+ }
+ while (++i < _buckets.Length && i != index + MaxBucketsToTry);
+
+ // The pool was exhausted for this buffer size. Allocate a new buffer with a size corresponding
+ // to the appropriate bucket.
+ buffer = new T[_buckets[index]._bufferLength];
+ }
+ else
+ {
+ // The request was for a size too large for the pool. Allocate an array of exactly the requested length.
+ // When it's returned to the pool, we'll simply throw it away.
+ buffer = new T[minimumLength];
+ }
+
+ if (log.IsEnabled())
+ {
+ int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer
+ log.BufferRented(bufferId, buffer.Length, Id, bucketId);
+ log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, index >= _buckets.Length ?
+ ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
+ }
+
+ return buffer;
+ }
+
+ public override void Return(T[] array, bool clearArray = false)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+ else if (array.Length == 0)
+ {
+ // Ignore empty arrays. When a zero-length array is rented, we return a singleton
+ // rather than actually taking a buffer out of the lowest bucket.
+ return;
+ }
+
+ // Determine with what bucket this array length is associated
+ int bucket = Utilities.SelectBucketIndex(array.Length);
+
+ // If we can tell that the buffer was allocated, drop it. Otherwise, check if we have space in the pool
+ if (bucket < _buckets.Length)
+ {
+ // Clear the array if the user requests
+ if (clearArray)
+ {
+ Array.Clear(array, 0, array.Length);
+ }
+
+ // Return the buffer to its bucket. In the future, we might consider having Return return false
+ // instead of dropping a bucket, in which case we could try to return to a lower-sized bucket,
+ // just as how in Rent we allow renting from a higher-sized bucket.
+ _buckets[bucket].Return(array);
+ }
+
+ // Log that the buffer was returned
+ var log = ArrayPoolEventSource.Log;
+ if (log.IsEnabled())
+ {
+ log.BufferReturned(array.GetHashCode(), array.Length, Id);
+ }
+ }
+
+ /// <summary>Provides a thread-safe bucket containing buffers that can be Rent'd and Return'd.</summary>
+ private sealed class Bucket
+ {
+ internal readonly int _bufferLength;
+ private readonly T[][] _buffers;
+ private readonly int _poolId;
+
+ private SpinLock _lock; // do not make this readonly; it's a mutable struct
+ private int _index;
+
+ /// <summary>
+ /// Creates the pool with numberOfBuffers arrays where each buffer is of bufferLength length.
+ /// </summary>
+ internal Bucket(int bufferLength, int numberOfBuffers, int poolId)
+ {
+ _lock = new SpinLock(Debugger.IsAttached); // only enable thread tracking if debugger is attached; it adds non-trivial overheads to Enter/Exit
+ _buffers = new T[numberOfBuffers][];
+ _bufferLength = bufferLength;
+ _poolId = poolId;
+ }
+
+ /// <summary>Gets an ID for the bucket to use with events.</summary>
+ internal int Id => GetHashCode();
+
+ /// <summary>Takes an array from the bucket. If the bucket is empty, returns null.</summary>
+ internal T[] Rent()
+ {
+ T[][] buffers = _buffers;
+ T[] buffer = null;
+
+ // While holding the lock, grab whatever is at the next available index and
+ // update the index. We do as little work as possible while holding the spin
+ // lock to minimize contention with other threads. The try/finally is
+ // necessary to properly handle thread aborts on platforms which have them.
+ bool lockTaken = false, allocateBuffer = false;
+ try
+ {
+ _lock.Enter(ref lockTaken);
+
+ if (_index < buffers.Length)
+ {
+ buffer = buffers[_index];
+ buffers[_index++] = null;
+ allocateBuffer = buffer == null;
+ }
+ }
+ finally
+ {
+ if (lockTaken) _lock.Exit(false);
+ }
+
+ // While we were holding the lock, we grabbed whatever was at the next available index, if
+ // there was one. If we tried and if we got back null, that means we hadn't yet allocated
+ // for that slot, in which case we should do so now.
+ if (allocateBuffer)
+ {
+ buffer = new T[_bufferLength];
+
+ var log = ArrayPoolEventSource.Log;
+ if (log.IsEnabled())
+ {
+ log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id,
+ ArrayPoolEventSource.BufferAllocatedReason.Pooled);
+ }
+ }
+
+ return buffer;
+ }
+
+ /// <summary>
+ /// Attempts to return the buffer to the bucket. If successful, the buffer will be stored
+ /// in the bucket and true will be returned; otherwise, the buffer won't be stored, and false
+ /// will be returned.
+ /// </summary>
+ internal void Return(T[] array)
+ {
+ // Check to see if the buffer is the correct size for this bucket
+ if (array.Length != _bufferLength)
+ {
+ throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array));
+ }
+
+ // While holding the spin lock, if there's room available in the bucket,
+ // put the buffer into the next available slot. Otherwise, we just drop it.
+ // The try/finally is necessary to properly handle thread aborts on platforms
+ // which have them.
+ bool lockTaken = false;
+ try
+ {
+ _lock.Enter(ref lockTaken);
+
+ if (_index != 0)
+ {
+ _buffers[--_index] = array;
+ }
+ }
+ finally
+ {
+ if (lockTaken) _lock.Exit(false);
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/IMemoryOwner.cs b/src/System.Private.CoreLib/shared/System/Buffers/IMemoryOwner.cs
new file mode 100644
index 0000000000..44f16c5827
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/IMemoryOwner.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers
+{
+ /// <summary>
+ /// Owner of Memory<typeparamref name="T"/> that is responsible for disposing the underlying memory appropriately.
+ /// </summary>
+ public interface IMemoryOwner<T> : IDisposable
+ {
+ /// <summary>
+ /// Returns a Memory<typeparamref name="T"/>.
+ /// </summary>
+ Memory<T> Memory { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/IPinnable.cs b/src/System.Private.CoreLib/shared/System/Buffers/IPinnable.cs
new file mode 100644
index 0000000000..623e716a05
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/IPinnable.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers
+{
+ /// <summary>
+ /// Provides a mechanism for pinning and unpinning objects to prevent the GC from moving them.
+ /// </summary>
+ public interface IPinnable
+ {
+ /// <summary>
+ /// Call this method to indicate that the IPinnable object can not be moved by the garbage collector.
+ /// The address of the pinned object can be taken.
+ /// <param name="elementIndex">The offset to the element within the memory at which the returned <see cref="MemoryHandle"/> points to.</param>
+ /// </summary>
+ MemoryHandle Pin(int elementIndex);
+
+ /// <summary>
+ /// Call this method to indicate that the IPinnable object no longer needs to be pinned.
+ /// The garbage collector is free to move the object now.
+ /// </summary>
+ void Unpin();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/MemoryHandle.cs b/src/System.Private.CoreLib/shared/System/Buffers/MemoryHandle.cs
new file mode 100644
index 0000000000..231add1c01
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/MemoryHandle.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime;
+using System.Runtime.InteropServices;
+
+namespace System.Buffers
+{
+ /// <summary>
+ /// A handle for the memory.
+ /// </summary>
+ public unsafe struct MemoryHandle : IDisposable
+ {
+ private void* _pointer;
+ private GCHandle _handle;
+ private IPinnable _pinnable;
+
+ /// <summary>
+ /// Creates a new memory handle for the memory.
+ /// </summary>
+ /// <param name="pointer">pointer to memory</param>
+ /// <param name="pinnable">reference to manually managed object, or default if there is no memory manager</param>
+ /// <param name="handle">handle used to pin array buffers</param>
+ [CLSCompliant(false)]
+ public MemoryHandle(void* pointer, GCHandle handle = default, IPinnable pinnable = default)
+ {
+ _pointer = pointer;
+ _handle = handle;
+ _pinnable = pinnable;
+ }
+
+ /// <summary>
+ /// Returns the pointer to memory, where the memory is assumed to be pinned and hence the address won't change.
+ /// </summary>
+ [CLSCompliant(false)]
+ public void* Pointer => _pointer;
+
+ /// <summary>
+ /// Frees the pinned handle and releases IPinnable.
+ /// </summary>
+ public void Dispose()
+ {
+ if (_handle.IsAllocated)
+ {
+ _handle.Free();
+ }
+
+ if (_pinnable != null)
+ {
+ _pinnable.Unpin();
+ _pinnable = null;
+ }
+
+ _pointer = null;
+ }
+
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/MemoryManager.cs b/src/System.Private.CoreLib/shared/System/Buffers/MemoryManager.cs
new file mode 100644
index 0000000000..acba3d2a94
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/MemoryManager.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime;
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers
+{
+ /// <summary>
+ /// Manager of <see cref="System.Memory{T}"/> that provides the implementation.
+ /// </summary>
+ public abstract class MemoryManager<T> : IMemoryOwner<T>, IPinnable
+ {
+ /// <summary>
+ /// Returns a <see cref="System.Memory{T}"/>.
+ /// </summary>
+ public virtual Memory<T> Memory => new Memory<T>(this, GetSpan().Length);
+
+ /// <summary>
+ /// Returns a span wrapping the underlying memory.
+ /// </summary>
+ public abstract Span<T> GetSpan();
+
+ /// <summary>
+ /// Returns a handle to the memory that has been pinned and hence its address can be taken.
+ /// </summary>
+ /// <param name="elementIndex">The offset to the element within the memory at which the returned <see cref="MemoryHandle"/> points to. (default = 0)</param>
+ public abstract MemoryHandle Pin(int elementIndex = 0);
+
+ /// <summary>
+ /// Lets the garbage collector know that the object is free to be moved now.
+ /// </summary>
+ public abstract void Unpin();
+
+ /// <summary>
+ /// Returns a <see cref="System.Memory{T}"/> for the current <see cref="MemoryManager{T}"/>.
+ /// </summary>
+ /// <param name="length">The element count in the memory, starting at offset 0.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected Memory<T> CreateMemory(int length) => new Memory<T>(this, length);
+
+ /// <summary>
+ /// Returns a <see cref="System.Memory{T}"/> for the current <see cref="MemoryManager{T}"/>.
+ /// </summary>
+ /// <param name="start">The offset to the element which the returned memory starts at.</param>
+ /// <param name="length">The element count in the memory, starting at element offset <paramref name="start"/>.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected Memory<T> CreateMemory(int start, int length) => new Memory<T>(this, start, length);
+
+ /// <summary>
+ /// Returns an array segment.
+ /// <remarks>Returns the default array segment if not overriden.</remarks>
+ /// </summary>
+ protected internal virtual bool TryGetArray(out ArraySegment<T> segment)
+ {
+ segment = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Implements IDisposable.
+ /// </summary>
+ void IDisposable.Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Clean up of any leftover managed and unmanaged resources.
+ /// </summary>
+ protected abstract void Dispose(bool disposing);
+
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/FormattingHelpers.CountDigits.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/FormattingHelpers.CountDigits.cs
new file mode 100644
index 0000000000..709ac4fba6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/FormattingHelpers.CountDigits.cs
@@ -0,0 +1,132 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers.Text
+{
+ internal static partial class FormattingHelpers
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int CountDigits(ulong value)
+ {
+ int digits = 1;
+ uint part;
+ if (value >= 10000000)
+ {
+ if (value >= 100000000000000)
+ {
+ part = (uint)(value / 100000000000000);
+ digits += 14;
+ }
+ else
+ {
+ part = (uint)(value / 10000000);
+ digits += 7;
+ }
+ }
+ else
+ {
+ part = (uint)value;
+ }
+
+ if (part < 10)
+ {
+ // no-op
+ }
+ else if (part < 100)
+ {
+ digits += 1;
+ }
+ else if (part < 1000)
+ {
+ digits += 2;
+ }
+ else if (part < 10000)
+ {
+ digits += 3;
+ }
+ else if (part < 100000)
+ {
+ digits += 4;
+ }
+ else if (part < 1000000)
+ {
+ digits += 5;
+ }
+ else
+ {
+ Debug.Assert(part < 10000000);
+ digits += 6;
+ }
+
+ return digits;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int CountDigits(uint value)
+ {
+ int digits = 1;
+ if (value >= 100000)
+ {
+ value = value / 100000;
+ digits += 5;
+ }
+
+ if (value < 10)
+ {
+ // no-op
+ }
+ else if (value < 100)
+ {
+ digits += 1;
+ }
+ else if (value < 1000)
+ {
+ digits += 2;
+ }
+ else if (value < 10000)
+ {
+ digits += 3;
+ }
+ else
+ {
+ Debug.Assert(value < 100000);
+ digits += 4;
+ }
+
+ return digits;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int CountHexDigits(ulong value)
+ {
+ // TODO: When x86 intrinsic support comes online, experiment with implementing this using lzcnt.
+ // return 16 - (int)((uint)Lzcnt.LeadingZeroCount(value | 1) >> 3);
+
+ int digits = 1;
+
+ if (value > 0xFFFFFFFF)
+ {
+ digits += 8;
+ value >>= 0x20;
+ }
+ if (value > 0xFFFF)
+ {
+ digits += 4;
+ value >>= 0x10;
+ }
+ if (value > 0xFF)
+ {
+ digits += 2;
+ value >>= 0x8;
+ }
+ if (value > 0xF)
+ digits++;
+
+ return digits;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
new file mode 100644
index 0000000000..96efb0d521
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
@@ -0,0 +1,496 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using Internal.Runtime.Augments;
+using Internal.Runtime.CompilerServices;
+
+namespace System.Buffers
+{
+ /// <summary>
+ /// Provides an ArrayPool implementation meant to be used as the singleton returned from ArrayPool.Shared.
+ /// </summary>
+ /// <remarks>
+ /// The implementation uses a tiered caching scheme, with a small per-thread cache for each array size, followed
+ /// by a cache per array size shared by all threads, split into per-core stacks meant to be used by threads
+ /// running on that core. Locks are used to protect each per-core stack, because a thread can migrate after
+ /// checking its processor number, because multiple threads could interleave on the same core, and because
+ /// a thread is allowed to check other core's buckets if its core's bucket is empty/full.
+ /// </remarks>
+ internal sealed partial class TlsOverPerCoreLockedStacksArrayPool<T> : ArrayPool<T>
+ {
+ // TODO: #7747: "Investigate optimizing ArrayPool heuristics"
+ // - Explore caching in TLS more than one array per size per thread, and moving stale buffers to the global queue.
+ // - Explore changing the size of each per-core bucket, potentially dynamically or based on other factors like array size.
+ // - Explore changing number of buckets and what sizes of arrays are cached.
+ // - Investigate whether false sharing is causing any issues, in particular on LockedStack's count and the contents of its array.
+ // ...
+
+ /// <summary>The number of buckets (array sizes) in the pool, one for each array length, starting from length 16.</summary>
+ private const int NumBuckets = 17; // Utilities.SelectBucketIndex(2*1024*1024)
+ /// <summary>Maximum number of per-core stacks to use per array size.</summary>
+ private const int MaxPerCorePerArraySizeStacks = 64; // selected to avoid needing to worry about processor groups
+ /// <summary>The maximum number of buffers to store in a bucket's global queue.</summary>
+ private const int MaxBuffersPerArraySizePerCore = 8;
+
+ /// <summary>The length of arrays stored in the corresponding indices in <see cref="_buckets"/> and <see cref="t_tlsBuckets"/>.</summary>
+ private readonly int[] _bucketArraySizes;
+ /// <summary>
+ /// An array of per-core array stacks. The slots are lazily initialized to avoid creating
+ /// lots of overhead for unused array sizes.
+ /// </summary>
+ private readonly PerCoreLockedStacks[] _buckets = new PerCoreLockedStacks[NumBuckets];
+ /// <summary>A per-thread array of arrays, to cache one array per array size per thread.</summary>
+ [ThreadStatic]
+ private static T[][] t_tlsBuckets;
+
+ private int _callbackCreated;
+
+ private readonly static bool s_trimBuffers = GetTrimBuffers();
+
+ /// <summary>
+ /// Used to keep track of all thread local buckets for trimming if needed
+ /// </summary>
+ private static readonly ConditionalWeakTable<T[][], object> s_allTlsBuckets = s_trimBuffers ? new ConditionalWeakTable<T[][], object>() : null;
+
+ /// <summary>Initialize the pool.</summary>
+ public TlsOverPerCoreLockedStacksArrayPool()
+ {
+ var sizes = new int[NumBuckets];
+ for (int i = 0; i < sizes.Length; i++)
+ {
+ sizes[i] = Utilities.GetMaxSizeForBucket(i);
+ }
+ _bucketArraySizes = sizes;
+ }
+
+ /// <summary>Allocate a new PerCoreLockedStacks and try to store it into the <see cref="_buckets"/> array.</summary>
+ private PerCoreLockedStacks CreatePerCoreLockedStacks(int bucketIndex)
+ {
+ var inst = new PerCoreLockedStacks();
+ return Interlocked.CompareExchange(ref _buckets[bucketIndex], inst, null) ?? inst;
+ }
+
+ /// <summary>Gets an ID for the pool to use with events.</summary>
+ private int Id => GetHashCode();
+
+ public override T[] Rent(int minimumLength)
+ {
+ // Arrays can't be smaller than zero. We allow requesting zero-length arrays (even though
+ // pooling such an array isn't valuable) as it's a valid length array, and we want the pool
+ // to be usable in general instead of using `new`, even for computed lengths.
+ if (minimumLength < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(minimumLength));
+ }
+ else if (minimumLength == 0)
+ {
+ // No need to log the empty array. Our pool is effectively infinite
+ // and we'll never allocate for rents and never store for returns.
+ return Array.Empty<T>();
+ }
+
+ ArrayPoolEventSource log = ArrayPoolEventSource.Log;
+ T[] buffer;
+
+ // Get the bucket number for the array length
+ int bucketIndex = Utilities.SelectBucketIndex(minimumLength);
+
+ // If the array could come from a bucket...
+ if (bucketIndex < _buckets.Length)
+ {
+ // First try to get it from TLS if possible.
+ T[][] tlsBuckets = t_tlsBuckets;
+ if (tlsBuckets != null)
+ {
+ buffer = tlsBuckets[bucketIndex];
+ if (buffer != null)
+ {
+ tlsBuckets[bucketIndex] = null;
+ if (log.IsEnabled())
+ {
+ log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex);
+ }
+ return buffer;
+ }
+ }
+
+ // We couldn't get a buffer from TLS, so try the global stack.
+ PerCoreLockedStacks b = _buckets[bucketIndex];
+ if (b != null)
+ {
+ buffer = b.TryPop();
+ if (buffer != null)
+ {
+ if (log.IsEnabled())
+ {
+ log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex);
+ }
+ return buffer;
+ }
+ }
+
+ // No buffer available. Allocate a new buffer with a size corresponding to the appropriate bucket.
+ buffer = new T[_bucketArraySizes[bucketIndex]];
+ }
+ else
+ {
+ // The request was for a size too large for the pool. Allocate an array of exactly the requested length.
+ // When it's returned to the pool, we'll simply throw it away.
+ buffer = new T[minimumLength];
+ }
+
+ if (log.IsEnabled())
+ {
+ int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer
+ log.BufferRented(bufferId, buffer.Length, Id, bucketId);
+ log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, bucketIndex >= _buckets.Length ?
+ ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize :
+ ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
+ }
+
+ return buffer;
+ }
+
+ public override void Return(T[] array, bool clearArray = false)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ // Determine with what bucket this array length is associated
+ int bucketIndex = Utilities.SelectBucketIndex(array.Length);
+
+ // If we can tell that the buffer was allocated (or empty), drop it. Otherwise, check if we have space in the pool.
+ if (bucketIndex < _buckets.Length)
+ {
+ // Clear the array if the user requests.
+ if (clearArray)
+ {
+ Array.Clear(array, 0, array.Length);
+ }
+
+ // Check to see if the buffer is the correct size for this bucket
+ if (array.Length != _bucketArraySizes[bucketIndex])
+ {
+ throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array));
+ }
+
+ // Write through the TLS bucket. If there weren't any buckets, create them
+ // and store this array into it. If there were, store this into it, and
+ // if there was a previous one there, push that to the global stack. This
+ // helps to keep LIFO access such that the most recently pushed stack will
+ // be in TLS and the first to be popped next.
+ T[][] tlsBuckets = t_tlsBuckets;
+ if (tlsBuckets == null)
+ {
+ t_tlsBuckets = tlsBuckets = new T[NumBuckets][];
+ tlsBuckets[bucketIndex] = array;
+ if (s_trimBuffers)
+ {
+ s_allTlsBuckets.Add(tlsBuckets, null);
+ if (Interlocked.Exchange(ref _callbackCreated, 1) != 1)
+ {
+ Gen2GcCallback.Register(Gen2GcCallbackFunc, this);
+ }
+ }
+ }
+ else
+ {
+ T[] prev = tlsBuckets[bucketIndex];
+ tlsBuckets[bucketIndex] = array;
+
+ if (prev != null)
+ {
+ PerCoreLockedStacks stackBucket = _buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex);
+ stackBucket.TryPush(prev);
+ }
+ }
+ }
+
+ // Log that the buffer was returned
+ ArrayPoolEventSource log = ArrayPoolEventSource.Log;
+ if (log.IsEnabled())
+ {
+ log.BufferReturned(array.GetHashCode(), array.Length, Id);
+ }
+ }
+
+ public bool Trim()
+ {
+ int milliseconds = Environment.TickCount;
+ MemoryPressure pressure = GetMemoryPressure();
+
+ ArrayPoolEventSource log = ArrayPoolEventSource.Log;
+ if (log.IsEnabled())
+ log.BufferTrimPoll(milliseconds, (int)pressure);
+
+ foreach (PerCoreLockedStacks bucket in _buckets)
+ {
+ bucket?.Trim((uint)milliseconds, Id, pressure, _bucketArraySizes);
+ }
+
+ if (pressure == MemoryPressure.High)
+ {
+ // Under high pressure, release all thread locals
+ if (log.IsEnabled())
+ {
+ foreach (KeyValuePair<T[][], object> tlsBuckets in s_allTlsBuckets)
+ {
+ T[][] buckets = tlsBuckets.Key;
+ for (int i = 0; i < buckets.Length; i++)
+ {
+ T[] buffer = Interlocked.Exchange(ref buckets[i], null);
+ if (buffer != null)
+ {
+ // As we don't want to take a perf hit in the rent path it
+ // is possible that a buffer could be rented as we "free" it.
+ log.BufferTrimmed(buffer.GetHashCode(), buffer.Length, Id);
+ }
+ }
+ }
+ }
+ else
+ {
+ foreach (KeyValuePair<T[][], object> tlsBuckets in s_allTlsBuckets)
+ {
+ T[][] buckets = tlsBuckets.Key;
+ Array.Clear(buckets, 0, buckets.Length);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// This is the static function that is called from the gen2 GC callback.
+ /// The input object is the instance we want the callback on.
+ /// </summary>
+ /// <remarks>
+ /// The reason that we make this function static and take the instance as a parameter is that
+ /// we would otherwise root the instance to the Gen2GcCallback object, leaking the instance even when
+ /// the application no longer needs it.
+ /// </remarks>
+ private static bool Gen2GcCallbackFunc(object target)
+ {
+ return ((TlsOverPerCoreLockedStacksArrayPool<T>)(target)).Trim();
+ }
+
+ private enum MemoryPressure
+ {
+ Low,
+ Medium,
+ High
+ }
+
+ private static MemoryPressure GetMemoryPressure()
+ {
+ const double HighPressureThreshold = .90; // Percent of GC memory pressure threshold we consider "high"
+ const double MediumPressureThreshold = .70; // Percent of GC memory pressure threshold we consider "medium"
+
+ GC.GetMemoryInfo(out uint threshold, out _, out uint lastLoad, out _, out _);
+ if (lastLoad >= threshold * HighPressureThreshold)
+ {
+ return MemoryPressure.High;
+ }
+ else if (lastLoad >= threshold * MediumPressureThreshold)
+ {
+ return MemoryPressure.Medium;
+ }
+ return MemoryPressure.Low;
+ }
+
+ private static bool GetTrimBuffers()
+ {
+ // Environment uses ArrayPool, so we have to hit the API directly.
+#if !CORECLR
+ // P/Invokes are different for CoreCLR/RT- for RT we'll not allow
+ // enabling/disabling for now.
+ return true;
+#else
+ return CLRConfig.GetBoolValueWithFallbacks("System.Buffers.ArrayPool.TrimShared", "DOTNET_SYSTEM_BUFFERS_ARRAYPOOL_TRIMSHARED", defaultValue: true);
+#endif
+ }
+
+ /// <summary>
+ /// Stores a set of stacks of arrays, with one stack per core.
+ /// </summary>
+ private sealed class PerCoreLockedStacks
+ {
+ /// <summary>The stacks.</summary>
+ private readonly LockedStack[] _perCoreStacks;
+
+ /// <summary>Initializes the stacks.</summary>
+ public PerCoreLockedStacks()
+ {
+ // Create the stacks. We create as many as there are processors, limited by our max.
+ var stacks = new LockedStack[Math.Min(Environment.ProcessorCount, MaxPerCorePerArraySizeStacks)];
+ for (int i = 0; i < stacks.Length; i++)
+ {
+ stacks[i] = new LockedStack();
+ }
+ _perCoreStacks = stacks;
+ }
+
+ /// <summary>Try to push the array into the stacks. If each is full when it's tested, the array will be dropped.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void TryPush(T[] array)
+ {
+ // Try to push on to the associated stack first. If that fails,
+ // round-robin through the other stacks.
+ LockedStack[] stacks = _perCoreStacks;
+ int index = RuntimeThread.GetCurrentProcessorId() % stacks.Length;
+ for (int i = 0; i < stacks.Length; i++)
+ {
+ if (stacks[index].TryPush(array)) return;
+ if (++index == stacks.Length) index = 0;
+ }
+ }
+
+ /// <summary>Try to get an array from the stacks. If each is empty when it's tested, null will be returned.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public T[] TryPop()
+ {
+ // Try to pop from the associated stack first. If that fails,
+ // round-robin through the other stacks.
+ T[] arr;
+ LockedStack[] stacks = _perCoreStacks;
+ int index = RuntimeThread.GetCurrentProcessorId() % stacks.Length;
+ for (int i = 0; i < stacks.Length; i++)
+ {
+ if ((arr = stacks[index].TryPop()) != null) return arr;
+ if (++index == stacks.Length) index = 0;
+ }
+ return null;
+ }
+
+ public bool Trim(uint tickCount, int id, MemoryPressure pressure, int[] bucketSizes)
+ {
+ LockedStack[] stacks = _perCoreStacks;
+ for (int i = 0; i < stacks.Length; i++)
+ {
+ stacks[i].Trim(tickCount, id, pressure, bucketSizes[i]);
+ }
+ return true;
+ }
+ }
+
+ /// <summary>Provides a simple stack of arrays, protected by a lock.</summary>
+ private sealed class LockedStack
+ {
+ private readonly T[][] _arrays = new T[MaxBuffersPerArraySizePerCore][];
+ private int _count;
+ private uint _firstStackItemMS;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryPush(T[] array)
+ {
+ bool enqueued = false;
+ Monitor.Enter(this);
+ if (_count < MaxBuffersPerArraySizePerCore)
+ {
+ if (s_trimBuffers && _count == 0)
+ {
+ // Stash the time the bottom of the stack was filled
+ _firstStackItemMS = (uint)Environment.TickCount;
+ }
+
+ _arrays[_count++] = array;
+ enqueued = true;
+ }
+ Monitor.Exit(this);
+ return enqueued;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public T[] TryPop()
+ {
+ T[] arr = null;
+ Monitor.Enter(this);
+ if (_count > 0)
+ {
+ arr = _arrays[--_count];
+ _arrays[_count] = null;
+ }
+ Monitor.Exit(this);
+ return arr;
+ }
+
+ public void Trim(uint tickCount, int id, MemoryPressure pressure, int bucketSize)
+ {
+ const uint StackTrimAfterMS = 60 * 1000; // Trim after 60 seconds for low/moderate pressure
+ const uint StackHighTrimAfterMS = 10 * 1000; // Trim after 10 seconds for high pressure
+ const uint StackRefreshMS = StackTrimAfterMS / 4; // Time bump after trimming (1/4 trim time)
+ const int StackLowTrimCount = 1; // Trim one item when pressure is low
+ const int StackMediumTrimCount = 2; // Trim two items when pressure is moderate
+ const int StackHighTrimCount = MaxBuffersPerArraySizePerCore; // Trim all items when pressure is high
+ const int StackLargeBucket = 16384; // If the bucket is larger than this we'll trim an extra when under high pressure
+ const int StackModerateTypeSize = 16; // If T is larger than this we'll trim an extra when under high pressure
+ const int StackLargeTypeSize = 32; // If T is larger than this we'll trim an extra (additional) when under high pressure
+
+ if (_count == 0)
+ return;
+ uint trimTicks = pressure == MemoryPressure.High ? StackHighTrimAfterMS : StackTrimAfterMS;
+
+ lock (this)
+ {
+ if (_count > 0 && _firstStackItemMS > tickCount || (tickCount - _firstStackItemMS) > trimTicks)
+ {
+ // We've wrapped the tick count or elapsed enough time since the
+ // first item went into the stack. Drop the top item so it can
+ // be collected and make the stack look a little newer.
+
+ ArrayPoolEventSource log = ArrayPoolEventSource.Log;
+ int trimCount = StackLowTrimCount;
+ switch (pressure)
+ {
+ case MemoryPressure.High:
+ trimCount = StackHighTrimCount;
+
+ // When pressure is high, aggressively trim larger arrays.
+ if (bucketSize > StackLargeBucket)
+ {
+ trimCount++;
+ }
+ if (Unsafe.SizeOf<T>() > StackModerateTypeSize)
+ {
+ trimCount++;
+ }
+ if (Unsafe.SizeOf<T>() > StackLargeTypeSize)
+ {
+ trimCount++;
+ }
+ break;
+ case MemoryPressure.Medium:
+ trimCount = StackMediumTrimCount;
+ break;
+ }
+
+ while (_count > 0 && trimCount-- > 0)
+ {
+ T[] array = _arrays[--_count];
+ _arrays[_count] = null;
+
+ if (log.IsEnabled())
+ {
+ log.BufferTrimmed(array.GetHashCode(), array.Length, id);
+ }
+ }
+
+ if (_count > 0 && _firstStackItemMS < uint.MaxValue - StackRefreshMS)
+ {
+ // Give the remaining items a bit more time
+ _firstStackItemMS += StackRefreshMS;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Utilities.cs b/src/System.Private.CoreLib/shared/System/Buffers/Utilities.cs
new file mode 100644
index 0000000000..b675100b03
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Utilities.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers
+{
+ internal static class Utilities
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static int SelectBucketIndex(int bufferSize)
+ {
+ Debug.Assert(bufferSize >= 0);
+
+ // bufferSize of 0 will underflow here, causing a huge
+ // index which the caller will discard because it is not
+ // within the bounds of the bucket array.
+ uint bitsRemaining = ((uint)bufferSize - 1) >> 4;
+
+ int poolIndex = 0;
+ if (bitsRemaining > 0xFFFF) { bitsRemaining >>= 16; poolIndex = 16; }
+ if (bitsRemaining > 0xFF) { bitsRemaining >>= 8; poolIndex += 8; }
+ if (bitsRemaining > 0xF) { bitsRemaining >>= 4; poolIndex += 4; }
+ if (bitsRemaining > 0x3) { bitsRemaining >>= 2; poolIndex += 2; }
+ if (bitsRemaining > 0x1) { bitsRemaining >>= 1; poolIndex += 1; }
+
+ return poolIndex + (int)bitsRemaining;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static int GetMaxSizeForBucket(int binIndex)
+ {
+ int maxSize = 16 << binIndex;
+ Debug.Assert(maxSize >= 0);
+ return maxSize;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Byte.cs b/src/System.Private.CoreLib/shared/System/Byte.cs
new file mode 100644
index 0000000000..31185f0ed0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Byte.cs
@@ -0,0 +1,284 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Byte : IComparable, IConvertible, IFormattable, IComparable<Byte>, IEquatable<Byte>, ISpanFormattable
+ {
+ private byte m_value; // Do not rename (binary serialization)
+
+ // The maximum value that a Byte may represent: 255.
+ public const byte MaxValue = (byte)0xFF;
+
+ // The minimum value that a Byte may represent: 0.
+ public const byte MinValue = 0;
+
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type byte, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (!(value is Byte))
+ {
+ throw new ArgumentException(SR.Arg_MustBeByte);
+ }
+
+ return m_value - (((Byte)value).m_value);
+ }
+
+ public int CompareTo(Byte value)
+ {
+ return m_value - value;
+ }
+
+ // Determines whether two Byte objects are equal.
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is Byte))
+ {
+ return false;
+ }
+ return m_value == ((Byte)obj).m_value;
+ }
+
+ [NonVersionable]
+ public bool Equals(Byte obj)
+ {
+ return m_value == obj;
+ }
+
+ // Gets a hash code for this instance.
+ public override int GetHashCode()
+ {
+ return m_value;
+ }
+
+ public static byte Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static byte Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static byte Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
+ }
+
+ // Parses an unsigned byte from a String in the given style. If
+ // a NumberFormatInfo isn't specified, the current culture's
+ // NumberFormatInfo is assumed.
+ public static byte Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static byte Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Parse(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ private static byte Parse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info)
+ {
+ int i = 0;
+ try
+ {
+ i = Number.ParseInt32(s, style, info);
+ }
+ catch (OverflowException e)
+ {
+ throw new OverflowException(SR.Overflow_Byte, e);
+ }
+
+ if (i < MinValue || i > MaxValue) throw new OverflowException(SR.Overflow_Byte);
+ return (byte)i;
+ }
+
+ public static bool TryParse(String s, out Byte result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, out byte result)
+ {
+ return TryParse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(String s, NumberStyles style, IFormatProvider provider, out Byte result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out byte result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return TryParse(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ private static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out Byte result)
+ {
+ result = 0;
+ int i;
+ if (!Number.TryParseInt32(s, style, info, out i))
+ {
+ return false;
+ }
+ if (i < MinValue || i > MaxValue)
+ {
+ return false;
+ }
+ result = (byte)i;
+ return true;
+ }
+
+ public override String ToString()
+ {
+ return Number.FormatInt32(m_value, null, null);
+ }
+
+ public String ToString(String format)
+ {
+ return Number.FormatInt32(m_value, format, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatInt32(m_value, null, provider);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return Number.FormatInt32(m_value, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ return Number.TryFormatInt32(m_value, format, provider, destination, out charsWritten);
+ }
+
+ //
+ // IConvertible implementation
+ //
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Byte;
+ }
+
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(m_value);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Byte", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/CLSCompliantAttribute.cs b/src/System.Private.CoreLib/shared/System/CLSCompliantAttribute.cs
new file mode 100644
index 0000000000..d895b5ac71
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/CLSCompliantAttribute.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Container for assemblies.
+**
+**
+=============================================================================*/
+
+namespace System
+{
+ [AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
+ public sealed class CLSCompliantAttribute : Attribute
+ {
+ private bool _compliant;
+
+ public CLSCompliantAttribute(bool isCompliant)
+ {
+ _compliant = isCompliant;
+ }
+ public bool IsCompliant
+ {
+ get
+ {
+ return _compliant;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Char.cs b/src/System.Private.CoreLib/shared/System/Char.cs
new file mode 100644
index 0000000000..d3ed1f5b68
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Char.cs
@@ -0,0 +1,1076 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: This is the value class representing a Unicode character
+** Char methods until we create this functionality.
+**
+**
+===========================================================*/
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Char : IComparable, IComparable<Char>, IEquatable<Char>, IConvertible
+ {
+ //
+ // Member Variables
+ //
+ private char m_value; // Do not rename (binary serialization)
+
+ //
+ // Public Constants
+ //
+ // The maximum character value.
+ public const char MaxValue = (char)0xFFFF;
+ // The minimum character value.
+ public const char MinValue = (char)0x00;
+
+ // Unicode category values from Unicode U+0000 ~ U+00FF. Store them in byte[] array to save space.
+ private static readonly byte[] s_categoryForLatin1 = {
+ (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, // 0000 - 0007
+ (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, // 0008 - 000F
+ (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, // 0010 - 0017
+ (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, // 0018 - 001F
+ (byte)UnicodeCategory.SpaceSeparator, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.CurrencySymbol, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.OtherPunctuation, // 0020 - 0027
+ (byte)UnicodeCategory.OpenPunctuation, (byte)UnicodeCategory.ClosePunctuation, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.MathSymbol, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.DashPunctuation, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.OtherPunctuation, // 0028 - 002F
+ (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.DecimalDigitNumber, // 0030 - 0037
+ (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.DecimalDigitNumber, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.MathSymbol, (byte)UnicodeCategory.MathSymbol, (byte)UnicodeCategory.MathSymbol, (byte)UnicodeCategory.OtherPunctuation, // 0038 - 003F
+ (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, // 0040 - 0047
+ (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, // 0048 - 004F
+ (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, // 0050 - 0057
+ (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.OpenPunctuation, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.ClosePunctuation, (byte)UnicodeCategory.ModifierSymbol, (byte)UnicodeCategory.ConnectorPunctuation, // 0058 - 005F
+ (byte)UnicodeCategory.ModifierSymbol, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, // 0060 - 0067
+ (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, // 0068 - 006F
+ (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, // 0070 - 0077
+ (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.OpenPunctuation, (byte)UnicodeCategory.MathSymbol, (byte)UnicodeCategory.ClosePunctuation, (byte)UnicodeCategory.MathSymbol, (byte)UnicodeCategory.Control, // 0078 - 007F
+ (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, // 0080 - 0087
+ (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, // 0088 - 008F
+ (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, // 0090 - 0097
+ (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, (byte)UnicodeCategory.Control, // 0098 - 009F
+ (byte)UnicodeCategory.SpaceSeparator, (byte)UnicodeCategory.OtherPunctuation, (byte)UnicodeCategory.CurrencySymbol, (byte)UnicodeCategory.CurrencySymbol, (byte)UnicodeCategory.CurrencySymbol, (byte)UnicodeCategory.CurrencySymbol, (byte)UnicodeCategory.OtherSymbol, (byte)UnicodeCategory.OtherSymbol, // 00A0 - 00A7
+ (byte)UnicodeCategory.ModifierSymbol, (byte)UnicodeCategory.OtherSymbol, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.InitialQuotePunctuation, (byte)UnicodeCategory.MathSymbol, (byte)UnicodeCategory.DashPunctuation, (byte)UnicodeCategory.OtherSymbol, (byte)UnicodeCategory.ModifierSymbol, // 00A8 - 00AF
+ (byte)UnicodeCategory.OtherSymbol, (byte)UnicodeCategory.MathSymbol, (byte)UnicodeCategory.OtherNumber, (byte)UnicodeCategory.OtherNumber, (byte)UnicodeCategory.ModifierSymbol, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.OtherSymbol, (byte)UnicodeCategory.OtherPunctuation, // 00B0 - 00B7
+ (byte)UnicodeCategory.ModifierSymbol, (byte)UnicodeCategory.OtherNumber, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.FinalQuotePunctuation, (byte)UnicodeCategory.OtherNumber, (byte)UnicodeCategory.OtherNumber, (byte)UnicodeCategory.OtherNumber, (byte)UnicodeCategory.OtherPunctuation, // 00B8 - 00BF
+ (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, // 00C0 - 00C7
+ (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, // 00C8 - 00CF
+ (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.MathSymbol, // 00D0 - 00D7
+ (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.UppercaseLetter, (byte)UnicodeCategory.LowercaseLetter, // 00D8 - 00DF
+ (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, // 00E0 - 00E7
+ (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, // 00E8 - 00EF
+ (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.MathSymbol, // 00F0 - 00F7
+ (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, (byte)UnicodeCategory.LowercaseLetter, // 00F8 - 00FF
+ };
+
+ // Return true for all characters below or equal U+00ff, which is ASCII + Latin-1 Supplement.
+ private static bool IsLatin1(char ch)
+ {
+ return (ch <= '\x00ff');
+ }
+
+ // Return true for all characters below or equal U+007f, which is ASCII.
+ private static bool IsAscii(char ch)
+ {
+ return (ch <= '\x007f');
+ }
+
+ // Return the Unicode category for Unicode character <= 0x00ff.
+ private static UnicodeCategory GetLatin1UnicodeCategory(char ch)
+ {
+ Debug.Assert(IsLatin1(ch), "Char.GetLatin1UnicodeCategory(): ch should be <= 007f");
+ return (UnicodeCategory)(s_categoryForLatin1[(int)ch]);
+ }
+
+ //
+ // Private Constants
+ //
+
+ //
+ // Overriden Instance Methods
+ //
+
+ // Calculate a hashcode for a 2 byte Unicode character.
+ public override int GetHashCode()
+ {
+ return (int)m_value | ((int)m_value << 16);
+ }
+
+ // Used for comparing two boxed Char objects.
+ //
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is Char))
+ {
+ return false;
+ }
+ return (m_value == ((Char)obj).m_value);
+ }
+
+ [System.Runtime.Versioning.NonVersionable]
+ public bool Equals(Char obj)
+ {
+ return m_value == obj;
+ }
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type Char, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (!(value is Char))
+ {
+ throw new ArgumentException(SR.Arg_MustBeChar);
+ }
+
+ return (m_value - ((Char)value).m_value);
+ }
+
+ public int CompareTo(Char value)
+ {
+ return (m_value - value);
+ }
+
+ // Overrides System.Object.ToString.
+ public override String ToString()
+ {
+ return Char.ToString(m_value);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Char.ToString(m_value);
+ }
+
+ //
+ // Formatting Methods
+ //
+
+ /*===================================ToString===================================
+ **This static methods takes a character and returns the String representation of it.
+ ==============================================================================*/
+ // Provides a string representation of a character.
+ public static string ToString(char c) => string.CreateFromChar(c);
+
+ public static char Parse(String s)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+
+ if (s.Length != 1)
+ {
+ throw new FormatException(SR.Format_NeedSingleChar);
+ }
+ return s[0];
+ }
+
+ public static bool TryParse(String s, out Char result)
+ {
+ result = '\0';
+ if (s == null)
+ {
+ return false;
+ }
+ if (s.Length != 1)
+ {
+ return false;
+ }
+ result = s[0];
+ return true;
+ }
+
+ //
+ // Static Methods
+ //
+ /*=================================ISDIGIT======================================
+ **A wrapper for Char. Returns a boolean indicating whether **
+ **character c is considered to be a digit. **
+ ==============================================================================*/
+ // Determines whether a character is a digit.
+ public static bool IsDigit(char c)
+ {
+ if (IsLatin1(c))
+ {
+ return (c >= '0' && c <= '9');
+ }
+ return (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber);
+ }
+
+
+ /*=================================CheckLetter=====================================
+ ** Check if the specified UnicodeCategory belongs to the letter categories.
+ ==============================================================================*/
+ internal static bool CheckLetter(UnicodeCategory uc)
+ {
+ switch (uc)
+ {
+ case (UnicodeCategory.UppercaseLetter):
+ case (UnicodeCategory.LowercaseLetter):
+ case (UnicodeCategory.TitlecaseLetter):
+ case (UnicodeCategory.ModifierLetter):
+ case (UnicodeCategory.OtherLetter):
+ return (true);
+ }
+ return (false);
+ }
+
+ /*=================================ISLETTER=====================================
+ **A wrapper for Char. Returns a boolean indicating whether **
+ **character c is considered to be a letter. **
+ ==============================================================================*/
+ // Determines whether a character is a letter.
+ public static bool IsLetter(char c)
+ {
+ if (IsLatin1(c))
+ {
+ if (IsAscii(c))
+ {
+ c |= (char)0x20;
+ return ((c >= 'a' && c <= 'z'));
+ }
+ return (CheckLetter(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckLetter(CharUnicodeInfo.GetUnicodeCategory(c)));
+ }
+
+ private static bool IsWhiteSpaceLatin1(char c)
+ {
+ // There are characters which belong to UnicodeCategory.Control but are considered as white spaces.
+ // We use code point comparisons for these characters here as a temporary fix.
+
+ // U+0009 = <control> HORIZONTAL TAB
+ // U+000a = <control> LINE FEED
+ // U+000b = <control> VERTICAL TAB
+ // U+000c = <contorl> FORM FEED
+ // U+000d = <control> CARRIAGE RETURN
+ // U+0085 = <control> NEXT LINE
+ // U+00a0 = NO-BREAK SPACE
+ return
+ c == ' ' ||
+ (uint)(c - '\x0009') <= ('\x000d' - '\x0009') || // (c >= '\x0009' && c <= '\x000d')
+ c == '\x00a0' ||
+ c == '\x0085';
+ }
+
+ /*===============================ISWHITESPACE===================================
+ **A wrapper for Char. Returns a boolean indicating whether **
+ **character c is considered to be a whitespace character. **
+ ==============================================================================*/
+ // Determines whether a character is whitespace.
+ public static bool IsWhiteSpace(char c)
+ {
+ if (IsLatin1(c))
+ {
+ return (IsWhiteSpaceLatin1(c));
+ }
+ return CharUnicodeInfo.IsWhiteSpace(c);
+ }
+
+
+ /*===================================IsUpper====================================
+ **Arguments: c -- the characater to be checked.
+ **Returns: True if c is an uppercase character.
+ ==============================================================================*/
+ // Determines whether a character is upper-case.
+ public static bool IsUpper(char c)
+ {
+ if (IsLatin1(c))
+ {
+ if (IsAscii(c))
+ {
+ return (c >= 'A' && c <= 'Z');
+ }
+ return (GetLatin1UnicodeCategory(c) == UnicodeCategory.UppercaseLetter);
+ }
+ return (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.UppercaseLetter);
+ }
+
+ /*===================================IsLower====================================
+ **Arguments: c -- the characater to be checked.
+ **Returns: True if c is an lowercase character.
+ ==============================================================================*/
+ // Determines whether a character is lower-case.
+ public static bool IsLower(char c)
+ {
+ if (IsLatin1(c))
+ {
+ if (IsAscii(c))
+ {
+ return (c >= 'a' && c <= 'z');
+ }
+ return (GetLatin1UnicodeCategory(c) == UnicodeCategory.LowercaseLetter);
+ }
+ return (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.LowercaseLetter);
+ }
+
+ internal static bool CheckPunctuation(UnicodeCategory uc)
+ {
+ switch (uc)
+ {
+ case UnicodeCategory.ConnectorPunctuation:
+ case UnicodeCategory.DashPunctuation:
+ case UnicodeCategory.OpenPunctuation:
+ case UnicodeCategory.ClosePunctuation:
+ case UnicodeCategory.InitialQuotePunctuation:
+ case UnicodeCategory.FinalQuotePunctuation:
+ case UnicodeCategory.OtherPunctuation:
+ return (true);
+ }
+ return (false);
+ }
+
+
+ /*================================IsPunctuation=================================
+ **Arguments: c -- the characater to be checked.
+ **Returns: True if c is an punctuation mark
+ ==============================================================================*/
+ // Determines whether a character is a punctuation mark.
+ public static bool IsPunctuation(char c)
+ {
+ if (IsLatin1(c))
+ {
+ return (CheckPunctuation(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckPunctuation(CharUnicodeInfo.GetUnicodeCategory(c)));
+ }
+
+ /*=================================CheckLetterOrDigit=====================================
+ ** Check if the specified UnicodeCategory belongs to the letter or digit categories.
+ ==============================================================================*/
+ internal static bool CheckLetterOrDigit(UnicodeCategory uc)
+ {
+ switch (uc)
+ {
+ case UnicodeCategory.UppercaseLetter:
+ case UnicodeCategory.LowercaseLetter:
+ case UnicodeCategory.TitlecaseLetter:
+ case UnicodeCategory.ModifierLetter:
+ case UnicodeCategory.OtherLetter:
+ case UnicodeCategory.DecimalDigitNumber:
+ return (true);
+ }
+ return (false);
+ }
+
+ // Determines whether a character is a letter or a digit.
+ public static bool IsLetterOrDigit(char c)
+ {
+ if (IsLatin1(c))
+ {
+ return (CheckLetterOrDigit(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckLetterOrDigit(CharUnicodeInfo.GetUnicodeCategory(c)));
+ }
+
+ /*===================================ToUpper====================================
+ **
+ ==============================================================================*/
+ // Converts a character to upper-case for the specified culture.
+ // <;<;Not fully implemented>;>;
+ public static char ToUpper(char c, CultureInfo culture)
+ {
+ if (culture == null)
+ throw new ArgumentNullException(nameof(culture));
+ return culture.TextInfo.ToUpper(c);
+ }
+
+ /*=================================TOUPPER======================================
+ **A wrapper for Char.toUpperCase. Converts character c to its **
+ **uppercase equivalent. If c is already an uppercase character or is not an **
+ **alphabetic, nothing happens. **
+ ==============================================================================*/
+ // Converts a character to upper-case for the default culture.
+ //
+ public static char ToUpper(char c)
+ {
+ return CultureInfo.CurrentCulture.TextInfo.ToUpper(c);
+ }
+
+
+ // Converts a character to upper-case for invariant culture.
+ public static char ToUpperInvariant(char c)
+ {
+ return CultureInfo.InvariantCulture.TextInfo.ToUpper(c);
+ }
+
+
+ /*===================================ToLower====================================
+ **
+ ==============================================================================*/
+ // Converts a character to lower-case for the specified culture.
+ // <;<;Not fully implemented>;>;
+ public static char ToLower(char c, CultureInfo culture)
+ {
+ if (culture == null)
+ throw new ArgumentNullException(nameof(culture));
+ return culture.TextInfo.ToLower(c);
+ }
+
+ /*=================================TOLOWER======================================
+ **A wrapper for Char.toLowerCase. Converts character c to its **
+ **lowercase equivalent. If c is already a lowercase character or is not an **
+ **alphabetic, nothing happens. **
+ ==============================================================================*/
+ // Converts a character to lower-case for the default culture.
+ public static char ToLower(char c)
+ {
+ return CultureInfo.CurrentCulture.TextInfo.ToLower(c);
+ }
+
+
+ // Converts a character to lower-case for invariant culture.
+ public static char ToLowerInvariant(char c)
+ {
+ return CultureInfo.InvariantCulture.TextInfo.ToLower(c);
+ }
+
+
+ //
+ // IConvertible implementation
+ //
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Char;
+ }
+
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Char", "Boolean"));
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Char", "Single"));
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Char", "Double"));
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Char", "Decimal"));
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Char", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ public static bool IsControl(char c)
+ {
+ if (IsLatin1(c))
+ {
+ return (GetLatin1UnicodeCategory(c) == UnicodeCategory.Control);
+ }
+ return (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.Control);
+ }
+
+ public static bool IsControl(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ return (GetLatin1UnicodeCategory(c) == UnicodeCategory.Control);
+ }
+ return (CharUnicodeInfo.GetUnicodeCategory(s, index) == UnicodeCategory.Control);
+ }
+
+
+ public static bool IsDigit(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ return (c >= '0' && c <= '9');
+ }
+ return (CharUnicodeInfo.GetUnicodeCategory(s, index) == UnicodeCategory.DecimalDigitNumber);
+ }
+
+ public static bool IsLetter(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ if (IsAscii(c))
+ {
+ c |= (char)0x20;
+ return ((c >= 'a' && c <= 'z'));
+ }
+ return (CheckLetter(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckLetter(CharUnicodeInfo.GetUnicodeCategory(s, index)));
+ }
+
+ public static bool IsLetterOrDigit(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ return CheckLetterOrDigit(GetLatin1UnicodeCategory(c));
+ }
+ return CheckLetterOrDigit(CharUnicodeInfo.GetUnicodeCategory(s, index));
+ }
+
+ public static bool IsLower(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ if (IsAscii(c))
+ {
+ return (c >= 'a' && c <= 'z');
+ }
+ return (GetLatin1UnicodeCategory(c) == UnicodeCategory.LowercaseLetter);
+ }
+
+ return (CharUnicodeInfo.GetUnicodeCategory(s, index) == UnicodeCategory.LowercaseLetter);
+ }
+
+ /*=================================CheckNumber=====================================
+ ** Check if the specified UnicodeCategory belongs to the number categories.
+ ==============================================================================*/
+
+ internal static bool CheckNumber(UnicodeCategory uc)
+ {
+ switch (uc)
+ {
+ case (UnicodeCategory.DecimalDigitNumber):
+ case (UnicodeCategory.LetterNumber):
+ case (UnicodeCategory.OtherNumber):
+ return (true);
+ }
+ return (false);
+ }
+
+ public static bool IsNumber(char c)
+ {
+ if (IsLatin1(c))
+ {
+ if (IsAscii(c))
+ {
+ return (c >= '0' && c <= '9');
+ }
+ return (CheckNumber(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckNumber(CharUnicodeInfo.GetUnicodeCategory(c)));
+ }
+
+ public static bool IsNumber(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ if (IsAscii(c))
+ {
+ return (c >= '0' && c <= '9');
+ }
+ return (CheckNumber(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckNumber(CharUnicodeInfo.GetUnicodeCategory(s, index)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsPunctuation
+ //
+ // Determines if the given character is a punctuation character.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ public static bool IsPunctuation(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ return (CheckPunctuation(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckPunctuation(CharUnicodeInfo.GetUnicodeCategory(s, index)));
+ }
+
+
+ /*================================= CheckSeparator ============================
+ ** Check if the specified UnicodeCategory belongs to the seprator categories.
+ ==============================================================================*/
+
+ internal static bool CheckSeparator(UnicodeCategory uc)
+ {
+ switch (uc)
+ {
+ case UnicodeCategory.SpaceSeparator:
+ case UnicodeCategory.LineSeparator:
+ case UnicodeCategory.ParagraphSeparator:
+ return (true);
+ }
+ return (false);
+ }
+
+ private static bool IsSeparatorLatin1(char c)
+ {
+ // U+00a0 = NO-BREAK SPACE
+ // There is no LineSeparator or ParagraphSeparator in Latin 1 range.
+ return (c == '\x0020' || c == '\x00a0');
+ }
+
+ public static bool IsSeparator(char c)
+ {
+ if (IsLatin1(c))
+ {
+ return (IsSeparatorLatin1(c));
+ }
+ return (CheckSeparator(CharUnicodeInfo.GetUnicodeCategory(c)));
+ }
+
+ public static bool IsSeparator(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ return (IsSeparatorLatin1(c));
+ }
+ return (CheckSeparator(CharUnicodeInfo.GetUnicodeCategory(s, index)));
+ }
+
+ public static bool IsSurrogate(char c)
+ {
+ return (c >= HIGH_SURROGATE_START && c <= LOW_SURROGATE_END);
+ }
+
+ public static bool IsSurrogate(String s, int index)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ return (IsSurrogate(s[index]));
+ }
+
+ /*================================= CheckSymbol ============================
+ ** Check if the specified UnicodeCategory belongs to the symbol categories.
+ ==============================================================================*/
+
+ internal static bool CheckSymbol(UnicodeCategory uc)
+ {
+ switch (uc)
+ {
+ case (UnicodeCategory.MathSymbol):
+ case (UnicodeCategory.CurrencySymbol):
+ case (UnicodeCategory.ModifierSymbol):
+ case (UnicodeCategory.OtherSymbol):
+ return (true);
+ }
+ return (false);
+ }
+
+ public static bool IsSymbol(char c)
+ {
+ if (IsLatin1(c))
+ {
+ return (CheckSymbol(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckSymbol(CharUnicodeInfo.GetUnicodeCategory(c)));
+ }
+
+ public static bool IsSymbol(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ return (CheckSymbol(GetLatin1UnicodeCategory(c)));
+ }
+ return (CheckSymbol(CharUnicodeInfo.GetUnicodeCategory(s, index)));
+ }
+
+
+ public static bool IsUpper(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ char c = s[index];
+ if (IsLatin1(c))
+ {
+ if (IsAscii(c))
+ {
+ return (c >= 'A' && c <= 'Z');
+ }
+ return (GetLatin1UnicodeCategory(c) == UnicodeCategory.UppercaseLetter);
+ }
+
+ return (CharUnicodeInfo.GetUnicodeCategory(s, index) == UnicodeCategory.UppercaseLetter);
+ }
+
+ public static bool IsWhiteSpace(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ if (IsLatin1(s[index]))
+ {
+ return IsWhiteSpaceLatin1(s[index]);
+ }
+
+ return CharUnicodeInfo.IsWhiteSpace(s, index);
+ }
+
+ public static UnicodeCategory GetUnicodeCategory(char c)
+ {
+ if (IsLatin1(c))
+ {
+ return (GetLatin1UnicodeCategory(c));
+ }
+ return CharUnicodeInfo.GetUnicodeCategory((int)c);
+ }
+
+ public static UnicodeCategory GetUnicodeCategory(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ if (IsLatin1(s[index]))
+ {
+ return (GetLatin1UnicodeCategory(s[index]));
+ }
+ return CharUnicodeInfo.InternalGetUnicodeCategory(s, index);
+ }
+
+ public static double GetNumericValue(char c)
+ {
+ return CharUnicodeInfo.GetNumericValue(c);
+ }
+
+ public static double GetNumericValue(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ return CharUnicodeInfo.GetNumericValue(s, index);
+ }
+
+
+ /*================================= IsHighSurrogate ============================
+ ** Check if a char is a high surrogate.
+ ==============================================================================*/
+ public static bool IsHighSurrogate(char c)
+ {
+ return ((c >= CharUnicodeInfo.HIGH_SURROGATE_START) && (c <= CharUnicodeInfo.HIGH_SURROGATE_END));
+ }
+
+ public static bool IsHighSurrogate(String s, int index)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+ if (index < 0 || index >= s.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ return (IsHighSurrogate(s[index]));
+ }
+
+ /*================================= IsLowSurrogate ============================
+ ** Check if a char is a low surrogate.
+ ==============================================================================*/
+ public static bool IsLowSurrogate(char c)
+ {
+ return ((c >= CharUnicodeInfo.LOW_SURROGATE_START) && (c <= CharUnicodeInfo.LOW_SURROGATE_END));
+ }
+
+ public static bool IsLowSurrogate(String s, int index)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+ if (index < 0 || index >= s.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ return (IsLowSurrogate(s[index]));
+ }
+
+ /*================================= IsSurrogatePair ============================
+ ** Check if the string specified by the index starts with a surrogate pair.
+ ==============================================================================*/
+ public static bool IsSurrogatePair(String s, int index)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+ if (index < 0 || index >= s.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ if (index + 1 < s.Length)
+ {
+ return (IsSurrogatePair(s[index], s[index + 1]));
+ }
+ return (false);
+ }
+
+ public static bool IsSurrogatePair(char highSurrogate, char lowSurrogate)
+ {
+ return ((highSurrogate >= CharUnicodeInfo.HIGH_SURROGATE_START && highSurrogate <= CharUnicodeInfo.HIGH_SURROGATE_END) &&
+ (lowSurrogate >= CharUnicodeInfo.LOW_SURROGATE_START && lowSurrogate <= CharUnicodeInfo.LOW_SURROGATE_END));
+ }
+
+ internal const int UNICODE_PLANE00_END = 0x00ffff;
+ // The starting codepoint for Unicode plane 1. Plane 1 contains 0x010000 ~ 0x01ffff.
+ internal const int UNICODE_PLANE01_START = 0x10000;
+ // The end codepoint for Unicode plane 16. This is the maximum code point value allowed for Unicode.
+ // Plane 16 contains 0x100000 ~ 0x10ffff.
+ internal const int UNICODE_PLANE16_END = 0x10ffff;
+
+ internal const int HIGH_SURROGATE_START = 0x00d800;
+ internal const int LOW_SURROGATE_END = 0x00dfff;
+
+
+
+ /*================================= ConvertFromUtf32 ============================
+ ** Convert an UTF32 value into a surrogate pair.
+ ==============================================================================*/
+
+ public static String ConvertFromUtf32(int utf32)
+ {
+ // For UTF32 values from U+00D800 ~ U+00DFFF, we should throw. They
+ // are considered as irregular code unit sequence, but they are not illegal.
+ if ((utf32 < 0 || utf32 > UNICODE_PLANE16_END) || (utf32 >= HIGH_SURROGATE_START && utf32 <= LOW_SURROGATE_END))
+ {
+ throw new ArgumentOutOfRangeException(nameof(utf32), SR.ArgumentOutOfRange_InvalidUTF32);
+ }
+
+ if (utf32 < UNICODE_PLANE01_START)
+ {
+ // This is a BMP character.
+ return (Char.ToString((char)utf32));
+ }
+
+ unsafe
+ {
+ // This is a supplementary character. Convert it to a surrogate pair in UTF-16.
+ utf32 -= UNICODE_PLANE01_START;
+ uint surrogate = 0; // allocate 2 chars worth of stack space
+ char* address = (char*)&surrogate;
+ address[0] = (char)((utf32 / 0x400) + (int)CharUnicodeInfo.HIGH_SURROGATE_START);
+ address[1] = (char)((utf32 % 0x400) + (int)CharUnicodeInfo.LOW_SURROGATE_START);
+ return new string(address, 0, 2);
+ }
+ }
+
+
+ /*=============================ConvertToUtf32===================================
+ ** Convert a surrogate pair to UTF32 value
+ ==============================================================================*/
+
+ public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
+ {
+ if (!IsHighSurrogate(highSurrogate))
+ {
+ throw new ArgumentOutOfRangeException(nameof(highSurrogate), SR.ArgumentOutOfRange_InvalidHighSurrogate);
+ }
+ if (!IsLowSurrogate(lowSurrogate))
+ {
+ throw new ArgumentOutOfRangeException(nameof(lowSurrogate), SR.ArgumentOutOfRange_InvalidLowSurrogate);
+ }
+ return (((highSurrogate - CharUnicodeInfo.HIGH_SURROGATE_START) * 0x400) + (lowSurrogate - CharUnicodeInfo.LOW_SURROGATE_START) + UNICODE_PLANE01_START);
+ }
+
+ /*=============================ConvertToUtf32===================================
+ ** Convert a character or a surrogate pair starting at index of the specified string
+ ** to UTF32 value.
+ ** The char pointed by index should be a surrogate pair or a BMP character.
+ ** This method throws if a high-surrogate is not followed by a low surrogate.
+ ** This method throws if a low surrogate is seen without preceding a high-surrogate.
+ ==============================================================================*/
+
+ public static int ConvertToUtf32(String s, int index)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+
+ if (index < 0 || index >= s.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+ // Check if the character at index is a high surrogate.
+ int temp1 = (int)s[index] - CharUnicodeInfo.HIGH_SURROGATE_START;
+ if (temp1 >= 0 && temp1 <= 0x7ff)
+ {
+ // Found a surrogate char.
+ if (temp1 <= 0x3ff)
+ {
+ // Found a high surrogate.
+ if (index < s.Length - 1)
+ {
+ int temp2 = (int)s[index + 1] - CharUnicodeInfo.LOW_SURROGATE_START;
+ if (temp2 >= 0 && temp2 <= 0x3ff)
+ {
+ // Found a low surrogate.
+ return ((temp1 * 0x400) + temp2 + UNICODE_PLANE01_START);
+ }
+ else
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidHighSurrogate, index), nameof(s));
+ }
+ }
+ else
+ {
+ // Found a high surrogate at the end of the string.
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidHighSurrogate, index), nameof(s));
+ }
+ }
+ else
+ {
+ // Find a low surrogate at the character pointed by index.
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidLowSurrogate, index), nameof(s));
+ }
+ }
+ // Not a high-surrogate or low-surrogate. Genereate the UTF32 value for the BMP characters.
+ return ((int)s[index]);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/CharEnumerator.cs b/src/System.Private.CoreLib/shared/System/CharEnumerator.cs
new file mode 100644
index 0000000000..ea9915a7c4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/CharEnumerator.cs
@@ -0,0 +1,80 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Enumerates the characters on a string. skips range
+** checks.
+**
+**
+============================================================*/
+
+using System.Collections;
+using System.Collections.Generic;
+
+namespace System
+{
+ public sealed class CharEnumerator : IEnumerator, IEnumerator<char>, IDisposable, ICloneable
+ {
+ private String _str;
+ private int _index;
+ private char _currentElement;
+
+ internal CharEnumerator(String str)
+ {
+ _str = str;
+ _index = -1;
+ }
+
+ public object Clone()
+ {
+ return MemberwiseClone();
+ }
+
+ public bool MoveNext()
+ {
+ if (_index < (_str.Length - 1))
+ {
+ _index++;
+ _currentElement = _str[_index];
+ return true;
+ }
+ else
+ _index = _str.Length;
+ return false;
+ }
+
+ public void Dispose()
+ {
+ if (_str != null)
+ _index = _str.Length;
+ _str = null;
+ }
+
+ Object IEnumerator.Current
+ {
+ get { return Current; }
+ }
+
+ public char Current
+ {
+ get
+ {
+ if (_index == -1)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
+ if (_index >= _str.Length)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
+ return _currentElement;
+ }
+ }
+
+ public void Reset()
+ {
+ _currentElement = (char)0;
+ _index = -1;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Comparer.cs b/src/System.Private.CoreLib/shared/System/Collections/Comparer.cs
new file mode 100644
index 0000000000..6fcedc09ab
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Comparer.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Purpose: Default IComparer implementation.
+**
+===========================================================*/
+
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System.Collections
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class Comparer : IComparer, ISerializable
+ {
+ private CompareInfo _compareInfo;
+
+ public static readonly Comparer Default = new Comparer(CultureInfo.CurrentCulture);
+ public static readonly Comparer DefaultInvariant = new Comparer(CultureInfo.InvariantCulture);
+
+ public Comparer(CultureInfo culture)
+ {
+ if (culture == null)
+ throw new ArgumentNullException(nameof(culture));
+
+ _compareInfo = culture.CompareInfo;
+ }
+
+ private Comparer(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ throw new ArgumentNullException(nameof(info));
+
+ _compareInfo = (CompareInfo)info.GetValue("CompareInfo", typeof(CompareInfo));
+ }
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ throw new ArgumentNullException(nameof(info));
+
+ info.AddValue("CompareInfo", _compareInfo);
+ }
+
+ // Compares two Objects by calling CompareTo.
+ // If a == b, 0 is returned.
+ // If a implements IComparable, a.CompareTo(b) is returned.
+ // If a doesn't implement IComparable and b does, -(b.CompareTo(a)) is returned.
+ // Otherwise an exception is thrown.
+ //
+ public int Compare(Object a, Object b)
+ {
+ if (a == b) return 0;
+ if (a == null) return -1;
+ if (b == null) return 1;
+
+ string sa = a as string;
+ string sb = b as string;
+ if (sa != null && sb != null)
+ return _compareInfo.Compare(sa, sb);
+
+ IComparable ia = a as IComparable;
+ if (ia != null)
+ return ia.CompareTo(b);
+
+ IComparable ib = b as IComparable;
+ if (ib != null)
+ return -ib.CompareTo(a);
+
+ throw new ArgumentException(SR.Argument_ImplementIComparable);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/CompatibleComparer.cs b/src/System.Private.CoreLib/shared/System/Collections/CompatibleComparer.cs
new file mode 100644
index 0000000000..587fd68391
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/CompatibleComparer.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#pragma warning disable 618 // obsolete types
+
+namespace System.Collections
+{
+ internal sealed class CompatibleComparer : IEqualityComparer
+ {
+ private readonly IHashCodeProvider _hcp;
+ private readonly IComparer _comparer;
+
+ internal CompatibleComparer(IHashCodeProvider hashCodeProvider, IComparer comparer)
+ {
+ _hcp = hashCodeProvider;
+ _comparer = comparer;
+ }
+
+ internal IHashCodeProvider HashCodeProvider => _hcp;
+
+ internal IComparer Comparer => _comparer;
+
+ public new bool Equals(object a, object b) => Compare(a, b) == 0;
+
+ public int Compare(object a, object b)
+ {
+ if (a == b)
+ return 0;
+ if (a == null)
+ return -1;
+ if (b == null)
+ return 1;
+
+ if (_comparer != null)
+ {
+ return _comparer.Compare(a, b);
+ }
+
+ IComparable ia = a as IComparable;
+ if (ia != null)
+ {
+ return ia.CompareTo(b);
+ }
+
+ throw new ArgumentException(SR.Argument_ImplementIComparable);
+ }
+
+ public int GetHashCode(object obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+
+ return _hcp != null ?
+ _hcp.GetHashCode(obj) :
+ obj.GetHashCode();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/DictionaryEntry.cs b/src/System.Private.CoreLib/shared/System/Collections/DictionaryEntry.cs
new file mode 100644
index 0000000000..3c1c0befa3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/DictionaryEntry.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+
+namespace System.Collections
+{
+ // A DictionaryEntry holds a key and a value from a dictionary.
+ // It is returned by IDictionaryEnumerator::GetEntry().
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct DictionaryEntry
+ {
+ private Object _key; // Do not rename (binary serialization)
+ private Object _value; // Do not rename (binary serialization)
+
+ // Constructs a new DictionaryEnumerator by setting the Key
+ // and Value fields appropriately.
+ public DictionaryEntry(Object key, Object value)
+ {
+ _key = key;
+ _value = value;
+ }
+
+ public Object Key
+ {
+ get
+ {
+ return _key;
+ }
+
+ set
+ {
+ _key = value;
+ }
+ }
+
+ public Object Value
+ {
+ get
+ {
+ return _value;
+ }
+
+ set
+ {
+ _value = value;
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Deconstruct(out object key, out object value)
+ {
+ key = Key;
+ value = Value;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs
new file mode 100644
index 0000000000..da5d8bb86b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs
@@ -0,0 +1,1624 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace System.Collections.Generic
+{
+ /// <summary>
+ /// Used internally to control behavior of insertion into a <see cref="Dictionary{TKey, TValue}"/>.
+ /// </summary>
+ internal enum InsertionBehavior : byte
+ {
+ /// <summary>
+ /// The default insertion behavior.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Specifies that an existing entry with the same key should be overwritten if encountered.
+ /// </summary>
+ OverwriteExisting = 1,
+
+ /// <summary>
+ /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown.
+ /// </summary>
+ ThrowOnExisting = 2
+ }
+
+ [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
+ [DebuggerDisplay("Count = {Count}")]
+ [Serializable]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>, ISerializable, IDeserializationCallback
+ {
+ private struct Entry
+ {
+ public int hashCode; // Lower 31 bits of hash code, -1 if unused
+ public int next; // Index of next entry, -1 if last
+ public TKey key; // Key of entry
+ public TValue value; // Value of entry
+ }
+
+ private int[] _buckets;
+ private Entry[] _entries;
+ private int _count;
+ private int _freeList;
+ private int _freeCount;
+ private int _version;
+ private IEqualityComparer<TKey> _comparer;
+ private KeyCollection _keys;
+ private ValueCollection _values;
+ private object _syncRoot;
+
+ // constants for serialization
+ private const string VersionName = "Version"; // Do not rename (binary serialization)
+ private const string HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length
+ private const string KeyValuePairsName = "KeyValuePairs"; // Do not rename (binary serialization)
+ private const string ComparerName = "Comparer"; // Do not rename (binary serialization)
+
+ public Dictionary() : this(0, null) { }
+
+ public Dictionary(int capacity) : this(capacity, null) { }
+
+ public Dictionary(IEqualityComparer<TKey> comparer) : this(0, comparer) { }
+
+ public Dictionary(int capacity, IEqualityComparer<TKey> comparer)
+ {
+ if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
+ if (capacity > 0) Initialize(capacity);
+ if (comparer != EqualityComparer<TKey>.Default)
+ {
+ _comparer = comparer;
+ }
+
+ if (typeof(TKey) == typeof(string) && _comparer == null)
+ {
+ // To start, move off default comparer for string which is randomised
+ _comparer = (IEqualityComparer<TKey>)NonRandomizedStringEqualityComparer.Default;
+ }
+ }
+
+ public Dictionary(IDictionary<TKey, TValue> dictionary) : this(dictionary, null) { }
+
+ public Dictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) :
+ this(dictionary != null ? dictionary.Count : 0, comparer)
+ {
+ if (dictionary == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
+ }
+
+ // It is likely that the passed-in dictionary is Dictionary<TKey,TValue>. When this is the case,
+ // avoid the enumerator allocation and overhead by looping through the entries array directly.
+ // We only do this when dictionary is Dictionary<TKey,TValue> and not a subclass, to maintain
+ // back-compat with subclasses that may have overridden the enumerator behavior.
+ if (dictionary.GetType() == typeof(Dictionary<TKey, TValue>))
+ {
+ Dictionary<TKey, TValue> d = (Dictionary<TKey, TValue>)dictionary;
+ int count = d._count;
+ Entry[] entries = d._entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ Add(entries[i].key, entries[i].value);
+ }
+ }
+ return;
+ }
+
+ foreach (KeyValuePair<TKey, TValue> pair in dictionary)
+ {
+ Add(pair.Key, pair.Value);
+ }
+ }
+
+ public Dictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, null) { }
+
+ public Dictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) :
+ this((collection as ICollection<KeyValuePair<TKey, TValue>>)?.Count ?? 0, comparer)
+ {
+ if (collection == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
+ }
+
+ foreach (KeyValuePair<TKey, TValue> pair in collection)
+ {
+ Add(pair.Key, pair.Value);
+ }
+ }
+
+ protected Dictionary(SerializationInfo info, StreamingContext context)
+ {
+ // We can't do anything with the keys and values until the entire graph has been deserialized
+ // and we have a resonable estimate that GetHashCode is not going to fail. For the time being,
+ // we'll just cache this. The graph is not valid until OnDeserialization has been called.
+ HashHelpers.SerializationInfoTable.Add(this, info);
+ }
+
+ public IEqualityComparer<TKey> Comparer
+ {
+ get
+ {
+ return (_comparer == null || _comparer is NonRandomizedStringEqualityComparer) ? EqualityComparer<TKey>.Default : _comparer;
+ }
+ }
+
+ public int Count
+ {
+ get { return _count - _freeCount; }
+ }
+
+ public KeyCollection Keys
+ {
+ get
+ {
+ if (_keys == null) _keys = new KeyCollection(this);
+ return _keys;
+ }
+ }
+
+ ICollection<TKey> IDictionary<TKey, TValue>.Keys
+ {
+ get
+ {
+ if (_keys == null) _keys = new KeyCollection(this);
+ return _keys;
+ }
+ }
+
+ IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
+ {
+ get
+ {
+ if (_keys == null) _keys = new KeyCollection(this);
+ return _keys;
+ }
+ }
+
+ public ValueCollection Values
+ {
+ get
+ {
+ if (_values == null) _values = new ValueCollection(this);
+ return _values;
+ }
+ }
+
+ ICollection<TValue> IDictionary<TKey, TValue>.Values
+ {
+ get
+ {
+ if (_values == null) _values = new ValueCollection(this);
+ return _values;
+ }
+ }
+
+ IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
+ {
+ get
+ {
+ if (_values == null) _values = new ValueCollection(this);
+ return _values;
+ }
+ }
+
+ public TValue this[TKey key]
+ {
+ get
+ {
+ int i = FindEntry(key);
+ if (i >= 0) return _entries[i].value;
+ ThrowHelper.ThrowKeyNotFoundException(key);
+ return default;
+ }
+ set
+ {
+ bool modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting);
+ Debug.Assert(modified);
+ }
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+ bool modified = TryInsert(key, value, InsertionBehavior.ThrowOnExisting);
+ Debug.Assert(modified); // If there was an existing key and the Add failed, an exception will already have been thrown.
+ }
+
+ void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair)
+ => Add(keyValuePair.Key, keyValuePair.Value);
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair)
+ {
+ int i = FindEntry(keyValuePair.Key);
+ if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, keyValuePair.Value))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair)
+ {
+ int i = FindEntry(keyValuePair.Key);
+ if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, keyValuePair.Value))
+ {
+ Remove(keyValuePair.Key);
+ return true;
+ }
+ return false;
+ }
+
+ public void Clear()
+ {
+ int count = _count;
+ if (count > 0)
+ {
+ Array.Clear(_buckets, 0, _buckets.Length);
+
+ _count = 0;
+ _freeList = -1;
+ _freeCount = 0;
+ Array.Clear(_entries, 0, count);
+ }
+ _version++;
+ }
+
+ public bool ContainsKey(TKey key)
+ => FindEntry(key) >= 0;
+
+ public bool ContainsValue(TValue value)
+ {
+ Entry[] entries = _entries;
+ if (value == null)
+ {
+ for (int i = 0; i < _count; i++)
+ {
+ if (entries[i].hashCode >= 0 && entries[i].value == null) return true;
+ }
+ }
+ else
+ {
+ if (default(TValue) != null)
+ {
+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
+ for (int i = 0; i < _count; i++)
+ {
+ if (entries[i].hashCode >= 0 && EqualityComparer<TValue>.Default.Equals(entries[i].value, value)) return true;
+ }
+ }
+ else
+ {
+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
+ // https://github.com/dotnet/coreclr/issues/17273
+ // So cache in a local rather than get EqualityComparer per loop iteration
+ EqualityComparer<TValue> defaultComparer = EqualityComparer<TValue>.Default;
+ for (int i = 0; i < _count; i++)
+ {
+ if (entries[i].hashCode >= 0 && defaultComparer.Equals(entries[i].value, value)) return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
+ {
+ if (array == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ }
+
+ if ((uint)index > (uint)array.Length)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (array.Length - index < Count)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+ }
+
+ int count = _count;
+ Entry[] entries = _entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ array[index++] = new KeyValuePair<TKey, TValue>(entries[i].key, entries[i].value);
+ }
+ }
+ }
+
+ public Enumerator GetEnumerator()
+ => new Enumerator(this, Enumerator.KeyValuePair);
+
+ IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
+ => new Enumerator(this, Enumerator.KeyValuePair);
+
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info);
+ }
+
+ info.AddValue(VersionName, _version);
+ info.AddValue(ComparerName, _comparer ?? EqualityComparer<TKey>.Default, typeof(IEqualityComparer<TKey>));
+ info.AddValue(HashSizeName, _buckets == null ? 0 : _buckets.Length); // This is the length of the bucket array
+
+ if (_buckets != null)
+ {
+ var array = new KeyValuePair<TKey, TValue>[Count];
+ CopyTo(array, 0);
+ info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair<TKey, TValue>[]));
+ }
+ }
+
+ private int FindEntry(TKey key)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+
+ int i = -1;
+ int[] buckets = _buckets;
+ Entry[] entries = _entries;
+ int collisionCount = 0;
+ if (buckets != null)
+ {
+ IEqualityComparer<TKey> comparer = _comparer;
+ if (comparer == null)
+ {
+ int hashCode = key.GetHashCode() & 0x7FFFFFFF;
+ // Value in _buckets is 1-based
+ i = buckets[hashCode % buckets.Length] - 1;
+ if (default(TKey) != null)
+ {
+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test in if to drop range check for following array access
+ if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)))
+ {
+ break;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= entries.Length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ else
+ {
+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
+ // https://github.com/dotnet/coreclr/issues/17273
+ // So cache in a local rather than get EqualityComparer per loop iteration
+ EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test in if to drop range check for following array access
+ if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key)))
+ {
+ break;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= entries.Length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ }
+ else
+ {
+ int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
+ // Value in _buckets is 1-based
+ i = buckets[hashCode % buckets.Length] - 1;
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test in if to drop range check for following array access
+ if ((uint)i >= (uint)entries.Length ||
+ (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)))
+ {
+ break;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= entries.Length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ }
+
+ return i;
+ }
+
+ private int Initialize(int capacity)
+ {
+ int size = HashHelpers.GetPrime(capacity);
+
+ _freeList = -1;
+ _buckets = new int[size];
+ _entries = new Entry[size];
+
+ return size;
+ }
+
+ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+
+ _version++;
+ if (_buckets == null)
+ {
+ Initialize(0);
+ }
+
+ Entry[] entries = _entries;
+ IEqualityComparer<TKey> comparer = _comparer;
+
+ int hashCode = ((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)) & 0x7FFFFFFF;
+
+ int collisionCount = 0;
+ ref int bucket = ref _buckets[hashCode % _buckets.Length];
+ // Value in _buckets is 1-based
+ int i = bucket - 1;
+
+ if (comparer == null)
+ {
+ if (default(TKey) != null)
+ {
+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test uint in if rather than loop condition to drop range check for following array access
+ if ((uint)i >= (uint)entries.Length)
+ {
+ break;
+ }
+
+ if (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key))
+ {
+ if (behavior == InsertionBehavior.OverwriteExisting)
+ {
+ entries[i].value = value;
+ return true;
+ }
+
+ if (behavior == InsertionBehavior.ThrowOnExisting)
+ {
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
+ }
+
+ return false;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= entries.Length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ else
+ {
+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
+ // https://github.com/dotnet/coreclr/issues/17273
+ // So cache in a local rather than get EqualityComparer per loop iteration
+ EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test uint in if rather than loop condition to drop range check for following array access
+ if ((uint)i >= (uint)entries.Length)
+ {
+ break;
+ }
+
+ if (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key))
+ {
+ if (behavior == InsertionBehavior.OverwriteExisting)
+ {
+ entries[i].value = value;
+ return true;
+ }
+
+ if (behavior == InsertionBehavior.ThrowOnExisting)
+ {
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
+ }
+
+ return false;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= entries.Length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ }
+ else
+ {
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test uint in if rather than loop condition to drop range check for following array access
+ if ((uint)i >= (uint)entries.Length)
+ {
+ break;
+ }
+
+ if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
+ {
+ if (behavior == InsertionBehavior.OverwriteExisting)
+ {
+ entries[i].value = value;
+ return true;
+ }
+
+ if (behavior == InsertionBehavior.ThrowOnExisting)
+ {
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
+ }
+
+ return false;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= entries.Length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+
+ }
+
+ bool updateFreeList = false;
+ int index;
+ if (_freeCount > 0)
+ {
+ index = _freeList;
+ updateFreeList = true;
+ _freeCount--;
+ }
+ else
+ {
+ int count = _count;
+ if (count == entries.Length)
+ {
+ Resize();
+ bucket = ref _buckets[hashCode % _buckets.Length];
+ }
+ index = count;
+ _count = count + 1;
+ entries = _entries;
+ }
+
+ ref Entry entry = ref entries[index];
+
+ if (updateFreeList)
+ {
+ _freeList = entry.next;
+ }
+ entry.hashCode = hashCode;
+ // Value in _buckets is 1-based
+ entry.next = bucket - 1;
+ entry.key = key;
+ entry.value = value;
+ // Value in _buckets is 1-based
+ bucket = index + 1;
+
+ // Value types never rehash
+ if (default(TKey) == null && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer)
+ {
+ // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
+ // i.e. EqualityComparer<string>.Default.
+ _comparer = null;
+ Resize(entries.Length, true);
+ }
+
+ return true;
+ }
+
+ public virtual void OnDeserialization(object sender)
+ {
+ HashHelpers.SerializationInfoTable.TryGetValue(this, out SerializationInfo siInfo);
+
+ if (siInfo == null)
+ {
+ // We can return immediately if this function is called twice.
+ // Note we remove the serialization info from the table at the end of this method.
+ return;
+ }
+
+ int realVersion = siInfo.GetInt32(VersionName);
+ int hashsize = siInfo.GetInt32(HashSizeName);
+ _comparer = (IEqualityComparer<TKey>)siInfo.GetValue(ComparerName, typeof(IEqualityComparer<TKey>));
+
+ if (hashsize != 0)
+ {
+ Initialize(hashsize);
+
+ KeyValuePair<TKey, TValue>[] array = (KeyValuePair<TKey, TValue>[])
+ siInfo.GetValue(KeyValuePairsName, typeof(KeyValuePair<TKey, TValue>[]));
+
+ if (array == null)
+ {
+ ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingKeys);
+ }
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ if (array[i].Key == null)
+ {
+ ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_NullKey);
+ }
+ Add(array[i].Key, array[i].Value);
+ }
+ }
+ else
+ {
+ _buckets = null;
+ }
+
+ _version = realVersion;
+ HashHelpers.SerializationInfoTable.Remove(this);
+ }
+
+ private void Resize()
+ => Resize(HashHelpers.ExpandPrime(_count), false);
+
+ private void Resize(int newSize, bool forceNewHashCodes)
+ {
+ // Value types never rehash
+ Debug.Assert(!forceNewHashCodes || default(TKey) == null);
+ Debug.Assert(newSize >= _entries.Length);
+
+ int[] buckets = new int[newSize];
+ Entry[] entries = new Entry[newSize];
+
+ int count = _count;
+ Array.Copy(_entries, 0, entries, 0, count);
+
+ if (default(TKey) == null && forceNewHashCodes)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ Debug.Assert(_comparer == null);
+ entries[i].hashCode = (entries[i].key.GetHashCode() & 0x7FFFFFFF);
+ }
+ }
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ int bucket = entries[i].hashCode % newSize;
+ // Value in _buckets is 1-based
+ entries[i].next = buckets[bucket] - 1;
+ // Value in _buckets is 1-based
+ buckets[bucket] = i + 1;
+ }
+ }
+
+ _buckets = buckets;
+ _entries = entries;
+ }
+
+ // The overload Remove(TKey key, out TValue value) is a copy of this method with one additional
+ // statement to copy the value for entry being removed into the output parameter.
+ // Code has been intentionally duplicated for performance reasons.
+ public bool Remove(TKey key)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+
+ if (_buckets != null)
+ {
+ int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
+ int bucket = hashCode % _buckets.Length;
+ int last = -1;
+ // Value in _buckets is 1-based
+ int i = _buckets[bucket] - 1;
+ while (i >= 0)
+ {
+ ref Entry entry = ref _entries[i];
+
+ if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
+ {
+ if (last < 0)
+ {
+ // Value in _buckets is 1-based
+ _buckets[bucket] = entry.next + 1;
+ }
+ else
+ {
+ _entries[last].next = entry.next;
+ }
+ entry.hashCode = -1;
+ entry.next = _freeList;
+
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
+ {
+ entry.key = default;
+ }
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>())
+ {
+ entry.value = default;
+ }
+ _freeList = i;
+ _freeCount++;
+ _version++;
+ return true;
+ }
+
+ last = i;
+ i = entry.next;
+ }
+ }
+ return false;
+ }
+
+ // This overload is a copy of the overload Remove(TKey key) with one additional
+ // statement to copy the value for entry being removed into the output parameter.
+ // Code has been intentionally duplicated for performance reasons.
+ public bool Remove(TKey key, out TValue value)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+
+ if (_buckets != null)
+ {
+ int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
+ int bucket = hashCode % _buckets.Length;
+ int last = -1;
+ // Value in _buckets is 1-based
+ int i = _buckets[bucket] - 1;
+ while (i >= 0)
+ {
+ ref Entry entry = ref _entries[i];
+
+ if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
+ {
+ if (last < 0)
+ {
+ // Value in _buckets is 1-based
+ _buckets[bucket] = entry.next + 1;
+ }
+ else
+ {
+ _entries[last].next = entry.next;
+ }
+
+ value = entry.value;
+
+ entry.hashCode = -1;
+ entry.next = _freeList;
+
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
+ {
+ entry.key = default;
+ }
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>())
+ {
+ entry.value = default;
+ }
+ _freeList = i;
+ _freeCount++;
+ _version++;
+ return true;
+ }
+
+ last = i;
+ i = entry.next;
+ }
+ }
+ value = default;
+ return false;
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ int i = FindEntry(key);
+ if (i >= 0)
+ {
+ value = _entries[i].value;
+ return true;
+ }
+ value = default;
+ return false;
+ }
+
+ public bool TryAdd(TKey key, TValue value)
+ => TryInsert(key, value, InsertionBehavior.None);
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
+
+ void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
+ => CopyTo(array, index);
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (array.Rank != 1)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ if (array.GetLowerBound(0) != 0)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
+ if ((uint)index > (uint)array.Length)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ if (array.Length - index < Count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+
+ if (array is KeyValuePair<TKey, TValue>[] pairs)
+ {
+ CopyTo(pairs, index);
+ }
+ else if (array is DictionaryEntry[] dictEntryArray)
+ {
+ Entry[] entries = _entries;
+ for (int i = 0; i < _count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value);
+ }
+ }
+ }
+ else
+ {
+ object[] objects = array as object[];
+ if (objects == null)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+
+ try
+ {
+ int count = _count;
+ Entry[] entries = _entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ objects[index++] = new KeyValuePair<TKey, TValue>(entries[i].key, entries[i].value);
+ }
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(this, Enumerator.KeyValuePair);
+
+ /// <summary>
+ /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
+ /// </summary>
+ public int EnsureCapacity(int capacity)
+ {
+ if (capacity < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
+ int currentCapacity = _entries == null ? 0 : _entries.Length;
+ if (currentCapacity >= capacity)
+ return currentCapacity;
+ if (_buckets == null)
+ return Initialize(capacity);
+ int newSize = HashHelpers.GetPrime(capacity);
+ Resize(newSize, forceNewHashCodes: false);
+ return newSize;
+ }
+
+ /// <summary>
+ /// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries
+ ///
+ /// This method can be used to minimize the memory overhead
+ /// once it is known that no new elements will be added.
+ ///
+ /// To allocate minimum size storage array, execute the following statements:
+ ///
+ /// dictionary.Clear();
+ /// dictionary.TrimExcess();
+ /// </summary>
+ public void TrimExcess()
+ => TrimExcess(Count);
+
+ /// <summary>
+ /// Sets the capacity of this dictionary to hold up 'capacity' entries without any further expansion of its backing storage
+ ///
+ /// This method can be used to minimize the memory overhead
+ /// once it is known that no new elements will be added.
+ /// </summary>
+ public void TrimExcess(int capacity)
+ {
+ if (capacity < Count)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
+ int newSize = HashHelpers.GetPrime(capacity);
+
+ Entry[] oldEntries = _entries;
+ int currentCapacity = oldEntries == null ? 0 : oldEntries.Length;
+ if (newSize >= currentCapacity)
+ return;
+
+ int oldCount = _count;
+ Initialize(newSize);
+ Entry[] entries = _entries;
+ int[] buckets = _buckets;
+ int count = 0;
+ for (int i = 0; i < oldCount; i++)
+ {
+ int hashCode = oldEntries[i].hashCode;
+ if (hashCode >= 0)
+ {
+ ref Entry entry = ref entries[count];
+ entry = oldEntries[i];
+ int bucket = hashCode % newSize;
+ // Value in _buckets is 1-based
+ entry.next = buckets[bucket] - 1;
+ // Value in _buckets is 1-based
+ buckets[bucket] = count + 1;
+ count++;
+ }
+ }
+ _count = count;
+ _freeCount = 0;
+ }
+
+ bool ICollection.IsSynchronized => false;
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
+ }
+ return _syncRoot;
+ }
+ }
+
+ bool IDictionary.IsFixedSize => false;
+
+ bool IDictionary.IsReadOnly => false;
+
+ ICollection IDictionary.Keys => (ICollection)Keys;
+
+ ICollection IDictionary.Values => (ICollection)Values;
+
+ object IDictionary.this[object key]
+ {
+ get
+ {
+ if (IsCompatibleKey(key))
+ {
+ int i = FindEntry((TKey)key);
+ if (i >= 0)
+ {
+ return _entries[i].value;
+ }
+ }
+ return null;
+ }
+ set
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow<TValue>(value, ExceptionArgument.value);
+
+ try
+ {
+ TKey tempKey = (TKey)key;
+ try
+ {
+ this[tempKey] = (TValue)value;
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue));
+ }
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey));
+ }
+ }
+ }
+
+ private static bool IsCompatibleKey(object key)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+ return (key is TKey);
+ }
+
+ void IDictionary.Add(object key, object value)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow<TValue>(value, ExceptionArgument.value);
+
+ try
+ {
+ TKey tempKey = (TKey)key;
+
+ try
+ {
+ Add(tempKey, (TValue)value);
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue));
+ }
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey));
+ }
+ }
+
+ bool IDictionary.Contains(object key)
+ {
+ if (IsCompatibleKey(key))
+ {
+ return ContainsKey((TKey)key);
+ }
+
+ return false;
+ }
+
+ IDictionaryEnumerator IDictionary.GetEnumerator()
+ => new Enumerator(this, Enumerator.DictEntry);
+
+ void IDictionary.Remove(object key)
+ {
+ if (IsCompatibleKey(key))
+ {
+ Remove((TKey)key);
+ }
+ }
+
+ public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>,
+ IDictionaryEnumerator
+ {
+ private Dictionary<TKey, TValue> _dictionary;
+ private int _version;
+ private int _index;
+ private KeyValuePair<TKey, TValue> _current;
+ private int _getEnumeratorRetType; // What should Enumerator.Current return?
+
+ internal const int DictEntry = 1;
+ internal const int KeyValuePair = 2;
+
+ internal Enumerator(Dictionary<TKey, TValue> dictionary, int getEnumeratorRetType)
+ {
+ _dictionary = dictionary;
+ _version = dictionary._version;
+ _index = 0;
+ _getEnumeratorRetType = getEnumeratorRetType;
+ _current = new KeyValuePair<TKey, TValue>();
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
+ // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue
+ while ((uint)_index < (uint)_dictionary._count)
+ {
+ ref Entry entry = ref _dictionary._entries[_index++];
+
+ if (entry.hashCode >= 0)
+ {
+ _current = new KeyValuePair<TKey, TValue>(entry.key, entry.value);
+ return true;
+ }
+ }
+
+ _index = _dictionary._count + 1;
+ _current = new KeyValuePair<TKey, TValue>();
+ return false;
+ }
+
+ public KeyValuePair<TKey, TValue> Current => _current;
+
+ public void Dispose()
+ {
+ }
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ if (_getEnumeratorRetType == DictEntry)
+ {
+ return new DictionaryEntry(_current.Key, _current.Value);
+ }
+ else
+ {
+ return new KeyValuePair<TKey, TValue>(_current.Key, _current.Value);
+ }
+ }
+ }
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ _index = 0;
+ _current = new KeyValuePair<TKey, TValue>();
+ }
+
+ DictionaryEntry IDictionaryEnumerator.Entry
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return new DictionaryEntry(_current.Key, _current.Value);
+ }
+ }
+
+ object IDictionaryEnumerator.Key
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return _current.Key;
+ }
+ }
+
+ object IDictionaryEnumerator.Value
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return _current.Value;
+ }
+ }
+ }
+
+ [DebuggerTypeProxy(typeof(DictionaryKeyCollectionDebugView<,>))]
+ [DebuggerDisplay("Count = {Count}")]
+ public sealed class KeyCollection : ICollection<TKey>, ICollection, IReadOnlyCollection<TKey>
+ {
+ private Dictionary<TKey, TValue> _dictionary;
+
+ public KeyCollection(Dictionary<TKey, TValue> dictionary)
+ {
+ if (dictionary == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
+ }
+ _dictionary = dictionary;
+ }
+
+ public Enumerator GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ public void CopyTo(TKey[] array, int index)
+ {
+ if (array == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ }
+
+ if (index < 0 || index > array.Length)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (array.Length - index < _dictionary.Count)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+ }
+
+ int count = _dictionary._count;
+ Entry[] entries = _dictionary._entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0) array[index++] = entries[i].key;
+ }
+ }
+
+ public int Count => _dictionary.Count;
+
+ bool ICollection<TKey>.IsReadOnly => true;
+
+ void ICollection<TKey>.Add(TKey item)
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
+
+ void ICollection<TKey>.Clear()
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
+
+ bool ICollection<TKey>.Contains(TKey item)
+ => _dictionary.ContainsKey(item);
+
+ bool ICollection<TKey>.Remove(TKey item)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
+ return false;
+ }
+
+ IEnumerator<TKey> IEnumerable<TKey>.GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (array.Rank != 1)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ if (array.GetLowerBound(0) != 0)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
+ if ((uint)index > (uint)array.Length)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ if (array.Length - index < _dictionary.Count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+
+ if (array is TKey[] keys)
+ {
+ CopyTo(keys, index);
+ }
+ else
+ {
+ object[] objects = array as object[];
+ if (objects == null)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+
+ int count = _dictionary._count;
+ Entry[] entries = _dictionary._entries;
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0) objects[index++] = entries[i].key;
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+ }
+
+ bool ICollection.IsSynchronized => false;
+
+ object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
+
+ public struct Enumerator : IEnumerator<TKey>, IEnumerator
+ {
+ private Dictionary<TKey, TValue> _dictionary;
+ private int _index;
+ private int _version;
+ private TKey _currentKey;
+
+ internal Enumerator(Dictionary<TKey, TValue> dictionary)
+ {
+ _dictionary = dictionary;
+ _version = dictionary._version;
+ _index = 0;
+ _currentKey = default;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ while ((uint)_index < (uint)_dictionary._count)
+ {
+ ref Entry entry = ref _dictionary._entries[_index++];
+
+ if (entry.hashCode >= 0)
+ {
+ _currentKey = entry.key;
+ return true;
+ }
+ }
+
+ _index = _dictionary._count + 1;
+ _currentKey = default;
+ return false;
+ }
+
+ public TKey Current => _currentKey;
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return _currentKey;
+ }
+ }
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ _index = 0;
+ _currentKey = default;
+ }
+ }
+ }
+
+ [DebuggerTypeProxy(typeof(DictionaryValueCollectionDebugView<,>))]
+ [DebuggerDisplay("Count = {Count}")]
+ public sealed class ValueCollection : ICollection<TValue>, ICollection, IReadOnlyCollection<TValue>
+ {
+ private Dictionary<TKey, TValue> _dictionary;
+
+ public ValueCollection(Dictionary<TKey, TValue> dictionary)
+ {
+ if (dictionary == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
+ }
+ _dictionary = dictionary;
+ }
+
+ public Enumerator GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ public void CopyTo(TValue[] array, int index)
+ {
+ if (array == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ }
+
+ if (index < 0 || index > array.Length)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (array.Length - index < _dictionary.Count)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+ }
+
+ int count = _dictionary._count;
+ Entry[] entries = _dictionary._entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0) array[index++] = entries[i].value;
+ }
+ }
+
+ public int Count => _dictionary.Count;
+
+ bool ICollection<TValue>.IsReadOnly => true;
+
+ void ICollection<TValue>.Add(TValue item)
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
+
+ bool ICollection<TValue>.Remove(TValue item)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
+ return false;
+ }
+
+ void ICollection<TValue>.Clear()
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
+
+ bool ICollection<TValue>.Contains(TValue item)
+ => _dictionary.ContainsValue(item);
+
+ IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (array.Rank != 1)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ if (array.GetLowerBound(0) != 0)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
+ if ((uint)index > (uint)array.Length)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ if (array.Length - index < _dictionary.Count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+
+ if (array is TValue[] values)
+ {
+ CopyTo(values, index);
+ }
+ else
+ {
+ object[] objects = array as object[];
+ if (objects == null)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+
+ int count = _dictionary._count;
+ Entry[] entries = _dictionary._entries;
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0) objects[index++] = entries[i].value;
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+ }
+
+ bool ICollection.IsSynchronized => false;
+
+ object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
+
+ public struct Enumerator : IEnumerator<TValue>, IEnumerator
+ {
+ private Dictionary<TKey, TValue> _dictionary;
+ private int _index;
+ private int _version;
+ private TValue _currentValue;
+
+ internal Enumerator(Dictionary<TKey, TValue> dictionary)
+ {
+ _dictionary = dictionary;
+ _version = dictionary._version;
+ _index = 0;
+ _currentValue = default;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ while ((uint)_index < (uint)_dictionary._count)
+ {
+ ref Entry entry = ref _dictionary._entries[_index++];
+
+ if (entry.hashCode >= 0)
+ {
+ _currentValue = entry.value;
+ return true;
+ }
+ }
+ _index = _dictionary._count + 1;
+ _currentValue = default;
+ return false;
+ }
+
+ public TValue Current => _currentValue;
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return _currentValue;
+ }
+ }
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+ _index = 0;
+ _currentValue = default;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/ICollection.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/ICollection.cs
new file mode 100644
index 0000000000..78ee5cb1f5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/ICollection.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ // Base interface for all collections, defining enumerators, size, and
+ // synchronization methods.
+ public interface ICollection<T> : IEnumerable<T>
+ {
+ // Number of items in the collections.
+ int Count { get; }
+
+ bool IsReadOnly { get; }
+
+ void Add(T item);
+
+ void Clear();
+
+ bool Contains(T item);
+
+ // CopyTo copies a collection into an Array, starting at a particular
+ // index into the array.
+ //
+ void CopyTo(T[] array, int arrayIndex);
+
+ //void CopyTo(int sourceIndex, T[] destinationArray, int destinationIndex, int count);
+
+ bool Remove(T item);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/ICollectionDebugView.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/ICollectionDebugView.cs
new file mode 100644
index 0000000000..9916e857e2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/ICollectionDebugView.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Collections.Generic
+{
+ internal sealed class ICollectionDebugView<T>
+ {
+ private readonly ICollection<T> _collection;
+
+ public ICollectionDebugView(ICollection<T> collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException(nameof(collection));
+ }
+
+ _collection = collection;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items
+ {
+ get
+ {
+ T[] items = new T[_collection.Count];
+ _collection.CopyTo(items, 0);
+ return items;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IComparer.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IComparer.cs
new file mode 100644
index 0000000000..713d499cc8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IComparer.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections.Generic
+{
+ // The generic IComparer interface implements a method that compares
+ // two objects. It is used in conjunction with the Sort and
+ // BinarySearch methods on the Array, List, and SortedList classes.
+ public interface IComparer<in T>
+ {
+ // Compares two objects. An implementation of this method must return a
+ // value less than zero if x is less than y, zero if x is equal to y, or a
+ // value greater than zero if x is greater than y.
+ //
+ int Compare(T x, T y);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IDictionary.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IDictionary.cs
new file mode 100644
index 0000000000..05677da3b2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IDictionary.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections.Generic
+{
+ // An IDictionary is a possibly unordered set of key-value pairs.
+ // Keys can be any non-null object. Values can be any object.
+ // You can look up a value in an IDictionary via the default indexed
+ // property, Items.
+ public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>
+ {
+ // Interfaces are not serializable
+ // The Item property provides methods to read and edit entries
+ // in the Dictionary.
+ TValue this[TKey key]
+ {
+ get;
+ set;
+ }
+
+ // Returns a collections of the keys in this dictionary.
+ ICollection<TKey> Keys
+ {
+ get;
+ }
+
+ // Returns a collections of the values in this dictionary.
+ ICollection<TValue> Values
+ {
+ get;
+ }
+
+ // Returns whether this dictionary contains a particular key.
+ //
+ bool ContainsKey(TKey key);
+
+ // Adds a key-value pair to the dictionary.
+ //
+ void Add(TKey key, TValue value);
+
+ // Removes a particular key from the dictionary.
+ //
+ bool Remove(TKey key);
+
+ bool TryGetValue(TKey key, out TValue value);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IDictionaryDebugView.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IDictionaryDebugView.cs
new file mode 100644
index 0000000000..4721642fee
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IDictionaryDebugView.cs
@@ -0,0 +1,80 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Collections.Generic
+{
+ internal sealed class IDictionaryDebugView<K, V>
+ {
+ private readonly IDictionary<K, V> _dict;
+
+ public IDictionaryDebugView(IDictionary<K, V> dictionary)
+ {
+ if (dictionary == null)
+ throw new ArgumentNullException(nameof(dictionary));
+
+ _dict = dictionary;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public KeyValuePair<K, V>[] Items
+ {
+ get
+ {
+ KeyValuePair<K, V>[] items = new KeyValuePair<K, V>[_dict.Count];
+ _dict.CopyTo(items, 0);
+ return items;
+ }
+ }
+ }
+
+ internal sealed class DictionaryKeyCollectionDebugView<TKey, TValue>
+ {
+ private readonly ICollection<TKey> _collection;
+
+ public DictionaryKeyCollectionDebugView(ICollection<TKey> collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ _collection = collection;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public TKey[] Items
+ {
+ get
+ {
+ TKey[] items = new TKey[_collection.Count];
+ _collection.CopyTo(items, 0);
+ return items;
+ }
+ }
+ }
+
+ internal sealed class DictionaryValueCollectionDebugView<TKey, TValue>
+ {
+ private readonly ICollection<TValue> _collection;
+
+ public DictionaryValueCollectionDebugView(ICollection<TValue> collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException(nameof(collection));
+
+ _collection = collection;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public TValue[] Items
+ {
+ get
+ {
+ TValue[] items = new TValue[_collection.Count];
+ _collection.CopyTo(items, 0);
+ return items;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IEnumerable.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IEnumerable.cs
new file mode 100644
index 0000000000..ddb798e8a6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IEnumerable.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ // Implement this interface if you need to support foreach semantics.
+ public interface IEnumerable<out T> : IEnumerable
+ {
+ // Returns an IEnumerator for this enumerable Object. The enumerator provides
+ // a simple way to access all the contents of a collection.
+ /// <include file='doc\IEnumerable.uex' path='docs/doc[@for="IEnumerable.GetEnumerator"]/*' />
+ new IEnumerator<T> GetEnumerator();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IEnumerator.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IEnumerator.cs
new file mode 100644
index 0000000000..6360576974
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IEnumerator.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace System.Collections.Generic
+{
+ // Base interface for all generic enumerators, providing a simple approach
+ // to iterating over a collection.
+ public interface IEnumerator<out T> : IDisposable, IEnumerator
+ {
+ // Returns the current element of the enumeration. The returned value is
+ // undefined before the first call to MoveNext and following a
+ // call to MoveNext that returned false. Multiple calls to
+ // GetCurrent with no intervening calls to MoveNext
+ // will return the same object.
+ //
+ /// <include file='doc\IEnumerator.uex' path='docs/doc[@for="IEnumerator.Current"]/*' />
+ new T Current
+ {
+ get;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IEqualityComparer.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IEqualityComparer.cs
new file mode 100644
index 0000000000..543bdb5fce
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IEqualityComparer.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections.Generic
+{
+ // The generic IEqualityComparer interface implements methods to if check two objects are equal
+ // and generate Hashcode for an object.
+ // It is use in Dictionary class.
+ public interface IEqualityComparer<in T>
+ {
+ bool Equals(T x, T y);
+ int GetHashCode(T obj);
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IList.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IList.cs
new file mode 100644
index 0000000000..2abc7b9142
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IList.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ // An IList is an ordered collection of objects. The exact ordering
+ // is up to the implementation of the list, ranging from a sorted
+ // order to insertion order.
+ public interface IList<T> : ICollection<T>
+ {
+ // The Item property provides methods to read and edit entries in the List.
+ T this[int index]
+ {
+ get;
+ set;
+ }
+
+ // Returns the index of a particular item, if it is in the list.
+ // Returns -1 if the item isn't in the list.
+ int IndexOf(T item);
+
+ // Inserts value into the list at position index.
+ // index must be non-negative and less than or equal to the
+ // number of elements in the list. If index equals the number
+ // of items in the list, then value is appended to the end.
+ void Insert(int index, T item);
+
+ // Removes the item at position index.
+ void RemoveAt(int index);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyCollection.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyCollection.cs
new file mode 100644
index 0000000000..9eea39de22
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyCollection.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ // Provides a read-only, covariant view of a generic list.
+ public interface IReadOnlyCollection<out T> : IEnumerable<T>
+ {
+ int Count { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyDictionary.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyDictionary.cs
new file mode 100644
index 0000000000..300b996611
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyDictionary.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections.Generic
+{
+ // Provides a read-only view of a generic dictionary.
+ public interface IReadOnlyDictionary<TKey, TValue> : IReadOnlyCollection<KeyValuePair<TKey, TValue>>
+ {
+ bool ContainsKey(TKey key);
+ bool TryGetValue(TKey key, out TValue value);
+
+ TValue this[TKey key] { get; }
+ IEnumerable<TKey> Keys { get; }
+ IEnumerable<TValue> Values { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyList.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyList.cs
new file mode 100644
index 0000000000..7193805b06
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/IReadOnlyList.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ // Provides a read-only, covariant view of a generic list.
+ public interface IReadOnlyList<out T> : IReadOnlyCollection<T>
+ {
+ T this[int index] { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/KeyNotFoundException.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/KeyNotFoundException.cs
new file mode 100644
index 0000000000..48eddb8745
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/KeyNotFoundException.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.Collections.Generic
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class KeyNotFoundException : SystemException
+ {
+ public KeyNotFoundException()
+ : base(SR.Arg_KeyNotFound)
+ {
+ HResult = HResults.COR_E_KEYNOTFOUND;
+ }
+
+ public KeyNotFoundException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_KEYNOTFOUND;
+ }
+
+ public KeyNotFoundException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_KEYNOTFOUND;
+ }
+
+ protected KeyNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/KeyValuePair.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/KeyValuePair.cs
new file mode 100644
index 0000000000..82c786d407
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/KeyValuePair.cs
@@ -0,0 +1,83 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+using System.Text;
+
+namespace System.Collections.Generic
+{
+ // Provides the Create factory method for KeyValuePair<TKey, TValue>.
+ public static class KeyValuePair
+ {
+ // Creates a new KeyValuePair<TKey, TValue> from the given values.
+ public static KeyValuePair<TKey, TValue> Create<TKey, TValue>(TKey key, TValue value)
+ {
+ return new KeyValuePair<TKey, TValue>(key, value);
+ }
+
+ /// <summary>
+ /// Used by KeyValuePair.ToString to reduce generic code
+ /// </summary>
+ internal static string PairToString(object key, object value)
+ {
+ StringBuilder s = StringBuilderCache.Acquire();
+ s.Append('[');
+
+ if (key != null)
+ {
+ s.Append(key);
+ }
+
+ s.Append(", ");
+
+ if (value != null)
+ {
+ s.Append(value);
+ }
+
+ s.Append(']');
+
+ return StringBuilderCache.GetStringAndRelease(s);
+ }
+ }
+
+ // A KeyValuePair holds a key and a value from a dictionary.
+ // It is used by the IEnumerable<T> implementation for both IDictionary<TKey, TValue>
+ // and IReadOnlyDictionary<TKey, TValue>.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public readonly struct KeyValuePair<TKey, TValue>
+ {
+ private readonly TKey key; // Do not rename (binary serialization)
+ private readonly TValue value; // Do not rename (binary serialization)
+
+ public KeyValuePair(TKey key, TValue value)
+ {
+ this.key = key;
+ this.value = value;
+ }
+
+ public TKey Key
+ {
+ get { return key; }
+ }
+
+ public TValue Value
+ {
+ get { return value; }
+ }
+
+ public override string ToString()
+ {
+ return KeyValuePair.PairToString(Key, Value);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Deconstruct(out TKey key, out TValue value)
+ {
+ key = Key;
+ value = Value;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/List.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/List.cs
new file mode 100644
index 0000000000..6b9f9b45b3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/List.cs
@@ -0,0 +1,1188 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace System.Collections.Generic
+{
+ // Implements a variable-size List that uses an array of objects to store the
+ // elements. A List has a capacity, which is the allocated length
+ // of the internal array. As elements are added to a List, the capacity
+ // of the List is automatically increased as required by reallocating the
+ // internal array.
+ //
+ [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
+ [DebuggerDisplay("Count = {Count}")]
+ [Serializable]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class List<T> : IList<T>, IList, IReadOnlyList<T>
+ {
+ private const int DefaultCapacity = 4;
+
+ private T[] _items; // Do not rename (binary serialization)
+ private int _size; // Do not rename (binary serialization)
+ private int _version; // Do not rename (binary serialization)
+ [NonSerialized]
+ private object _syncRoot;
+
+ private static readonly T[] s_emptyArray = new T[0];
+
+ // Constructs a List. The list is initially empty and has a capacity
+ // of zero. Upon adding the first element to the list the capacity is
+ // increased to DefaultCapacity, and then increased in multiples of two
+ // as required.
+ public List()
+ {
+ _items = s_emptyArray;
+ }
+
+ // Constructs a List with a given initial capacity. The list is
+ // initially empty, but will have room for the given number of elements
+ // before any reallocations are required.
+ //
+ public List(int capacity)
+ {
+ if (capacity < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (capacity == 0)
+ _items = s_emptyArray;
+ else
+ _items = new T[capacity];
+ }
+
+ // Constructs a List, copying the contents of the given collection. The
+ // size and capacity of the new list will both be equal to the size of the
+ // given collection.
+ //
+ public List(IEnumerable<T> collection)
+ {
+ if (collection == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
+
+ if (collection is ICollection<T> c)
+ {
+ int count = c.Count;
+ if (count == 0)
+ {
+ _items = s_emptyArray;
+ }
+ else
+ {
+ _items = new T[count];
+ c.CopyTo(_items, 0);
+ _size = count;
+ }
+ }
+ else
+ {
+ _size = 0;
+ _items = s_emptyArray;
+ AddEnumerable(collection);
+ }
+ }
+
+ // Gets and sets the capacity of this list. The capacity is the size of
+ // the internal array used to hold items. When set, the internal
+ // array of the list is reallocated to the given capacity.
+ //
+ public int Capacity
+ {
+ get
+ {
+ return _items.Length;
+ }
+ set
+ {
+ if (value < _size)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
+ }
+
+ if (value != _items.Length)
+ {
+ if (value > 0)
+ {
+ T[] newItems = new T[value];
+ if (_size > 0)
+ {
+ Array.Copy(_items, 0, newItems, 0, _size);
+ }
+ _items = newItems;
+ }
+ else
+ {
+ _items = s_emptyArray;
+ }
+ }
+ }
+ }
+
+ // Read-only property describing how many elements are in the List.
+ public int Count => _size;
+
+ bool IList.IsFixedSize => false;
+
+ // Is this List read-only?
+ bool ICollection<T>.IsReadOnly => false;
+
+ bool IList.IsReadOnly => false;
+
+ // Is this List synchronized (thread-safe)?
+ bool ICollection.IsSynchronized => false;
+
+ // Synchronization root for this object.
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
+ }
+ return _syncRoot;
+ }
+ }
+
+ // Sets or Gets the element at the given index.
+ public T this[int index]
+ {
+ get
+ {
+ // Following trick can reduce the range check by one
+ if ((uint)index >= (uint)_size)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+ return _items[index];
+ }
+
+ set
+ {
+ if ((uint)index >= (uint)_size)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+ _items[index] = value;
+ _version++;
+ }
+ }
+
+ private static bool IsCompatibleObject(object value)
+ {
+ // Non-null values are fine. Only accept nulls if T is a class or Nullable<U>.
+ // Note that default(T) is not equal to null for value types except when T is Nullable<U>.
+ return ((value is T) || (value == null && default(T) == null));
+ }
+
+ object IList.this[int index]
+ {
+ get
+ {
+ return this[index];
+ }
+ set
+ {
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value);
+
+ try
+ {
+ this[index] = (T)value;
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T));
+ }
+ }
+ }
+
+ // Adds the given object to the end of this list. The size of the list is
+ // increased by one. If required, the capacity of the list is doubled
+ // before adding the new element.
+ //
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(T item)
+ {
+ _version++;
+ T[] array = _items;
+ int size = _size;
+ if ((uint)size < (uint)array.Length)
+ {
+ _size = size + 1;
+ array[size] = item;
+ }
+ else
+ {
+ AddWithResize(item);
+ }
+ }
+
+ // Non-inline from List.Add to improve its code quality as uncommon path
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void AddWithResize(T item)
+ {
+ int size = _size;
+ EnsureCapacity(size + 1);
+ _size = size + 1;
+ _items[size] = item;
+ }
+
+ int IList.Add(object item)
+ {
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item);
+
+ try
+ {
+ Add((T)item);
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T));
+ }
+
+ return Count - 1;
+ }
+
+ // Adds the elements of the given collection to the end of this list. If
+ // required, the capacity of the list is increased to twice the previous
+ // capacity or the new size, whichever is larger.
+ //
+ public void AddRange(IEnumerable<T> collection)
+ => InsertRange(_size, collection);
+
+ public ReadOnlyCollection<T> AsReadOnly()
+ => new ReadOnlyCollection<T>(this);
+
+ // Searches a section of the list for a given element using a binary search
+ // algorithm. Elements of the list are compared to the search value using
+ // the given IComparer interface. If comparer is null, elements of
+ // the list are compared to the search value using the IComparable
+ // interface, which in that case must be implemented by all elements of the
+ // list and the given search value. This method assumes that the given
+ // section of the list is already sorted; if this is not the case, the
+ // result will be incorrect.
+ //
+ // The method returns the index of the given value in the list. If the
+ // list does not contain the given value, the method returns a negative
+ // integer. The bitwise complement operator (~) can be applied to a
+ // negative result to produce the index of the first element (if any) that
+ // is larger than the given search value. This is also the index at which
+ // the search value should be inserted into the list in order for the list
+ // to remain sorted.
+ //
+ // The method uses the Array.BinarySearch method to perform the
+ // search.
+ //
+ public int BinarySearch(int index, int count, T item, IComparer<T> comparer)
+ {
+ if (index < 0)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ if (count < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+ if (_size - index < count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+
+ return Array.BinarySearch<T>(_items, index, count, item, comparer);
+ }
+
+ public int BinarySearch(T item)
+ => BinarySearch(0, Count, item, null);
+
+ public int BinarySearch(T item, IComparer<T> comparer)
+ => BinarySearch(0, Count, item, comparer);
+
+ // Clears the contents of List.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Clear()
+ {
+ _version++;
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ int size = _size;
+ _size = 0;
+ if (size > 0)
+ {
+ Array.Clear(_items, 0, size); // Clear the elements so that the gc can reclaim the references.
+ }
+ }
+ else
+ {
+ _size = 0;
+ }
+ }
+
+ // Contains returns true if the specified element is in the List.
+ // It does a linear, O(n) search. Equality is determined by calling
+ // EqualityComparer<T>.Default.Equals().
+ //
+ public bool Contains(T item)
+ {
+ // PERF: IndexOf calls Array.IndexOf, which internally
+ // calls EqualityComparer<T>.Default.IndexOf, which
+ // is specialized for different types. This
+ // boosts performance since instead of making a
+ // virtual method call each iteration of the loop,
+ // via EqualityComparer<T>.Default.Equals, we
+ // only make one virtual call to EqualityComparer.IndexOf.
+
+ return _size != 0 && IndexOf(item) != -1;
+ }
+
+ bool IList.Contains(object item)
+ {
+ if (IsCompatibleObject(item))
+ {
+ return Contains((T)item);
+ }
+ return false;
+ }
+
+ public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter)
+ {
+ if (converter == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.converter);
+ }
+
+ List<TOutput> list = new List<TOutput>(_size);
+ for (int i = 0; i < _size; i++)
+ {
+ list._items[i] = converter(_items[i]);
+ }
+ list._size = _size;
+ return list;
+ }
+
+ // Copies this List into array, which must be of a
+ // compatible array type.
+ public void CopyTo(T[] array)
+ => CopyTo(array, 0);
+
+ // Copies this List into array, which must be of a
+ // compatible array type.
+ void ICollection.CopyTo(Array array, int arrayIndex)
+ {
+ if ((array != null) && (array.Rank != 1))
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ }
+
+ try
+ {
+ // Array.Copy will check for NULL.
+ Array.Copy(_items, 0, array, arrayIndex, _size);
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+
+ // Copies a section of this list to the given array at the given index.
+ //
+ // The method uses the Array.Copy method to copy the elements.
+ //
+ public void CopyTo(int index, T[] array, int arrayIndex, int count)
+ {
+ if (_size - index < count)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+ }
+
+ // Delegate rest of error checking to Array.Copy.
+ Array.Copy(_items, index, array, arrayIndex, count);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ // Delegate rest of error checking to Array.Copy.
+ Array.Copy(_items, 0, array, arrayIndex, _size);
+ }
+
+ // Ensures that the capacity of this list is at least the given minimum
+ // value. If the current capacity of the list is less than min, the
+ // capacity is increased to twice the current capacity or to min,
+ // whichever is larger.
+ //
+ private void EnsureCapacity(int min)
+ {
+ if (_items.Length < min)
+ {
+ int newCapacity = _items.Length == 0 ? DefaultCapacity : _items.Length * 2;
+ // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
+ // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
+ if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
+ if (newCapacity < min) newCapacity = min;
+ Capacity = newCapacity;
+ }
+ }
+
+ public bool Exists(Predicate<T> match)
+ => FindIndex(match) != -1;
+
+ public T Find(Predicate<T> match)
+ {
+ if (match == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+ }
+
+ for (int i = 0; i < _size; i++)
+ {
+ if (match(_items[i]))
+ {
+ return _items[i];
+ }
+ }
+ return default;
+ }
+
+ public List<T> FindAll(Predicate<T> match)
+ {
+ if (match == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+ }
+
+ List<T> list = new List<T>();
+ for (int i = 0; i < _size; i++)
+ {
+ if (match(_items[i]))
+ {
+ list.Add(_items[i]);
+ }
+ }
+ return list;
+ }
+
+ public int FindIndex(Predicate<T> match)
+ => FindIndex(0, _size, match);
+
+ public int FindIndex(int startIndex, Predicate<T> match)
+ => FindIndex(startIndex, _size - startIndex, match);
+
+ public int FindIndex(int startIndex, int count, Predicate<T> match)
+ {
+ if ((uint)startIndex > (uint)_size)
+ {
+ ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index();
+ }
+
+ if (count < 0 || startIndex > _size - count)
+ {
+ ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
+ }
+
+ if (match == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+ }
+
+ int endIndex = startIndex + count;
+ for (int i = startIndex; i < endIndex; i++)
+ {
+ if (match(_items[i])) return i;
+ }
+ return -1;
+ }
+
+ public T FindLast(Predicate<T> match)
+ {
+ if (match == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+ }
+
+ for (int i = _size - 1; i >= 0; i--)
+ {
+ if (match(_items[i]))
+ {
+ return _items[i];
+ }
+ }
+ return default;
+ }
+
+ public int FindLastIndex(Predicate<T> match)
+ => FindLastIndex(_size - 1, _size, match);
+
+ public int FindLastIndex(int startIndex, Predicate<T> match)
+ => FindLastIndex(startIndex, startIndex + 1, match);
+
+ public int FindLastIndex(int startIndex, int count, Predicate<T> match)
+ {
+ if (match == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+ }
+
+ if (_size == 0)
+ {
+ // Special case for 0 length List
+ if (startIndex != -1)
+ {
+ ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index();
+ }
+ }
+ else
+ {
+ // Make sure we're not out of range
+ if ((uint)startIndex >= (uint)_size)
+ {
+ ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index();
+ }
+ }
+
+ // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
+ if (count < 0 || startIndex - count + 1 < 0)
+ {
+ ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
+ }
+
+ int endIndex = startIndex - count;
+ for (int i = startIndex; i > endIndex; i--)
+ {
+ if (match(_items[i]))
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void ForEach(Action<T> action)
+ {
+ if (action == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.action);
+ }
+
+ int version = _version;
+
+ for (int i = 0; i < _size; i++)
+ {
+ if (version != _version)
+ {
+ break;
+ }
+ action(_items[i]);
+ }
+
+ if (version != _version)
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ // Returns an enumerator for this list with the given
+ // permission for removal of elements. If modifications made to the list
+ // while an enumeration is in progress, the MoveNext and
+ // GetObject methods of the enumerator will throw an exception.
+ //
+ public Enumerator GetEnumerator()
+ => new Enumerator(this);
+
+ IEnumerator<T> IEnumerable<T>.GetEnumerator()
+ => new Enumerator(this);
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(this);
+
+ public List<T> GetRange(int index, int count)
+ {
+ if (index < 0)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (count < 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (_size - index < count)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+ }
+
+ List<T> list = new List<T>(count);
+ Array.Copy(_items, index, list._items, 0, count);
+ list._size = count;
+ return list;
+ }
+
+
+ // Returns the index of the first occurrence of a given value in a range of
+ // this list. The list is searched forwards from beginning to end.
+ // The elements of the list are compared to the given value using the
+ // Object.Equals method.
+ //
+ // This method uses the Array.IndexOf method to perform the
+ // search.
+ //
+ public int IndexOf(T item)
+ => Array.IndexOf(_items, item, 0, _size);
+
+ int IList.IndexOf(object item)
+ {
+ if (IsCompatibleObject(item))
+ {
+ return IndexOf((T)item);
+ }
+ return -1;
+ }
+
+ // Returns the index of the first occurrence of a given value in a range of
+ // this list. The list is searched forwards, starting at index
+ // index and ending at count number of elements. The
+ // elements of the list are compared to the given value using the
+ // Object.Equals method.
+ //
+ // This method uses the Array.IndexOf method to perform the
+ // search.
+ //
+ public int IndexOf(T item, int index)
+ {
+ if (index > _size)
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ return Array.IndexOf(_items, item, index, _size - index);
+ }
+
+ // Returns the index of the first occurrence of a given value in a range of
+ // this list. The list is searched forwards, starting at index
+ // index and upto count number of elements. The
+ // elements of the list are compared to the given value using the
+ // Object.Equals method.
+ //
+ // This method uses the Array.IndexOf method to perform the
+ // search.
+ //
+ public int IndexOf(T item, int index, int count)
+ {
+ if (index > _size)
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+
+ if (count < 0 || index > _size - count)
+ ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
+
+ return Array.IndexOf(_items, item, index, count);
+ }
+
+ // Inserts an element into this list at a given index. The size of the list
+ // is increased by one. If required, the capacity of the list is doubled
+ // before inserting the new element.
+ //
+ public void Insert(int index, T item)
+ {
+ // Note that insertions at the end are legal.
+ if ((uint)index > (uint)_size)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
+ }
+ if (_size == _items.Length) EnsureCapacity(_size + 1);
+ if (index < _size)
+ {
+ Array.Copy(_items, index, _items, index + 1, _size - index);
+ }
+ _items[index] = item;
+ _size++;
+ _version++;
+ }
+
+ void IList.Insert(int index, object item)
+ {
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item);
+
+ try
+ {
+ Insert(index, (T)item);
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T));
+ }
+ }
+
+ // Inserts the elements of the given collection at a given index. If
+ // required, the capacity of the list is increased to twice the previous
+ // capacity or the new size, whichever is larger. Ranges may be added
+ // to the end of the list by setting index to the List's size.
+ //
+ public void InsertRange(int index, IEnumerable<T> collection)
+ {
+ if (collection == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
+ }
+
+ if ((uint)index > (uint)_size)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+
+ if (collection is ICollection<T> c)
+ {
+ int count = c.Count;
+ if (count > 0)
+ {
+ EnsureCapacity(_size + count);
+ if (index < _size)
+ {
+ Array.Copy(_items, index, _items, index + count, _size - index);
+ }
+
+ // If we're inserting a List into itself, we want to be able to deal with that.
+ if (this == c)
+ {
+ // Copy first part of _items to insert location
+ Array.Copy(_items, 0, _items, index, index);
+ // Copy last part of _items back to inserted location
+ Array.Copy(_items, index + count, _items, index * 2, _size - index);
+ }
+ else
+ {
+ c.CopyTo(_items, index);
+ }
+ _size += count;
+ }
+ }
+ else if (index < _size)
+ {
+ // We're inserting a lazy enumerable. Call Insert on each of the constituent items.
+ using (IEnumerator<T> en = collection.GetEnumerator())
+ {
+ while (en.MoveNext())
+ {
+ Insert(index++, en.Current);
+ }
+ }
+ }
+ else
+ {
+ // We're adding a lazy enumerable because the index is at the end of this list.
+ AddEnumerable(collection);
+ }
+ _version++;
+ }
+
+ // Returns the index of the last occurrence of a given value in a range of
+ // this list. The list is searched backwards, starting at the end
+ // and ending at the first element in the list. The elements of the list
+ // are compared to the given value using the Object.Equals method.
+ //
+ // This method uses the Array.LastIndexOf method to perform the
+ // search.
+ //
+ public int LastIndexOf(T item)
+ {
+ if (_size == 0)
+ { // Special case for empty list
+ return -1;
+ }
+ else
+ {
+ return LastIndexOf(item, _size - 1, _size);
+ }
+ }
+
+ // Returns the index of the last occurrence of a given value in a range of
+ // this list. The list is searched backwards, starting at index
+ // index and ending at the first element in the list. The
+ // elements of the list are compared to the given value using the
+ // Object.Equals method.
+ //
+ // This method uses the Array.LastIndexOf method to perform the
+ // search.
+ //
+ public int LastIndexOf(T item, int index)
+ {
+ if (index >= _size)
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ return LastIndexOf(item, index, index + 1);
+ }
+
+ // Returns the index of the last occurrence of a given value in a range of
+ // this list. The list is searched backwards, starting at index
+ // index and upto count elements. The elements of
+ // the list are compared to the given value using the Object.Equals
+ // method.
+ //
+ // This method uses the Array.LastIndexOf method to perform the
+ // search.
+ //
+ public int LastIndexOf(T item, int index, int count)
+ {
+ if ((Count != 0) && (index < 0))
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if ((Count != 0) && (count < 0))
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (_size == 0)
+ { // Special case for empty list
+ return -1;
+ }
+
+ if (index >= _size)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_BiggerThanCollection);
+ }
+
+ if (count > index + 1)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_BiggerThanCollection);
+ }
+
+ return Array.LastIndexOf(_items, item, index, count);
+ }
+
+ // Removes the element at the given index. The size of the list is
+ // decreased by one.
+ public bool Remove(T item)
+ {
+ int index = IndexOf(item);
+ if (index >= 0)
+ {
+ RemoveAt(index);
+ return true;
+ }
+
+ return false;
+ }
+
+ void IList.Remove(object item)
+ {
+ if (IsCompatibleObject(item))
+ {
+ Remove((T)item);
+ }
+ }
+
+ // This method removes all items which matches the predicate.
+ // The complexity is O(n).
+ public int RemoveAll(Predicate<T> match)
+ {
+ if (match == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+ }
+
+ int freeIndex = 0; // the first free slot in items array
+
+ // Find the first item which needs to be removed.
+ while (freeIndex < _size && !match(_items[freeIndex])) freeIndex++;
+ if (freeIndex >= _size) return 0;
+
+ int current = freeIndex + 1;
+ while (current < _size)
+ {
+ // Find the first item which needs to be kept.
+ while (current < _size && match(_items[current])) current++;
+
+ if (current < _size)
+ {
+ // copy item to the free slot.
+ _items[freeIndex++] = _items[current++];
+ }
+ }
+
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ Array.Clear(_items, freeIndex, _size - freeIndex); // Clear the elements so that the gc can reclaim the references.
+ }
+
+ int result = _size - freeIndex;
+ _size = freeIndex;
+ _version++;
+ return result;
+ }
+
+ // Removes the element at the given index. The size of the list is
+ // decreased by one.
+ public void RemoveAt(int index)
+ {
+ if ((uint)index >= (uint)_size)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+ _size--;
+ if (index < _size)
+ {
+ Array.Copy(_items, index + 1, _items, index, _size - index);
+ }
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ _items[_size] = default;
+ }
+ _version++;
+ }
+
+ // Removes a range of elements from this list.
+ public void RemoveRange(int index, int count)
+ {
+ if (index < 0)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (count < 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (_size - index < count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+
+ if (count > 0)
+ {
+ int i = _size;
+ _size -= count;
+ if (index < _size)
+ {
+ Array.Copy(_items, index + count, _items, index, _size - index);
+ }
+
+ _version++;
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ Array.Clear(_items, _size, count);
+ }
+ }
+ }
+
+ // Reverses the elements in this list.
+ public void Reverse()
+ => Reverse(0, Count);
+
+ // Reverses the elements in a range of this list. Following a call to this
+ // method, an element in the range given by index and count
+ // which was previously located at index i will now be located at
+ // index index + (index + count - i - 1).
+ //
+ public void Reverse(int index, int count)
+ {
+ if (index < 0)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (count < 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (_size - index < count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+
+ if (count > 1)
+ {
+ Array.Reverse(_items, index, count);
+ }
+ _version++;
+ }
+
+ // Sorts the elements in this list. Uses the default comparer and
+ // Array.Sort.
+ public void Sort()
+ => Sort(0, Count, null);
+
+ // Sorts the elements in this list. Uses Array.Sort with the
+ // provided comparer.
+ public void Sort(IComparer<T> comparer)
+ => Sort(0, Count, comparer);
+
+ // Sorts the elements in a section of this list. The sort compares the
+ // elements to each other using the given IComparer interface. If
+ // comparer is null, the elements are compared to each other using
+ // the IComparable interface, which in that case must be implemented by all
+ // elements of the list.
+ //
+ // This method uses the Array.Sort method to sort the elements.
+ //
+ public void Sort(int index, int count, IComparer<T> comparer)
+ {
+ if (index < 0)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (count < 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (_size - index < count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+
+ if (count > 1)
+ {
+ Array.Sort<T>(_items, index, count, comparer);
+ }
+ _version++;
+ }
+
+ public void Sort(Comparison<T> comparison)
+ {
+ if (comparison == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparison);
+ }
+
+ if (_size > 1)
+ {
+ ArraySortHelper<T>.Sort(_items, 0, _size, comparison);
+ }
+ _version++;
+ }
+
+ // ToArray returns an array containing the contents of the List.
+ // This requires copying the List, which is an O(n) operation.
+ public T[] ToArray()
+ {
+ if (_size == 0)
+ {
+ return s_emptyArray;
+ }
+
+ T[] array = new T[_size];
+ Array.Copy(_items, 0, array, 0, _size);
+ return array;
+ }
+
+ // Sets the capacity of this list to the size of the list. This method can
+ // be used to minimize a list's memory overhead once it is known that no
+ // new elements will be added to the list. To completely clear a list and
+ // release all memory referenced by the list, execute the following
+ // statements:
+ //
+ // list.Clear();
+ // list.TrimExcess();
+ //
+ public void TrimExcess()
+ {
+ int threshold = (int)(((double)_items.Length) * 0.9);
+ if (_size < threshold)
+ {
+ Capacity = _size;
+ }
+ }
+
+ public bool TrueForAll(Predicate<T> match)
+ {
+ if (match == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+ }
+
+ for (int i = 0; i < _size; i++)
+ {
+ if (!match(_items[i]))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void AddEnumerable(IEnumerable<T> enumerable)
+ {
+ Debug.Assert(enumerable != null);
+ Debug.Assert(!(enumerable is ICollection<T>), "We should have optimized for this beforehand.");
+
+ _version++; // Even if the enumerable has no items, we can update _version.
+ using (IEnumerator<T> en = enumerable.GetEnumerator())
+ {
+
+ while (en.MoveNext())
+ {
+ // Capture Current before doing anything else. If this throws
+ // an exception, we want to make a clean break.
+ T current = en.Current;
+
+ if (_size == _items.Length)
+ {
+ EnsureCapacity(_size + 1);
+ }
+
+ _items[_size++] = current;
+ }
+ }
+ }
+
+ public struct Enumerator : IEnumerator<T>, IEnumerator
+ {
+ private List<T> _list;
+ private int _index;
+ private int _version;
+ private T _current;
+
+ internal Enumerator(List<T> list)
+ {
+ _list = list;
+ _index = 0;
+ _version = list._version;
+ _current = default;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ List<T> localList = _list;
+
+ if (_version == localList._version && ((uint)_index < (uint)localList._size))
+ {
+ _current = localList._items[_index];
+ _index++;
+ return true;
+ }
+ return MoveNextRare();
+ }
+
+ private bool MoveNextRare()
+ {
+ if (_version != _list._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ _index = _list._size + 1;
+ _current = default;
+ return false;
+ }
+
+ public T Current => _current;
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0 || _index == _list._size + 1)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+ return Current;
+ }
+ }
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _list._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ _index = 0;
+ _current = default;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs
new file mode 100644
index 0000000000..e7efa22b24
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Collections.Generic
+{
+ // NonRandomizedStringEqualityComparer is the comparer used by default with the Dictionary<string,...>
+ // We use NonRandomizedStringEqualityComparer as default comparer as it doesnt use the randomized string hashing which
+ // keeps the performance not affected till we hit collision threshold and then we switch to the comparer which is using
+ // randomized string hashing.
+ [Serializable] // Required for compatibility with .NET Core 2.0 as we exposed the NonRandomizedStringEqualityComparer inside the serialization blob
+ // Needs to be public to support binary serialization compatibility
+ public sealed class NonRandomizedStringEqualityComparer : EqualityComparer<string>, ISerializable
+ {
+ internal static new IEqualityComparer<string> Default { get; } = new NonRandomizedStringEqualityComparer();
+
+ private NonRandomizedStringEqualityComparer() { }
+
+ // This is used by the serialization engine.
+ private NonRandomizedStringEqualityComparer(SerializationInfo information, StreamingContext context) { }
+
+ public sealed override bool Equals(string x, string y) => string.Equals(x, y);
+
+ public sealed override int GetHashCode(string obj) => obj?.GetLegacyNonRandomizedHashCode() ?? 0;
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ // We are doing this to stay compatible with .NET Framework.
+ info.SetType(typeof(GenericEqualityComparer<string>));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs
new file mode 100644
index 0000000000..72da4a9e19
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/ValueListBuilder.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ internal ref partial struct ValueListBuilder<T>
+ {
+ private Span<T> _span;
+ private T[] _arrayFromPool;
+ private int _pos;
+
+ public ValueListBuilder(Span<T> initialSpan)
+ {
+ _span = initialSpan;
+ _arrayFromPool = null;
+ _pos = 0;
+ }
+
+ public int Length => _pos;
+
+ public ref T this[int index]
+ {
+ get
+ {
+ Debug.Assert(index < _pos);
+ return ref _span[index];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(T item)
+ {
+ int pos = _pos;
+ if (pos >= _span.Length)
+ Grow();
+
+ _span[pos] = item;
+ _pos = pos + 1;
+ }
+
+ public ReadOnlySpan<T> AsSpan()
+ {
+ return _span.Slice(0, _pos);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ if (_arrayFromPool != null)
+ {
+ ArrayPool<T>.Shared.Return(_arrayFromPool);
+ _arrayFromPool = null;
+ }
+ }
+
+ private void Grow()
+ {
+ T[] array = ArrayPool<T>.Shared.Rent(_span.Length * 2);
+
+ bool success = _span.TryCopyTo(array);
+ Debug.Assert(success);
+
+ T[] toReturn = _arrayFromPool;
+ _span = _arrayFromPool = array;
+ if (toReturn != null)
+ {
+ ArrayPool<T>.Shared.Return(toReturn);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/HashHelpers.SerializationInfoTable.cs b/src/System.Private.CoreLib/shared/System/Collections/HashHelpers.SerializationInfoTable.cs
new file mode 100644
index 0000000000..d91dfd878e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/HashHelpers.SerializationInfoTable.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Used by Hashtable and Dictionary's SeralizationInfo .ctor's to store the SeralizationInfo
+// object until OnDeserialization is called.
+
+using System.Threading;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+
+namespace System.Collections
+{
+ internal static partial class HashHelpers
+ {
+ private static ConditionalWeakTable<object, SerializationInfo> s_serializationInfoTable;
+
+ public static ConditionalWeakTable<object, SerializationInfo> SerializationInfoTable
+ {
+ get
+ {
+ if (s_serializationInfoTable == null)
+ Interlocked.CompareExchange(ref s_serializationInfoTable, new ConditionalWeakTable<object, SerializationInfo>(), null);
+
+ return s_serializationInfoTable;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Collections/HashHelpers.cs b/src/System.Private.CoreLib/shared/System/Collections/HashHelpers.cs
new file mode 100644
index 0000000000..ba2c75c89c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/HashHelpers.cs
@@ -0,0 +1,92 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System.Diagnostics;
+
+namespace System.Collections
+{
+ internal static partial class HashHelpers
+ {
+ public const int HashCollisionThreshold = 100;
+
+ // This is the maximum prime smaller than Array.MaxArrayLength
+ public const int MaxPrimeArrayLength = 0x7FEFFFFD;
+
+ public const int HashPrime = 101;
+
+ // Table of prime numbers to use as hash table sizes.
+ // A typical resize algorithm would pick the smallest prime number in this array
+ // that is larger than twice the previous capacity.
+ // Suppose our Hashtable currently has capacity x and enough elements are added
+ // such that a resize needs to occur. Resizing first computes 2x then finds the
+ // first prime in the table greater than 2x, i.e. if primes are ordered
+ // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n.
+ // Doubling is important for preserving the asymptotic complexity of the
+ // hashtable operations such as add. Having a prime guarantees that double
+ // hashing does not lead to infinite loops. IE, your hash function will be
+ // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime.
+ // We prefer the low computation costs of higher prime numbers over the increased
+ // memory allocation of a fixed prime number i.e. when right sizing a HashSet.
+ public static readonly int[] primes = {
+ 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
+ 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
+ 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
+ 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
+ 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 };
+
+ public static bool IsPrime(int candidate)
+ {
+ if ((candidate & 1) != 0)
+ {
+ int limit = (int)Math.Sqrt(candidate);
+ for (int divisor = 3; divisor <= limit; divisor += 2)
+ {
+ if ((candidate % divisor) == 0)
+ return false;
+ }
+ return true;
+ }
+ return (candidate == 2);
+ }
+
+ public static int GetPrime(int min)
+ {
+ if (min < 0)
+ throw new ArgumentException(SR.Arg_HTCapacityOverflow);
+
+ for (int i = 0; i < primes.Length; i++)
+ {
+ int prime = primes[i];
+ if (prime >= min)
+ return prime;
+ }
+
+ //outside of our predefined table.
+ //compute the hard way.
+ for (int i = (min | 1); i < int.MaxValue; i += 2)
+ {
+ if (IsPrime(i) && ((i - 1) % HashPrime != 0))
+ return i;
+ }
+ return min;
+ }
+
+ // Returns size of hashtable to grow to.
+ public static int ExpandPrime(int oldSize)
+ {
+ int newSize = 2 * oldSize;
+
+ // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow.
+ // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
+ if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
+ {
+ Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength");
+ return MaxPrimeArrayLength;
+ }
+
+ return GetPrime(newSize);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/Hashtable.cs b/src/System.Private.CoreLib/shared/System/Collections/Hashtable.cs
new file mode 100644
index 0000000000..1ee12138f8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/Hashtable.cs
@@ -0,0 +1,1650 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Class: Hashtable
+**
+** Purpose: Represents a collection of key/value pairs
+** that are organized based on the hash code
+** of the key.
+**
+===========================================================*/
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace System.Collections
+{
+ // The Hashtable class represents a dictionary of associated keys and values
+ // with constant lookup time.
+ //
+ // Objects used as keys in a hashtable must implement the GetHashCode
+ // and Equals methods (or they can rely on the default implementations
+ // inherited from Object if key equality is simply reference
+ // equality). Furthermore, the GetHashCode and Equals methods of
+ // a key object must produce the same results given the same parameters for the
+ // entire time the key is present in the hashtable. In practical terms, this
+ // means that key objects should be immutable, at least for the time they are
+ // used as keys in a hashtable.
+ //
+ // When entries are added to a hashtable, they are placed into
+ // buckets based on the hashcode of their keys. Subsequent lookups of
+ // keys will use the hashcode of the keys to only search a particular bucket,
+ // thus substantially reducing the number of key comparisons required to find
+ // an entry. A hashtable's maximum load factor, which can be specified
+ // when the hashtable is instantiated, determines the maximum ratio of
+ // hashtable entries to hashtable buckets. Smaller load factors cause faster
+ // average lookup times at the cost of increased memory consumption. The
+ // default maximum load factor of 1.0 generally provides the best balance
+ // between speed and size. As entries are added to a hashtable, the hashtable's
+ // actual load factor increases, and when the actual load factor reaches the
+ // maximum load factor value, the number of buckets in the hashtable is
+ // automatically increased by approximately a factor of two (to be precise, the
+ // number of hashtable buckets is increased to the smallest prime number that
+ // is larger than twice the current number of hashtable buckets).
+ //
+ // Each object provides their own hash function, accessed by calling
+ // GetHashCode(). However, one can write their own object
+ // implementing IEqualityComparer and pass it to a constructor on
+ // the Hashtable. That hash function (and the equals method on the
+ // IEqualityComparer) would be used for all objects in the table.
+ //
+ [DebuggerTypeProxy(typeof(System.Collections.Hashtable.HashtableDebugView))]
+ [DebuggerDisplay("Count = {Count}")]
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Hashtable : IDictionary, ISerializable, IDeserializationCallback, ICloneable
+ {
+ /*
+ This Hashtable uses double hashing. There are hashsize buckets in the
+ table, and each bucket can contain 0 or 1 element. We use a bit to mark
+ whether there's been a collision when we inserted multiple elements
+ (ie, an inserted item was hashed at least a second time and we probed
+ this bucket, but it was already in use). Using the collision bit, we
+ can terminate lookups & removes for elements that aren't in the hash
+ table more quickly. We steal the most significant bit from the hash code
+ to store the collision bit.
+
+ Our hash function is of the following form:
+
+ h(key, n) = h1(key) + n*h2(key)
+
+ where n is the number of times we've hit a collided bucket and rehashed
+ (on this particular lookup). Here are our hash functions:
+
+ h1(key) = GetHash(key); // default implementation calls key.GetHashCode();
+ h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1));
+
+ The h1 can return any number. h2 must return a number between 1 and
+ hashsize - 1 that is relatively prime to hashsize (not a problem if
+ hashsize is prime). (Knuth's Art of Computer Programming, Vol. 3, p. 528-9)
+ If this is true, then we are guaranteed to visit every bucket in exactly
+ hashsize probes, since the least common multiple of hashsize and h2(key)
+ will be hashsize * h2(key). (This is the first number where adding h2 to
+ h1 mod hashsize will be 0 and we will search the same bucket twice).
+
+ We previously used a different h2(key, n) that was not constant. That is a
+ horrifically bad idea, unless you can prove that series will never produce
+ any identical numbers that overlap when you mod them by hashsize, for all
+ subranges from i to i+hashsize, for all i. It's not worth investigating,
+ since there was no clear benefit from using that hash function, and it was
+ broken.
+
+ For efficiency reasons, we've implemented this by storing h1 and h2 in a
+ temporary, and setting a variable called seed equal to h1. We do a probe,
+ and if we collided, we simply add h2 to seed each time through the loop.
+
+ A good test for h2() is to subclass Hashtable, provide your own implementation
+ of GetHash() that returns a constant, then add many items to the hash table.
+ Make sure Count equals the number of items you inserted.
+
+ Note that when we remove an item from the hash table, we set the key
+ equal to buckets, if there was a collision in this bucket. Otherwise
+ we'd either wipe out the collision bit, or we'd still have an item in
+ the hash table.
+
+ --
+ */
+
+ private const Int32 InitialSize = 3;
+
+ private const String LoadFactorName = "LoadFactor"; // Do not rename (binary serialization)
+ private const String VersionName = "Version"; // Do not rename (binary serialization)
+ private const String ComparerName = "Comparer"; // Do not rename (binary serialization)
+ private const String HashCodeProviderName = "HashCodeProvider"; // Do not rename (binary serialization)
+ private const String HashSizeName = "HashSize"; // Must save buckets.Length. Do not rename (binary serialization)
+ private const String KeysName = "Keys"; // Do not rename (binary serialization)
+ private const String ValuesName = "Values"; // Do not rename (binary serialization)
+ private const String KeyComparerName = "KeyComparer"; // Do not rename (binary serialization)
+
+ // Deleted entries have their key set to buckets
+
+ // The hash table data.
+ // This cannot be serialized
+ private struct bucket
+ {
+ public Object key;
+ public Object val;
+ public int hash_coll; // Store hash code; sign bit means there was a collision.
+ }
+
+ private bucket[] _buckets;
+
+ // The total number of entries in the hash table.
+ private int _count;
+
+ // The total number of collision bits set in the hashtable
+ private int _occupancy;
+
+ private int _loadsize;
+ private float _loadFactor;
+
+ private volatile int _version;
+ private volatile bool _isWriterInProgress;
+
+ private ICollection _keys;
+ private ICollection _values;
+
+ private IEqualityComparer _keycomparer;
+ private Object _syncRoot;
+
+ [Obsolete("Please use EqualityComparer property.")]
+ protected IHashCodeProvider hcp
+ {
+ get
+ {
+ if (_keycomparer is CompatibleComparer)
+ {
+ return ((CompatibleComparer)_keycomparer).HashCodeProvider;
+ }
+ else if (_keycomparer == null)
+ {
+ return null;
+ }
+ else
+ {
+ throw new ArgumentException(SR.Arg_CannotMixComparisonInfrastructure);
+ }
+ }
+ set
+ {
+ if (_keycomparer is CompatibleComparer)
+ {
+ CompatibleComparer keyComparer = (CompatibleComparer)_keycomparer;
+ _keycomparer = new CompatibleComparer(value, keyComparer.Comparer);
+ }
+ else if (_keycomparer == null)
+ {
+ _keycomparer = new CompatibleComparer(value, (IComparer)null);
+ }
+ else
+ {
+ throw new ArgumentException(SR.Arg_CannotMixComparisonInfrastructure);
+ }
+ }
+ }
+
+ [Obsolete("Please use KeyComparer properties.")]
+ protected IComparer comparer
+ {
+ get
+ {
+ if (_keycomparer is CompatibleComparer)
+ {
+ return ((CompatibleComparer)_keycomparer).Comparer;
+ }
+ else if (_keycomparer == null)
+ {
+ return null;
+ }
+ else
+ {
+ throw new ArgumentException(SR.Arg_CannotMixComparisonInfrastructure);
+ }
+ }
+ set
+ {
+ if (_keycomparer is CompatibleComparer)
+ {
+ CompatibleComparer keyComparer = (CompatibleComparer)_keycomparer;
+ _keycomparer = new CompatibleComparer(keyComparer.HashCodeProvider, value);
+ }
+ else if (_keycomparer == null)
+ {
+ _keycomparer = new CompatibleComparer((IHashCodeProvider)null, value);
+ }
+ else
+ {
+ throw new ArgumentException(SR.Arg_CannotMixComparisonInfrastructure);
+ }
+ }
+ }
+
+
+ protected IEqualityComparer EqualityComparer
+ {
+ get
+ {
+ return _keycomparer;
+ }
+ }
+
+ // Note: this constructor is a bogus constructor that does nothing
+ // and is for use only with SyncHashtable.
+ internal Hashtable(bool trash)
+ {
+ }
+
+ // Constructs a new hashtable. The hashtable is created with an initial
+ // capacity of zero and a load factor of 1.0.
+ public Hashtable() : this(0, 1.0f)
+ {
+ }
+
+ // Constructs a new hashtable with the given initial capacity and a load
+ // factor of 1.0. The capacity argument serves as an indication of
+ // the number of entries the hashtable will contain. When this number (or
+ // an approximation) is known, specifying it in the constructor can
+ // eliminate a number of resizing operations that would otherwise be
+ // performed when elements are added to the hashtable.
+ //
+ public Hashtable(int capacity) : this(capacity, 1.0f)
+ {
+ }
+
+ // Constructs a new hashtable with the given initial capacity and load
+ // factor. The capacity argument serves as an indication of the
+ // number of entries the hashtable will contain. When this number (or an
+ // approximation) is known, specifying it in the constructor can eliminate
+ // a number of resizing operations that would otherwise be performed when
+ // elements are added to the hashtable. The loadFactor argument
+ // indicates the maximum ratio of hashtable entries to hashtable buckets.
+ // Smaller load factors cause faster average lookup times at the cost of
+ // increased memory consumption. A load factor of 1.0 generally provides
+ // the best balance between speed and size.
+ //
+ public Hashtable(int capacity, float loadFactor)
+ {
+ if (capacity < 0)
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (!(loadFactor >= 0.1f && loadFactor <= 1.0f))
+ throw new ArgumentOutOfRangeException(nameof(loadFactor), SR.Format(SR.ArgumentOutOfRange_HashtableLoadFactor, .1, 1.0));
+
+ // Based on perf work, .72 is the optimal load factor for this table.
+ _loadFactor = 0.72f * loadFactor;
+
+ double rawsize = capacity / _loadFactor;
+ if (rawsize > Int32.MaxValue)
+ throw new ArgumentException(SR.Arg_HTCapacityOverflow, nameof(capacity));
+
+ // Avoid awfully small sizes
+ int hashsize = (rawsize > InitialSize) ? HashHelpers.GetPrime((int)rawsize) : InitialSize;
+ _buckets = new bucket[hashsize];
+
+ _loadsize = (int)(_loadFactor * hashsize);
+ _isWriterInProgress = false;
+ // Based on the current algorithm, loadsize must be less than hashsize.
+ Debug.Assert(_loadsize < hashsize, "Invalid hashtable loadsize!");
+ }
+
+ public Hashtable(int capacity, float loadFactor, IEqualityComparer equalityComparer) : this(capacity, loadFactor)
+ {
+ _keycomparer = equalityComparer;
+ }
+
+ [Obsolete("Please use Hashtable(IEqualityComparer) instead.")]
+ public Hashtable(IHashCodeProvider hcp, IComparer comparer)
+ : this(0, 1.0f, hcp, comparer)
+ {
+ }
+
+ public Hashtable(IEqualityComparer equalityComparer) : this(0, 1.0f, equalityComparer)
+ {
+ }
+
+ [Obsolete("Please use Hashtable(int, IEqualityComparer) instead.")]
+ public Hashtable(int capacity, IHashCodeProvider hcp, IComparer comparer)
+ : this(capacity, 1.0f, hcp, comparer)
+ {
+ }
+
+ public Hashtable(int capacity, IEqualityComparer equalityComparer)
+ : this(capacity, 1.0f, equalityComparer)
+ {
+ }
+
+ // Constructs a new hashtable containing a copy of the entries in the given
+ // dictionary. The hashtable is created with a load factor of 1.0.
+ //
+ public Hashtable(IDictionary d) : this(d, 1.0f)
+ {
+ }
+
+ // Constructs a new hashtable containing a copy of the entries in the given
+ // dictionary. The hashtable is created with the given load factor.
+ //
+ public Hashtable(IDictionary d, float loadFactor)
+ : this(d, loadFactor, (IEqualityComparer)null)
+ {
+ }
+
+ [Obsolete("Please use Hashtable(IDictionary, IEqualityComparer) instead.")]
+ public Hashtable(IDictionary d, IHashCodeProvider hcp, IComparer comparer)
+ : this(d, 1.0f, hcp, comparer)
+ {
+ }
+
+ public Hashtable(IDictionary d, IEqualityComparer equalityComparer)
+ : this(d, 1.0f, equalityComparer)
+ {
+ }
+
+ [Obsolete("Please use Hashtable(int, float, IEqualityComparer) instead.")]
+ public Hashtable(int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer)
+ : this(capacity, loadFactor)
+ {
+ if (hcp != null || comparer != null)
+ {
+ _keycomparer = new CompatibleComparer(hcp, comparer);
+ }
+ }
+
+ [Obsolete("Please use Hashtable(IDictionary, float, IEqualityComparer) instead.")]
+ public Hashtable(IDictionary d, float loadFactor, IHashCodeProvider hcp, IComparer comparer)
+ : this((d != null ? d.Count : 0), loadFactor, hcp, comparer)
+ {
+ if (d == null)
+ throw new ArgumentNullException(nameof(d), SR.ArgumentNull_Dictionary);
+
+ IDictionaryEnumerator e = d.GetEnumerator();
+ while (e.MoveNext())
+ Add(e.Key, e.Value);
+ }
+
+ public Hashtable(IDictionary d, float loadFactor, IEqualityComparer equalityComparer)
+ : this((d != null ? d.Count : 0), loadFactor, equalityComparer)
+ {
+ if (d == null)
+ throw new ArgumentNullException(nameof(d), SR.ArgumentNull_Dictionary);
+
+ IDictionaryEnumerator e = d.GetEnumerator();
+ while (e.MoveNext())
+ Add(e.Key, e.Value);
+ }
+
+ protected Hashtable(SerializationInfo info, StreamingContext context)
+ {
+ //We can't do anything with the keys and values until the entire graph has been deserialized
+ //and we have a reasonable estimate that GetHashCode is not going to fail. For the time being,
+ //we'll just cache this. The graph is not valid until OnDeserialization has been called.
+ HashHelpers.SerializationInfoTable.Add(this, info);
+ }
+
+ // ?InitHash? is basically an implementation of classic DoubleHashing (see http://en.wikipedia.org/wiki/Double_hashing)
+ //
+ // 1) The only ?correctness? requirement is that the ?increment? used to probe
+ // a. Be non-zero
+ // b. Be relatively prime to the table size ?hashSize?. (This is needed to insure you probe all entries in the table before you ?wrap? and visit entries already probed)
+ // 2) Because we choose table sizes to be primes, we just need to insure that the increment is 0 < incr < hashSize
+ //
+ // Thus this function would work: Incr = 1 + (seed % (hashSize-1))
+ //
+ // While this works well for ?uniformly distributed? keys, in practice, non-uniformity is common.
+ // In particular in practice we can see ?mostly sequential? where you get long clusters of keys that ?pack?.
+ // To avoid bad behavior you want it to be the case that the increment is ?large? even for ?small? values (because small
+ // values tend to happen more in practice). Thus we multiply ?seed? by a number that will make these small values
+ // bigger (and not hurt large values). We picked HashPrime (101) because it was prime, and if ?hashSize-1? is not a multiple of HashPrime
+ // (enforced in GetPrime), then incr has the potential of being every value from 1 to hashSize-1. The choice was largely arbitrary.
+ //
+ // Computes the hash function: H(key, i) = h1(key) + i*h2(key, hashSize).
+ // The out parameter seed is h1(key), while the out parameter
+ // incr is h2(key, hashSize). Callers of this function should
+ // add incr each time through a loop.
+ private uint InitHash(Object key, int hashsize, out uint seed, out uint incr)
+ {
+ // Hashcode must be positive. Also, we must not use the sign bit, since
+ // that is used for the collision bit.
+ uint hashcode = (uint)GetHash(key) & 0x7FFFFFFF;
+ seed = (uint)hashcode;
+ // Restriction: incr MUST be between 1 and hashsize - 1, inclusive for
+ // the modular arithmetic to work correctly. This guarantees you'll
+ // visit every bucket in the table exactly once within hashsize
+ // iterations. Violate this and it'll cause obscure bugs forever.
+ // If you change this calculation for h2(key), update putEntry too!
+ incr = (uint)(1 + ((seed * HashHelpers.HashPrime) % ((uint)hashsize - 1)));
+ return hashcode;
+ }
+
+ // Adds an entry with the given key and value to this hashtable. An
+ // ArgumentException is thrown if the key is null or if the key is already
+ // present in the hashtable.
+ //
+ public virtual void Add(Object key, Object value)
+ {
+ Insert(key, value, true);
+ }
+
+ // Removes all entries from this hashtable.
+ public virtual void Clear()
+ {
+ Debug.Assert(!_isWriterInProgress, "Race condition detected in usages of Hashtable - multiple threads appear to be writing to a Hashtable instance simultaneously! Don't do that - use Hashtable.Synchronized.");
+
+ if (_count == 0 && _occupancy == 0)
+ return;
+
+ _isWriterInProgress = true;
+ for (int i = 0; i < _buckets.Length; i++)
+ {
+ _buckets[i].hash_coll = 0;
+ _buckets[i].key = null;
+ _buckets[i].val = null;
+ }
+
+ _count = 0;
+ _occupancy = 0;
+ UpdateVersion();
+ _isWriterInProgress = false;
+ }
+
+ // Clone returns a virtually identical copy of this hash table. This does
+ // a shallow copy - the Objects in the table aren't cloned, only the references
+ // to those Objects.
+ public virtual Object Clone()
+ {
+ bucket[] lbuckets = _buckets;
+ Hashtable ht = new Hashtable(_count, _keycomparer);
+ ht._version = _version;
+ ht._loadFactor = _loadFactor;
+ ht._count = 0;
+
+ int bucket = lbuckets.Length;
+ while (bucket > 0)
+ {
+ bucket--;
+ Object keyv = lbuckets[bucket].key;
+ if ((keyv != null) && (keyv != lbuckets))
+ {
+ ht[keyv] = lbuckets[bucket].val;
+ }
+ }
+
+ return ht;
+ }
+
+ // Checks if this hashtable contains the given key.
+ public virtual bool Contains(Object key)
+ {
+ return ContainsKey(key);
+ }
+
+ // Checks if this hashtable contains an entry with the given key. This is
+ // an O(1) operation.
+ //
+ public virtual bool ContainsKey(Object key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+
+ uint seed;
+ uint incr;
+ // Take a snapshot of buckets, in case another thread resizes table
+ bucket[] lbuckets = _buckets;
+ uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr);
+ int ntry = 0;
+
+ bucket b;
+ int bucketNumber = (int)(seed % (uint)lbuckets.Length);
+ do
+ {
+ b = lbuckets[bucketNumber];
+ if (b.key == null)
+ {
+ return false;
+ }
+ if (((b.hash_coll & 0x7FFFFFFF) == hashcode) &&
+ KeyEquals(b.key, key))
+ return true;
+ bucketNumber = (int)(((long)bucketNumber + incr) % (uint)lbuckets.Length);
+ } while (b.hash_coll < 0 && ++ntry < lbuckets.Length);
+ return false;
+ }
+
+
+
+ // Checks if this hashtable contains an entry with the given value. The
+ // values of the entries of the hashtable are compared to the given value
+ // using the Object.Equals method. This method performs a linear
+ // search and is thus be substantially slower than the ContainsKey
+ // method.
+ //
+ public virtual bool ContainsValue(Object value)
+ {
+ if (value == null)
+ {
+ for (int i = _buckets.Length; --i >= 0;)
+ {
+ if (_buckets[i].key != null && _buckets[i].key != _buckets && _buckets[i].val == null)
+ return true;
+ }
+ }
+ else
+ {
+ for (int i = _buckets.Length; --i >= 0;)
+ {
+ Object val = _buckets[i].val;
+ if (val != null && val.Equals(value))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Copies the keys of this hashtable to a given array starting at a given
+ // index. This method is used by the implementation of the CopyTo method in
+ // the KeyCollection class.
+ private void CopyKeys(Array array, int arrayIndex)
+ {
+ Debug.Assert(array != null);
+ Debug.Assert(array.Rank == 1);
+
+ bucket[] lbuckets = _buckets;
+ for (int i = lbuckets.Length; --i >= 0;)
+ {
+ Object keyv = lbuckets[i].key;
+ if ((keyv != null) && (keyv != _buckets))
+ {
+ array.SetValue(keyv, arrayIndex++);
+ }
+ }
+ }
+
+ // Copies the keys of this hashtable to a given array starting at a given
+ // index. This method is used by the implementation of the CopyTo method in
+ // the KeyCollection class.
+ private void CopyEntries(Array array, int arrayIndex)
+ {
+ Debug.Assert(array != null);
+ Debug.Assert(array.Rank == 1);
+
+ bucket[] lbuckets = _buckets;
+ for (int i = lbuckets.Length; --i >= 0;)
+ {
+ Object keyv = lbuckets[i].key;
+ if ((keyv != null) && (keyv != _buckets))
+ {
+ DictionaryEntry entry = new DictionaryEntry(keyv, lbuckets[i].val);
+ array.SetValue(entry, arrayIndex++);
+ }
+ }
+ }
+
+ // Copies the values in this hash table to an array at
+ // a given index. Note that this only copies values, and not keys.
+ public virtual void CopyTo(Array array, int arrayIndex)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Array);
+ if (array.Rank != 1)
+ throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - arrayIndex < Count)
+ throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
+
+ CopyEntries(array, arrayIndex);
+ }
+
+ // Copies the values in this Hashtable to an KeyValuePairs array.
+ // KeyValuePairs is different from Dictionary Entry in that it has special
+ // debugger attributes on its fields.
+
+ internal virtual KeyValuePairs[] ToKeyValuePairsArray()
+ {
+ KeyValuePairs[] array = new KeyValuePairs[_count];
+ int index = 0;
+ bucket[] lbuckets = _buckets;
+ for (int i = lbuckets.Length; --i >= 0;)
+ {
+ Object keyv = lbuckets[i].key;
+ if ((keyv != null) && (keyv != _buckets))
+ {
+ array[index++] = new KeyValuePairs(keyv, lbuckets[i].val);
+ }
+ }
+
+ return array;
+ }
+
+
+ // Copies the values of this hashtable to a given array starting at a given
+ // index. This method is used by the implementation of the CopyTo method in
+ // the ValueCollection class.
+ private void CopyValues(Array array, int arrayIndex)
+ {
+ Debug.Assert(array != null);
+ Debug.Assert(array.Rank == 1);
+
+ bucket[] lbuckets = _buckets;
+ for (int i = lbuckets.Length; --i >= 0;)
+ {
+ Object keyv = lbuckets[i].key;
+ if ((keyv != null) && (keyv != _buckets))
+ {
+ array.SetValue(lbuckets[i].val, arrayIndex++);
+ }
+ }
+ }
+
+ // Returns the value associated with the given key. If an entry with the
+ // given key is not found, the returned value is null.
+ //
+ public virtual Object this[Object key]
+ {
+ get
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+
+ uint seed;
+ uint incr;
+
+
+ // Take a snapshot of buckets, in case another thread does a resize
+ bucket[] lbuckets = _buckets;
+ uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr);
+ int ntry = 0;
+
+ bucket b;
+ int bucketNumber = (int)(seed % (uint)lbuckets.Length);
+ do
+ {
+ int currentversion;
+
+ // A read operation on hashtable has three steps:
+ // (1) calculate the hash and find the slot number.
+ // (2) compare the hashcode, if equal, go to step 3. Otherwise end.
+ // (3) compare the key, if equal, go to step 4. Otherwise end.
+ // (4) return the value contained in the bucket.
+ // After step 3 and before step 4. A writer can kick in a remove the old item and add a new one
+ // in the same bucket. So in the reader we need to check if the hash table is modified during above steps.
+ //
+ // Writers (Insert, Remove, Clear) will set 'isWriterInProgress' flag before it starts modifying
+ // the hashtable and will ckear the flag when it is done. When the flag is cleared, the 'version'
+ // will be increased. We will repeat the reading if a writer is in progress or done with the modification
+ // during the read.
+ //
+ // Our memory model guarantee if we pick up the change in bucket from another processor,
+ // we will see the 'isWriterProgress' flag to be true or 'version' is changed in the reader.
+ //
+ SpinWait spin = new SpinWait();
+ while (true)
+ {
+ // this is volatile read, following memory accesses can not be moved ahead of it.
+ currentversion = _version;
+ b = lbuckets[bucketNumber];
+
+ if (!_isWriterInProgress && (currentversion == _version))
+ break;
+
+ spin.SpinOnce();
+ }
+
+ if (b.key == null)
+ {
+ return null;
+ }
+ if (((b.hash_coll & 0x7FFFFFFF) == hashcode) &&
+ KeyEquals(b.key, key))
+ return b.val;
+ bucketNumber = (int)(((long)bucketNumber + incr) % (uint)lbuckets.Length);
+ } while (b.hash_coll < 0 && ++ntry < lbuckets.Length);
+ return null;
+ }
+
+ set
+ {
+ Insert(key, value, false);
+ }
+ }
+
+ // Increases the bucket count of this hashtable. This method is called from
+ // the Insert method when the actual load factor of the hashtable reaches
+ // the upper limit specified when the hashtable was constructed. The number
+ // of buckets in the hashtable is increased to the smallest prime number
+ // that is larger than twice the current number of buckets, and the entries
+ // in the hashtable are redistributed into the new buckets using the cached
+ // hashcodes.
+ private void expand()
+ {
+ int rawsize = HashHelpers.ExpandPrime(_buckets.Length);
+ rehash(rawsize);
+ }
+
+ // We occasionally need to rehash the table to clean up the collision bits.
+ private void rehash()
+ {
+ rehash(_buckets.Length);
+ }
+
+ private void UpdateVersion()
+ {
+ // Version might become negative when version is Int32.MaxValue, but the oddity will be still be correct.
+ // So we don't need to special case this.
+ _version++;
+ }
+
+ private void rehash(int newsize)
+ {
+ // reset occupancy
+ _occupancy = 0;
+
+ // Don't replace any internal state until we've finished adding to the
+ // new bucket[]. This serves two purposes:
+ // 1) Allow concurrent readers to see valid hashtable contents
+ // at all times
+ // 2) Protect against an OutOfMemoryException while allocating this
+ // new bucket[].
+ bucket[] newBuckets = new bucket[newsize];
+
+ // rehash table into new buckets
+ int nb;
+ for (nb = 0; nb < _buckets.Length; nb++)
+ {
+ bucket oldb = _buckets[nb];
+ if ((oldb.key != null) && (oldb.key != _buckets))
+ {
+ int hashcode = oldb.hash_coll & 0x7FFFFFFF;
+ putEntry(newBuckets, oldb.key, oldb.val, hashcode);
+ }
+ }
+
+ // New bucket[] is good to go - replace buckets and other internal state.
+ _isWriterInProgress = true;
+ _buckets = newBuckets;
+ _loadsize = (int)(_loadFactor * newsize);
+ UpdateVersion();
+ _isWriterInProgress = false;
+ // minimum size of hashtable is 3 now and maximum loadFactor is 0.72 now.
+ Debug.Assert(_loadsize < newsize, "Our current implementation means this is not possible.");
+ }
+
+ // Returns an enumerator for this hashtable.
+ // If modifications made to the hashtable while an enumeration is
+ // in progress, the MoveNext and Current methods of the
+ // enumerator will throw an exception.
+ //
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return new HashtableEnumerator(this, HashtableEnumerator.DictEntry);
+ }
+
+ // Returns a dictionary enumerator for this hashtable.
+ // If modifications made to the hashtable while an enumeration is
+ // in progress, the MoveNext and Current methods of the
+ // enumerator will throw an exception.
+ //
+ public virtual IDictionaryEnumerator GetEnumerator()
+ {
+ return new HashtableEnumerator(this, HashtableEnumerator.DictEntry);
+ }
+
+ // Internal method to get the hash code for an Object. This will call
+ // GetHashCode() on each object if you haven't provided an IHashCodeProvider
+ // instance. Otherwise, it calls hcp.GetHashCode(obj).
+ protected virtual int GetHash(Object key)
+ {
+ if (_keycomparer != null)
+ return _keycomparer.GetHashCode(key);
+ return key.GetHashCode();
+ }
+
+ // Is this Hashtable read-only?
+ public virtual bool IsReadOnly
+ {
+ get { return false; }
+ }
+
+ public virtual bool IsFixedSize
+ {
+ get { return false; }
+ }
+
+ // Is this Hashtable synchronized? See SyncRoot property
+ public virtual bool IsSynchronized
+ {
+ get { return false; }
+ }
+
+ // Internal method to compare two keys. If you have provided an IComparer
+ // instance in the constructor, this method will call comparer.Compare(item, key).
+ // Otherwise, it will call item.Equals(key).
+ //
+ protected virtual bool KeyEquals(Object item, Object key)
+ {
+ Debug.Assert(key != null, "key can't be null here!");
+ if (Object.ReferenceEquals(_buckets, item))
+ {
+ return false;
+ }
+
+ if (Object.ReferenceEquals(item, key))
+ return true;
+
+ if (_keycomparer != null)
+ return _keycomparer.Equals(item, key);
+ return item == null ? false : item.Equals(key);
+ }
+
+ // Returns a collection representing the keys of this hashtable. The order
+ // in which the returned collection represents the keys is unspecified, but
+ // it is guaranteed to be buckets = newBuckets; the same order in which a collection returned by
+ // GetValues represents the values of the hashtable.
+ //
+ // The returned collection is live in the sense that any changes
+ // to the hash table are reflected in this collection. It is not
+ // a static copy of all the keys in the hash table.
+ //
+ public virtual ICollection Keys
+ {
+ get
+ {
+ if (_keys == null)
+ _keys = new KeyCollection(this);
+ return _keys;
+ }
+ }
+
+ // Returns a collection representing the values of this hashtable. The
+ // order in which the returned collection represents the values is
+ // unspecified, but it is guaranteed to be the same order in which a
+ // collection returned by GetKeys represents the keys of the
+ // hashtable.
+ //
+ // The returned collection is live in the sense that any changes
+ // to the hash table are reflected in this collection. It is not
+ // a static copy of all the keys in the hash table.
+ //
+ public virtual ICollection Values
+ {
+ get
+ {
+ if (_values == null)
+ _values = new ValueCollection(this);
+ return _values;
+ }
+ }
+
+ // Inserts an entry into this hashtable. This method is called from the Set
+ // and Add methods. If the add parameter is true and the given key already
+ // exists in the hashtable, an exception is thrown.
+ private void Insert(Object key, Object nvalue, bool add)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+
+ if (_count >= _loadsize)
+ {
+ expand();
+ }
+ else if (_occupancy > _loadsize && _count > 100)
+ {
+ rehash();
+ }
+
+ uint seed;
+ uint incr;
+ // Assume we only have one thread writing concurrently. Modify
+ // buckets to contain new data, as long as we insert in the right order.
+ uint hashcode = InitHash(key, _buckets.Length, out seed, out incr);
+ int ntry = 0;
+ int emptySlotNumber = -1; // We use the empty slot number to cache the first empty slot. We chose to reuse slots
+ // create by remove that have the collision bit set over using up new slots.
+ int bucketNumber = (int)(seed % (uint)_buckets.Length);
+ do
+ {
+ // Set emptySlot number to current bucket if it is the first available bucket that we have seen
+ // that once contained an entry and also has had a collision.
+ // We need to search this entire collision chain because we have to ensure that there are no
+ // duplicate entries in the table.
+ if (emptySlotNumber == -1 && (_buckets[bucketNumber].key == _buckets) && (_buckets[bucketNumber].hash_coll < 0))//(((buckets[bucketNumber].hash_coll & unchecked(0x80000000))!=0)))
+ emptySlotNumber = bucketNumber;
+
+ // Insert the key/value pair into this bucket if this bucket is empty and has never contained an entry
+ // OR
+ // This bucket once contained an entry but there has never been a collision
+ if ((_buckets[bucketNumber].key == null) ||
+ (_buckets[bucketNumber].key == _buckets && ((_buckets[bucketNumber].hash_coll & unchecked(0x80000000)) == 0)))
+ {
+ // If we have found an available bucket that has never had a collision, but we've seen an available
+ // bucket in the past that has the collision bit set, use the previous bucket instead
+ if (emptySlotNumber != -1) // Reuse slot
+ bucketNumber = emptySlotNumber;
+
+ // We pretty much have to insert in this order. Don't set hash
+ // code until the value & key are set appropriately.
+ _isWriterInProgress = true;
+ _buckets[bucketNumber].val = nvalue;
+ _buckets[bucketNumber].key = key;
+ _buckets[bucketNumber].hash_coll |= (int)hashcode;
+ _count++;
+ UpdateVersion();
+ _isWriterInProgress = false;
+
+ return;
+ }
+
+ // The current bucket is in use
+ // OR
+ // it is available, but has had the collision bit set and we have already found an available bucket
+ if (((_buckets[bucketNumber].hash_coll & 0x7FFFFFFF) == hashcode) &&
+ KeyEquals(_buckets[bucketNumber].key, key))
+ {
+ if (add)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate__, _buckets[bucketNumber].key, key));
+ }
+ _isWriterInProgress = true;
+ _buckets[bucketNumber].val = nvalue;
+ UpdateVersion();
+ _isWriterInProgress = false;
+
+ return;
+ }
+
+ // The current bucket is full, and we have therefore collided. We need to set the collision bit
+ // unless we have remembered an available slot previously.
+ if (emptySlotNumber == -1)
+ {// We don't need to set the collision bit here since we already have an empty slot
+ if (_buckets[bucketNumber].hash_coll >= 0)
+ {
+ _buckets[bucketNumber].hash_coll |= unchecked((int)0x80000000);
+ _occupancy++;
+ }
+ }
+
+ bucketNumber = (int)(((long)bucketNumber + incr) % (uint)_buckets.Length);
+ } while (++ntry < _buckets.Length);
+
+ // This code is here if and only if there were no buckets without a collision bit set in the entire table
+ if (emptySlotNumber != -1)
+ {
+ // We pretty much have to insert in this order. Don't set hash
+ // code until the value & key are set appropriately.
+ _isWriterInProgress = true;
+ _buckets[emptySlotNumber].val = nvalue;
+ _buckets[emptySlotNumber].key = key;
+ _buckets[emptySlotNumber].hash_coll |= (int)hashcode;
+ _count++;
+ UpdateVersion();
+ _isWriterInProgress = false;
+
+ return;
+ }
+
+ // If you see this assert, make sure load factor & count are reasonable.
+ // Then verify that our double hash function (h2, described at top of file)
+ // meets the requirements described above. You should never see this assert.
+ Debug.Fail("hash table insert failed! Load factor too high, or our double hashing function is incorrect.");
+ throw new InvalidOperationException(SR.InvalidOperation_HashInsertFailed);
+ }
+
+ private void putEntry(bucket[] newBuckets, Object key, Object nvalue, int hashcode)
+ {
+ Debug.Assert(hashcode >= 0, "hashcode >= 0"); // make sure collision bit (sign bit) wasn't set.
+
+ uint seed = (uint)hashcode;
+ uint incr = unchecked((uint)(1 + ((seed * HashHelpers.HashPrime) % ((uint)newBuckets.Length - 1))));
+ int bucketNumber = (int)(seed % (uint)newBuckets.Length);
+ do
+ {
+ if ((newBuckets[bucketNumber].key == null) || (newBuckets[bucketNumber].key == _buckets))
+ {
+ newBuckets[bucketNumber].val = nvalue;
+ newBuckets[bucketNumber].key = key;
+ newBuckets[bucketNumber].hash_coll |= hashcode;
+ return;
+ }
+
+ if (newBuckets[bucketNumber].hash_coll >= 0)
+ {
+ newBuckets[bucketNumber].hash_coll |= unchecked((int)0x80000000);
+ _occupancy++;
+ }
+ bucketNumber = (int)(((long)bucketNumber + incr) % (uint)newBuckets.Length);
+ } while (true);
+ }
+
+ // Removes an entry from this hashtable. If an entry with the specified
+ // key exists in the hashtable, it is removed. An ArgumentException is
+ // thrown if the key is null.
+ //
+ public virtual void Remove(Object key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+
+ Debug.Assert(!_isWriterInProgress, "Race condition detected in usages of Hashtable - multiple threads appear to be writing to a Hashtable instance simultaneously! Don't do that - use Hashtable.Synchronized.");
+
+ uint seed;
+ uint incr;
+ // Assuming only one concurrent writer, write directly into buckets.
+ uint hashcode = InitHash(key, _buckets.Length, out seed, out incr);
+ int ntry = 0;
+
+ bucket b;
+ int bn = (int)(seed % (uint)_buckets.Length); // bucketNumber
+ do
+ {
+ b = _buckets[bn];
+ if (((b.hash_coll & 0x7FFFFFFF) == hashcode) &&
+ KeyEquals(b.key, key))
+ {
+ _isWriterInProgress = true;
+ // Clear hash_coll field, then key, then value
+ _buckets[bn].hash_coll &= unchecked((int)0x80000000);
+ if (_buckets[bn].hash_coll != 0)
+ {
+ _buckets[bn].key = _buckets;
+ }
+ else
+ {
+ _buckets[bn].key = null;
+ }
+ _buckets[bn].val = null; // Free object references sooner & simplify ContainsValue.
+ _count--;
+ UpdateVersion();
+ _isWriterInProgress = false;
+ return;
+ }
+ bn = (int)(((long)bn + incr) % (uint)_buckets.Length);
+ } while (b.hash_coll < 0 && ++ntry < _buckets.Length);
+ }
+
+ // Returns the object to synchronize on for this hash table.
+ public virtual Object SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);
+ }
+ return _syncRoot;
+ }
+ }
+
+ // Returns the number of associations in this hashtable.
+ //
+ public virtual int Count
+ {
+ get { return _count; }
+ }
+
+ // Returns a thread-safe wrapper for a Hashtable.
+ //
+ public static Hashtable Synchronized(Hashtable table)
+ {
+ if (table == null)
+ throw new ArgumentNullException(nameof(table));
+ return new SyncHashtable(table);
+ }
+
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ // This is imperfect - it only works well if all other writes are
+ // also using our synchronized wrapper. But it's still a good idea.
+ lock (SyncRoot)
+ {
+ // This method hasn't been fully tweaked to be safe for a concurrent writer.
+ int oldVersion = _version;
+ info.AddValue(LoadFactorName, _loadFactor);
+ info.AddValue(VersionName, _version);
+
+ //
+ // We need to maintain serialization compatibility with Everett and RTM.
+ // If the comparer is null or a compatible comparer, serialize Hashtable
+ // in a format that can be deserialized on Everett and RTM.
+ //
+ // Also, if the Hashtable is using randomized hashing, serialize the old
+ // view of the _keycomparer so perevious frameworks don't see the new types
+#pragma warning disable 618
+ IEqualityComparer keyComparerForSerilization = _keycomparer;
+
+ if (keyComparerForSerilization == null)
+ {
+ info.AddValue(ComparerName, null, typeof(IComparer));
+ info.AddValue(HashCodeProviderName, null, typeof(IHashCodeProvider));
+ }
+ else if (keyComparerForSerilization is CompatibleComparer)
+ {
+ CompatibleComparer c = keyComparerForSerilization as CompatibleComparer;
+ info.AddValue(ComparerName, c.Comparer, typeof(IComparer));
+ info.AddValue(HashCodeProviderName, c.HashCodeProvider, typeof(IHashCodeProvider));
+ }
+ else
+ {
+ info.AddValue(KeyComparerName, keyComparerForSerilization, typeof(IEqualityComparer));
+ }
+#pragma warning restore 618
+
+ info.AddValue(HashSizeName, _buckets.Length); //This is the length of the bucket array.
+ Object[] serKeys = new Object[_count];
+ Object[] serValues = new Object[_count];
+ CopyKeys(serKeys, 0);
+ CopyValues(serValues, 0);
+ info.AddValue(KeysName, serKeys, typeof(Object[]));
+ info.AddValue(ValuesName, serValues, typeof(Object[]));
+
+ // Explicitly check to see if anyone changed the Hashtable while we
+ // were serializing it. That's a race in their code.
+ if (_version != oldVersion)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ }
+ }
+ }
+
+ //
+ // DeserializationEvent Listener
+ //
+ public virtual void OnDeserialization(Object sender)
+ {
+ if (_buckets != null)
+ {
+ // Somebody had a dependency on this hashtable and fixed us up before the ObjectManager got to it.
+ return;
+ }
+
+ SerializationInfo siInfo;
+ HashHelpers.SerializationInfoTable.TryGetValue(this, out siInfo);
+
+ if (siInfo == null)
+ {
+ throw new SerializationException(SR.Serialization_InvalidOnDeser);
+ }
+
+ int hashsize = 0;
+ IComparer c = null;
+
+#pragma warning disable 618
+ IHashCodeProvider hcp = null;
+#pragma warning restore 618
+
+ Object[] serKeys = null;
+ Object[] serValues = null;
+
+ SerializationInfoEnumerator enumerator = siInfo.GetEnumerator();
+
+ while (enumerator.MoveNext())
+ {
+ switch (enumerator.Name)
+ {
+ case LoadFactorName:
+ _loadFactor = siInfo.GetSingle(LoadFactorName);
+ break;
+ case HashSizeName:
+ hashsize = siInfo.GetInt32(HashSizeName);
+ break;
+ case KeyComparerName:
+ _keycomparer = (IEqualityComparer)siInfo.GetValue(KeyComparerName, typeof(IEqualityComparer));
+ break;
+ case ComparerName:
+ c = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer));
+ break;
+ case HashCodeProviderName:
+#pragma warning disable 618
+ hcp = (IHashCodeProvider)siInfo.GetValue(HashCodeProviderName, typeof(IHashCodeProvider));
+#pragma warning restore 618
+ break;
+ case KeysName:
+ serKeys = (Object[])siInfo.GetValue(KeysName, typeof(Object[]));
+ break;
+ case ValuesName:
+ serValues = (Object[])siInfo.GetValue(ValuesName, typeof(Object[]));
+ break;
+ }
+ }
+
+ _loadsize = (int)(_loadFactor * hashsize);
+
+ // V1 object doesn't has _keycomparer field.
+ if ((_keycomparer == null) && ((c != null) || (hcp != null)))
+ {
+ _keycomparer = new CompatibleComparer(hcp, c);
+ }
+
+ _buckets = new bucket[hashsize];
+
+ if (serKeys == null)
+ {
+ throw new SerializationException(SR.Serialization_MissingKeys);
+ }
+ if (serValues == null)
+ {
+ throw new SerializationException(SR.Serialization_MissingValues);
+ }
+ if (serKeys.Length != serValues.Length)
+ {
+ throw new SerializationException(SR.Serialization_KeyValueDifferentSizes);
+ }
+ for (int i = 0; i < serKeys.Length; i++)
+ {
+ if (serKeys[i] == null)
+ {
+ throw new SerializationException(SR.Serialization_NullKey);
+ }
+ Insert(serKeys[i], serValues[i], true);
+ }
+
+ _version = siInfo.GetInt32(VersionName);
+
+ HashHelpers.SerializationInfoTable.Remove(this);
+ }
+
+ // Implements a Collection for the keys of a hashtable. An instance of this
+ // class is created by the GetKeys method of a hashtable.
+ private class KeyCollection : ICollection
+ {
+ private Hashtable _hashtable;
+
+ internal KeyCollection(Hashtable hashtable)
+ {
+ _hashtable = hashtable;
+ }
+
+ public virtual void CopyTo(Array array, int arrayIndex)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+ if (array.Rank != 1)
+ throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - arrayIndex < _hashtable._count)
+ throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
+ _hashtable.CopyKeys(array, arrayIndex);
+ }
+
+ public virtual IEnumerator GetEnumerator()
+ {
+ return new HashtableEnumerator(_hashtable, HashtableEnumerator.Keys);
+ }
+
+ public virtual bool IsSynchronized
+ {
+ get { return _hashtable.IsSynchronized; }
+ }
+
+ public virtual Object SyncRoot
+ {
+ get { return _hashtable.SyncRoot; }
+ }
+
+ public virtual int Count
+ {
+ get { return _hashtable._count; }
+ }
+ }
+
+ // Implements a Collection for the values of a hashtable. An instance of
+ // this class is created by the GetValues method of a hashtable.
+ private class ValueCollection : ICollection
+ {
+ private Hashtable _hashtable;
+
+ internal ValueCollection(Hashtable hashtable)
+ {
+ _hashtable = hashtable;
+ }
+
+ public virtual void CopyTo(Array array, int arrayIndex)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+ if (array.Rank != 1)
+ throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - arrayIndex < _hashtable._count)
+ throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
+ _hashtable.CopyValues(array, arrayIndex);
+ }
+
+ public virtual IEnumerator GetEnumerator()
+ {
+ return new HashtableEnumerator(_hashtable, HashtableEnumerator.Values);
+ }
+
+ public virtual bool IsSynchronized
+ {
+ get { return _hashtable.IsSynchronized; }
+ }
+
+ public virtual Object SyncRoot
+ {
+ get { return _hashtable.SyncRoot; }
+ }
+
+ public virtual int Count
+ {
+ get { return _hashtable._count; }
+ }
+ }
+
+ // Synchronized wrapper for hashtable
+ private class SyncHashtable : Hashtable, IEnumerable
+ {
+ protected Hashtable _table;
+
+ internal SyncHashtable(Hashtable table) : base(false)
+ {
+ _table = table;
+ }
+
+ internal SyncHashtable(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ public override int Count
+ {
+ get { return _table.Count; }
+ }
+
+ public override bool IsReadOnly
+ {
+ get { return _table.IsReadOnly; }
+ }
+
+ public override bool IsFixedSize
+ {
+ get { return _table.IsFixedSize; }
+ }
+
+ public override bool IsSynchronized
+ {
+ get { return true; }
+ }
+
+ public override Object this[Object key]
+ {
+ get
+ {
+ return _table[key];
+ }
+ set
+ {
+ lock (_table.SyncRoot)
+ {
+ _table[key] = value;
+ }
+ }
+ }
+
+ public override Object SyncRoot
+ {
+ get { return _table.SyncRoot; }
+ }
+
+ public override void Add(Object key, Object value)
+ {
+ lock (_table.SyncRoot)
+ {
+ _table.Add(key, value);
+ }
+ }
+
+ public override void Clear()
+ {
+ lock (_table.SyncRoot)
+ {
+ _table.Clear();
+ }
+ }
+
+ public override bool Contains(Object key)
+ {
+ return _table.Contains(key);
+ }
+
+ public override bool ContainsKey(Object key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+ return _table.ContainsKey(key);
+ }
+
+ public override bool ContainsValue(Object key)
+ {
+ lock (_table.SyncRoot)
+ {
+ return _table.ContainsValue(key);
+ }
+ }
+
+ public override void CopyTo(Array array, int arrayIndex)
+ {
+ lock (_table.SyncRoot)
+ {
+ _table.CopyTo(array, arrayIndex);
+ }
+ }
+
+ public override Object Clone()
+ {
+ lock (_table.SyncRoot)
+ {
+ return Hashtable.Synchronized((Hashtable)_table.Clone());
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _table.GetEnumerator();
+ }
+
+ public override IDictionaryEnumerator GetEnumerator()
+ {
+ return _table.GetEnumerator();
+ }
+
+ public override ICollection Keys
+ {
+ get
+ {
+ lock (_table.SyncRoot)
+ {
+ return _table.Keys;
+ }
+ }
+ }
+
+ public override ICollection Values
+ {
+ get
+ {
+ lock (_table.SyncRoot)
+ {
+ return _table.Values;
+ }
+ }
+ }
+
+ public override void Remove(Object key)
+ {
+ lock (_table.SyncRoot)
+ {
+ _table.Remove(key);
+ }
+ }
+
+ public override void OnDeserialization(Object sender)
+ {
+ // Does nothing. We have to implement this because our parent HT implements it,
+ // but it doesn't do anything meaningful. The real work will be done when we
+ // call OnDeserialization on our parent table.
+ }
+
+ internal override KeyValuePairs[] ToKeyValuePairsArray()
+ {
+ return _table.ToKeyValuePairsArray();
+ }
+ }
+
+
+ // Implements an enumerator for a hashtable. The enumerator uses the
+ // internal version number of the hashtable to ensure that no modifications
+ // are made to the hashtable while an enumeration is in progress.
+ private class HashtableEnumerator : IDictionaryEnumerator, ICloneable
+ {
+ private Hashtable _hashtable;
+ private int _bucket;
+ private int _version;
+ private bool _current;
+ private int _getObjectRetType; // What should GetObject return?
+ private Object _currentKey;
+ private Object _currentValue;
+
+ internal const int Keys = 1;
+ internal const int Values = 2;
+ internal const int DictEntry = 3;
+
+ internal HashtableEnumerator(Hashtable hashtable, int getObjRetType)
+ {
+ _hashtable = hashtable;
+ _bucket = hashtable._buckets.Length;
+ _version = hashtable._version;
+ _current = false;
+ _getObjectRetType = getObjRetType;
+ }
+
+ public object Clone() => MemberwiseClone();
+
+ public virtual Object Key
+ {
+ get
+ {
+ if (_current == false)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
+ return _currentKey;
+ }
+ }
+
+ public virtual bool MoveNext()
+ {
+ if (_version != _hashtable._version)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ while (_bucket > 0)
+ {
+ _bucket--;
+ Object keyv = _hashtable._buckets[_bucket].key;
+ if ((keyv != null) && (keyv != _hashtable._buckets))
+ {
+ _currentKey = keyv;
+ _currentValue = _hashtable._buckets[_bucket].val;
+ _current = true;
+ return true;
+ }
+ }
+ _current = false;
+ return false;
+ }
+
+ public virtual DictionaryEntry Entry
+ {
+ get
+ {
+ if (_current == false)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ return new DictionaryEntry(_currentKey, _currentValue);
+ }
+ }
+
+
+ public virtual Object Current
+ {
+ get
+ {
+ if (_current == false)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+
+ if (_getObjectRetType == Keys)
+ return _currentKey;
+ else if (_getObjectRetType == Values)
+ return _currentValue;
+ else
+ return new DictionaryEntry(_currentKey, _currentValue);
+ }
+ }
+
+ public virtual Object Value
+ {
+ get
+ {
+ if (_current == false)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ return _currentValue;
+ }
+ }
+
+ public virtual void Reset()
+ {
+ if (_version != _hashtable._version)
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ _current = false;
+ _bucket = _hashtable._buckets.Length;
+ _currentKey = null;
+ _currentValue = null;
+ }
+ }
+
+ // internal debug view class for hashtable
+ internal class HashtableDebugView
+ {
+ private Hashtable _hashtable;
+
+ public HashtableDebugView(Hashtable hashtable)
+ {
+ if (hashtable == null)
+ {
+ throw new ArgumentNullException(nameof(hashtable));
+ }
+
+ _hashtable = hashtable;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public KeyValuePairs[] Items
+ {
+ get
+ {
+ return _hashtable.ToKeyValuePairsArray();
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/ICollection.cs b/src/System.Private.CoreLib/shared/System/Collections/ICollection.cs
new file mode 100644
index 0000000000..b8eba71c24
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/ICollection.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections
+{
+ // Base interface for all collections, defining enumerators, size, and
+ // synchronization methods.
+ public interface ICollection : IEnumerable
+ {
+ // Interfaces are not serializable
+ // CopyTo copies a collection into an Array, starting at a particular
+ // index into the array.
+ //
+ void CopyTo(Array array, int index);
+
+ // Number of items in the collections.
+ int Count
+ { get; }
+
+
+ // SyncRoot will return an Object to use for synchronization
+ // (thread safety). You can use this object in your code to take a
+ // lock on the collection, even if this collection is a wrapper around
+ // another collection. The intent is to tunnel through to a real
+ // implementation of a collection, and use one of the internal objects
+ // found in that code.
+ //
+ // In the absence of a static Synchronized method on a collection,
+ // the expected usage for SyncRoot would look like this:
+ //
+ // ICollection col = ...
+ // lock (col.SyncRoot) {
+ // // Some operation on the collection, which is now thread safe.
+ // // This may include multiple operations.
+ // }
+ //
+ //
+ // The system-provided collections have a static method called
+ // Synchronized which will create a thread-safe wrapper around the
+ // collection. All access to the collection that you want to be
+ // thread-safe should go through that wrapper collection. However, if
+ // you need to do multiple calls on that collection (such as retrieving
+ // two items, or checking the count then doing something), you should
+ // NOT use our thread-safe wrapper since it only takes a lock for the
+ // duration of a single method call. Instead, use Monitor.Enter/Exit
+ // or your language's equivalent to the C# lock keyword as mentioned
+ // above.
+ //
+ // For collections with no publicly available underlying store, the
+ // expected implementation is to simply return the this pointer. Note
+ // that the this pointer may not be sufficient for collections that
+ // wrap other collections; those should return the underlying
+ // collection's SyncRoot property.
+ Object SyncRoot
+ { get; }
+
+ // Is this collection synchronized (i.e., thread-safe)? If you want a
+ // thread-safe collection, you can use SyncRoot as an object to
+ // synchronize your collection with. If you're using one of the
+ // collections in System.Collections, you could call the static
+ // Synchronized method to get a thread-safe wrapper around the
+ // underlying collection.
+ bool IsSynchronized
+ { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IComparer.cs b/src/System.Private.CoreLib/shared/System/Collections/IComparer.cs
new file mode 100644
index 0000000000..cef91c3ffa
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IComparer.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections
+{
+ // The IComparer interface implements a method that compares two objects. It is
+ // used in conjunction with the Sort and BinarySearch methods on
+ // the Array and List classes.
+ //
+ // Interfaces are not serializable
+ public interface IComparer
+ {
+ // Compares two objects. An implementation of this method must return a
+ // value less than zero if x is less than y, zero if x is equal to y, or a
+ // value greater than zero if x is greater than y.
+ //
+ int Compare(Object x, Object y);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IDictionary.cs b/src/System.Private.CoreLib/shared/System/Collections/IDictionary.cs
new file mode 100644
index 0000000000..b934c2399c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IDictionary.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections
+{
+ // An IDictionary is a possibly unordered set of key-value pairs.
+ // Keys can be any non-null object. Values can be any object.
+ // You can look up a value in an IDictionary via the default indexed
+ // property, Items.
+ public interface IDictionary : ICollection
+ {
+ // Interfaces are not serializable
+ // The Item property provides methods to read and edit entries
+ // in the Dictionary.
+ Object this[Object key]
+ {
+ get;
+ set;
+ }
+
+ // Returns a collections of the keys in this dictionary.
+ ICollection Keys
+ {
+ get;
+ }
+
+ // Returns a collections of the values in this dictionary.
+ ICollection Values
+ {
+ get;
+ }
+
+ // Returns whether this dictionary contains a particular key.
+ //
+ bool Contains(Object key);
+
+ // Adds a key-value pair to the dictionary.
+ //
+ void Add(Object key, Object value);
+
+ // Removes all pairs from the dictionary.
+ void Clear();
+
+ bool IsReadOnly
+ { get; }
+
+ bool IsFixedSize
+ { get; }
+
+ // Returns an IDictionaryEnumerator for this dictionary.
+ new IDictionaryEnumerator GetEnumerator();
+
+ // Removes a particular key from the dictionary.
+ //
+ void Remove(Object key);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IDictionaryEnumerator.cs b/src/System.Private.CoreLib/shared/System/Collections/IDictionaryEnumerator.cs
new file mode 100644
index 0000000000..451cf68976
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IDictionaryEnumerator.cs
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Collections
+{
+ // This interface represents an enumerator that allows sequential access to the
+ // elements of a dictionary. Upon creation, an enumerator is conceptually
+ // positioned before the first element of the enumeration. The first call to the
+ // MoveNext method brings the first element of the enumeration into view,
+ // and each successive call to MoveNext brings the next element into
+ // view until MoveNext returns false, indicating that there are no more
+ // elements to enumerate. Following each call to MoveNext, the
+ // Key and Value methods are used to obtain the key and
+ // value of the element currently in view. The values returned by calls to
+ // Key and Value are undefined before the first call to
+ // MoveNext and following a call to MoveNext that returned false.
+ // Enumerators are typically used in while loops of the form
+ //
+ // IDictionaryEnumerator e = ...;
+ // while (e.MoveNext()) {
+ // Object key = e.Key;
+ // Object value = e.Value;
+ // ...
+ // }
+ //
+ // The IDictionaryEnumerator interface extends the IEnumerator
+ // inerface and can thus be used as a regular enumerator. The Current
+ // method of an IDictionaryEnumerator returns a DictionaryEntry containing
+ // the current key and value pair. However, the GetEntry method will
+ // return the same DictionaryEntry and avoids boxing the DictionaryEntry (boxing
+ // is somewhat expensive).
+ //
+ public interface IDictionaryEnumerator : IEnumerator
+ {
+ // Returns the key of the current element of the enumeration. The returned
+ // value is undefined before the first call to GetNext and following
+ // a call to GetNext that returned false. Multiple calls to
+ // GetKey with no intervening calls to GetNext will return
+ // the same object.
+ //
+ Object Key
+ {
+ get;
+ }
+
+ // Returns the value of the current element of the enumeration. The
+ // returned value is undefined before the first call to GetNext and
+ // following a call to GetNext that returned false. Multiple calls
+ // to GetValue with no intervening calls to GetNext will
+ // return the same object.
+ //
+ Object Value
+ {
+ get;
+ }
+
+ // GetBlock will copy dictionary values into the given Array. It will either
+ // fill up the array, or if there aren't enough elements, it will
+ // copy as much as possible into the Array. The number of elements
+ // copied is returned.
+ //
+ DictionaryEntry Entry
+ {
+ get;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IEnumerable.cs b/src/System.Private.CoreLib/shared/System/Collections/IEnumerable.cs
new file mode 100644
index 0000000000..e5edeff266
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IEnumerable.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace System.Collections
+{
+ public interface IEnumerable
+ {
+ // Returns an IEnumerator for this enumerable Object. The enumerator provides
+ // a simple way to access all the contents of a collection.
+ IEnumerator GetEnumerator();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IEnumerator.cs b/src/System.Private.CoreLib/shared/System/Collections/IEnumerator.cs
new file mode 100644
index 0000000000..2c2c2e4a97
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IEnumerator.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace System.Collections
+{
+ // Base interface for all enumerators, providing a simple approach
+ // to iterating over a collection.
+ public interface IEnumerator
+ {
+ // Advances the enumerator to the next element of the enumeration and
+ // returns a boolean indicating whether an element is available. Upon
+ // creation, an enumerator is conceptually positioned before the first
+ // element of the enumeration, and the first call to MoveNext
+ // brings the first element of the enumeration into view.
+ //
+ bool MoveNext();
+
+ // Returns the current element of the enumeration. The returned value is
+ // undefined before the first call to MoveNext and following a
+ // call to MoveNext that returned false. Multiple calls to
+ // GetCurrent with no intervening calls to MoveNext
+ // will return the same object.
+ //
+ Object Current
+ {
+ get;
+ }
+
+ // Resets the enumerator to the beginning of the enumeration, starting over.
+ // The preferred behavior for Reset is to return the exact same enumeration.
+ // This means if you modify the underlying collection then call Reset, your
+ // IEnumerator will be invalid, just as it would have been if you had called
+ // MoveNext or Current.
+ //
+ void Reset();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IEqualityComparer.cs b/src/System.Private.CoreLib/shared/System/Collections/IEqualityComparer.cs
new file mode 100644
index 0000000000..35513f577d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IEqualityComparer.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections
+{
+ // An IEqualityComparer is a mechanism to consume custom performant comparison infrastructure
+ // that can be consumed by some of the common collections.
+ public interface IEqualityComparer
+ {
+ bool Equals(Object x, Object y);
+ int GetHashCode(Object obj);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IHashCodeProvider.cs b/src/System.Private.CoreLib/shared/System/Collections/IHashCodeProvider.cs
new file mode 100644
index 0000000000..7d6c63f9f1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IHashCodeProvider.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Collections
+{
+ /// <summary>
+ /// Provides a mechanism for a <see cref="Hashtable"/> user to override the default
+ /// GetHashCode() function on Objects, providing their own hash function.
+ /// </summary>
+ [Obsolete("Please use IEqualityComparer instead.")]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public interface IHashCodeProvider
+ {
+ /// <summary>Returns a hash code for the given object.</summary>
+ int GetHashCode(object obj);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IList.cs b/src/System.Private.CoreLib/shared/System/Collections/IList.cs
new file mode 100644
index 0000000000..0110eca1f5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IList.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections
+{
+ // An IList is an ordered collection of objects. The exact ordering
+ // is up to the implementation of the list, ranging from a sorted
+ // order to insertion order.
+ public interface IList : ICollection
+ {
+ // The Item property provides methods to read and edit entries in the List.
+ Object this[int index]
+ {
+ get;
+ set;
+ }
+
+ // Adds an item to the list. The exact position in the list is
+ // implementation-dependent, so while ArrayList may always insert
+ // in the last available location, a SortedList most likely would not.
+ // The return value is the position the new element was inserted in.
+ int Add(Object value);
+
+ // Returns whether the list contains a particular item.
+ bool Contains(Object value);
+
+ // Removes all items from the list.
+ void Clear();
+
+ bool IsReadOnly
+ { get; }
+
+
+ bool IsFixedSize
+ {
+ get;
+ }
+
+
+ // Returns the index of a particular item, if it is in the list.
+ // Returns -1 if the item isn't in the list.
+ int IndexOf(Object value);
+
+ // Inserts value into the list at position index.
+ // index must be non-negative and less than or equal to the
+ // number of elements in the list. If index equals the number
+ // of items in the list, then value is appended to the end.
+ void Insert(int index, Object value);
+
+ // Removes an item from the list.
+ void Remove(Object value);
+
+ // Removes the item at position index.
+ void RemoveAt(int index);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IStructuralComparable.cs b/src/System.Private.CoreLib/shared/System/Collections/IStructuralComparable.cs
new file mode 100644
index 0000000000..a5e4834b9b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IStructuralComparable.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Collections
+{
+ public interface IStructuralComparable
+ {
+ Int32 CompareTo(Object other, IComparer comparer);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/IStructuralEquatable.cs b/src/System.Private.CoreLib/shared/System/Collections/IStructuralEquatable.cs
new file mode 100644
index 0000000000..4e61d5e75f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/IStructuralEquatable.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Collections
+{
+ public interface IStructuralEquatable
+ {
+ Boolean Equals(Object other, IEqualityComparer comparer);
+ int GetHashCode(IEqualityComparer comparer);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/KeyValuePairs.cs b/src/System.Private.CoreLib/shared/System/Collections/KeyValuePairs.cs
new file mode 100644
index 0000000000..9ec6365b92
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/KeyValuePairs.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Class: KeyValuePairs
+**
+** Purpose: Defines key/value pairs for displaying items
+** in a collection class under the debugger.
+**
+===========================================================*/
+
+using System.Diagnostics;
+
+namespace System.Collections
+{
+ [DebuggerDisplay("{_value}", Name = "[{_key}]")]
+ internal class KeyValuePairs
+ {
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly object _key;
+
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly object _value;
+
+ public KeyValuePairs(object key, object value)
+ {
+ _value = value;
+ _key = key;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/ListDictionaryInternal.cs b/src/System.Private.CoreLib/shared/System/Collections/ListDictionaryInternal.cs
new file mode 100644
index 0000000000..eccb9f0347
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/ListDictionaryInternal.cs
@@ -0,0 +1,509 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: List for exceptions.
+**
+**
+===========================================================*/
+
+
+namespace System.Collections
+{
+ /// This is a simple implementation of IDictionary using a singly linked list. This
+ /// will be smaller and faster than a Hashtable if the number of elements is 10 or less.
+ /// This should not be used if performance is important for large numbers of elements.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ // Needs to be public to support binary serialization compatibility
+ public class ListDictionaryInternal : IDictionary
+ {
+ private DictionaryNode head; // Do not rename (binary serialization)
+ private int version; // Do not rename (binary serialization)
+ private int count; // Do not rename (binary serialization)
+ [NonSerialized]
+ private Object _syncRoot;
+
+ public ListDictionaryInternal()
+ {
+ }
+
+ public Object this[Object key]
+ {
+ get
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+ DictionaryNode node = head;
+
+ while (node != null)
+ {
+ if (node.key.Equals(key))
+ {
+ return node.value;
+ }
+ node = node.next;
+ }
+ return null;
+ }
+ set
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+
+
+ version++;
+ DictionaryNode last = null;
+ DictionaryNode node;
+ for (node = head; node != null; node = node.next)
+ {
+ if (node.key.Equals(key))
+ {
+ break;
+ }
+ last = node;
+ }
+ if (node != null)
+ {
+ // Found it
+ node.value = value;
+ return;
+ }
+ // Not found, so add a new one
+ DictionaryNode newNode = new DictionaryNode();
+ newNode.key = key;
+ newNode.value = value;
+ if (last != null)
+ {
+ last.next = newNode;
+ }
+ else
+ {
+ head = newNode;
+ }
+ count++;
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return count;
+ }
+ }
+
+ public ICollection Keys
+ {
+ get
+ {
+ return new NodeKeyValueCollection(this, true);
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public bool IsFixedSize
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public bool IsSynchronized
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public Object SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);
+ }
+ return _syncRoot;
+ }
+ }
+
+ public ICollection Values
+ {
+ get
+ {
+ return new NodeKeyValueCollection(this, false);
+ }
+ }
+
+ public void Add(Object key, Object value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+
+
+ version++;
+ DictionaryNode last = null;
+ DictionaryNode node;
+ for (node = head; node != null; node = node.next)
+ {
+ if (node.key.Equals(key))
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate__, node.key, key));
+ }
+ last = node;
+ }
+ if (node != null)
+ {
+ // Found it
+ node.value = value;
+ return;
+ }
+ // Not found, so add a new one
+ DictionaryNode newNode = new DictionaryNode();
+ newNode.key = key;
+ newNode.value = value;
+ if (last != null)
+ {
+ last.next = newNode;
+ }
+ else
+ {
+ head = newNode;
+ }
+ count++;
+ }
+
+ public void Clear()
+ {
+ count = 0;
+ head = null;
+ version++;
+ }
+
+ public bool Contains(Object key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+ for (DictionaryNode node = head; node != null; node = node.next)
+ {
+ if (node.key.Equals(key))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void CopyTo(Array array, int index)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+
+ if (array.Rank != 1)
+ throw new ArgumentException(SR.Arg_RankMultiDimNotSupported);
+
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (array.Length - index < this.Count)
+ throw new ArgumentException(SR.ArgumentOutOfRange_Index, nameof(index));
+
+ for (DictionaryNode node = head; node != null; node = node.next)
+ {
+ array.SetValue(new DictionaryEntry(node.key, node.value), index);
+ index++;
+ }
+ }
+
+ public IDictionaryEnumerator GetEnumerator()
+ {
+ return new NodeEnumerator(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return new NodeEnumerator(this);
+ }
+
+ public void Remove(Object key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+ }
+ version++;
+ DictionaryNode last = null;
+ DictionaryNode node;
+ for (node = head; node != null; node = node.next)
+ {
+ if (node.key.Equals(key))
+ {
+ break;
+ }
+ last = node;
+ }
+ if (node == null)
+ {
+ return;
+ }
+ if (node == head)
+ {
+ head = node.next;
+ }
+ else
+ {
+ last.next = node.next;
+ }
+ count--;
+ }
+
+ private class NodeEnumerator : IDictionaryEnumerator
+ {
+ private ListDictionaryInternal list;
+ private DictionaryNode current;
+ private int version;
+ private bool start;
+
+
+ public NodeEnumerator(ListDictionaryInternal list)
+ {
+ this.list = list;
+ version = list.version;
+ start = true;
+ current = null;
+ }
+
+ public Object Current
+ {
+ get
+ {
+ return Entry;
+ }
+ }
+
+ public DictionaryEntry Entry
+ {
+ get
+ {
+ if (current == null)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ }
+ return new DictionaryEntry(current.key, current.value);
+ }
+ }
+
+ public Object Key
+ {
+ get
+ {
+ if (current == null)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ }
+ return current.key;
+ }
+ }
+
+ public Object Value
+ {
+ get
+ {
+ if (current == null)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ }
+ return current.value;
+ }
+ }
+
+ public bool MoveNext()
+ {
+ if (version != list.version)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ }
+ if (start)
+ {
+ current = list.head;
+ start = false;
+ }
+ else
+ {
+ if (current != null)
+ {
+ current = current.next;
+ }
+ }
+ return (current != null);
+ }
+
+ public void Reset()
+ {
+ if (version != list.version)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ }
+ start = true;
+ current = null;
+ }
+ }
+
+
+ private class NodeKeyValueCollection : ICollection
+ {
+ private ListDictionaryInternal list;
+ private bool isKeys;
+
+ public NodeKeyValueCollection(ListDictionaryInternal list, bool isKeys)
+ {
+ this.list = list;
+ this.isKeys = isKeys;
+ }
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+ if (array.Rank != 1)
+ throw new ArgumentException(SR.Arg_RankMultiDimNotSupported);
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - index < list.Count)
+ throw new ArgumentException(SR.ArgumentOutOfRange_Index, nameof(index));
+ for (DictionaryNode node = list.head; node != null; node = node.next)
+ {
+ array.SetValue(isKeys ? node.key : node.value, index);
+ index++;
+ }
+ }
+
+ int ICollection.Count
+ {
+ get
+ {
+ int count = 0;
+ for (DictionaryNode node = list.head; node != null; node = node.next)
+ {
+ count++;
+ }
+ return count;
+ }
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ Object ICollection.SyncRoot
+ {
+ get
+ {
+ return list.SyncRoot;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return new NodeKeyValueEnumerator(list, isKeys);
+ }
+
+
+ private class NodeKeyValueEnumerator : IEnumerator
+ {
+ private ListDictionaryInternal list;
+ private DictionaryNode current;
+ private int version;
+ private bool isKeys;
+ private bool start;
+
+ public NodeKeyValueEnumerator(ListDictionaryInternal list, bool isKeys)
+ {
+ this.list = list;
+ this.isKeys = isKeys;
+ version = list.version;
+ start = true;
+ current = null;
+ }
+
+ public Object Current
+ {
+ get
+ {
+ if (current == null)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ }
+ return isKeys ? current.key : current.value;
+ }
+ }
+
+ public bool MoveNext()
+ {
+ if (version != list.version)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ }
+ if (start)
+ {
+ current = list.head;
+ start = false;
+ }
+ else
+ {
+ if (current != null)
+ {
+ current = current.next;
+ }
+ }
+ return (current != null);
+ }
+
+ public void Reset()
+ {
+ if (version != list.version)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ }
+ start = true;
+ current = null;
+ }
+ }
+ }
+
+ [Serializable]
+ private class DictionaryNode
+ {
+ public Object key;
+ public Object value;
+ public DictionaryNode next;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/ObjectModel/Collection.cs b/src/System.Private.CoreLib/shared/System/Collections/ObjectModel/Collection.cs
new file mode 100644
index 0000000000..1e1b2c7959
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/ObjectModel/Collection.cs
@@ -0,0 +1,396 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Collections.ObjectModel
+{
+ [Serializable]
+ [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
+ [DebuggerDisplay("Count = {Count}")]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Collection<T> : IList<T>, IList, IReadOnlyList<T>
+ {
+ private IList<T> items; // Do not rename (binary serialization)
+ [NonSerialized]
+ private Object _syncRoot;
+
+ public Collection()
+ {
+ items = new List<T>();
+ }
+
+ public Collection(IList<T> list)
+ {
+ if (list == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.list);
+ }
+ items = list;
+ }
+
+ public int Count
+ {
+ get { return items.Count; }
+ }
+
+ protected IList<T> Items
+ {
+ get { return items; }
+ }
+
+ public T this[int index]
+ {
+ get { return items[index]; }
+ set
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ if (index < 0 || index >= items.Count)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+
+ SetItem(index, value);
+ }
+ }
+
+ public void Add(T item)
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ int index = items.Count;
+ InsertItem(index, item);
+ }
+
+ public void Clear()
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ ClearItems();
+ }
+
+ public void CopyTo(T[] array, int index)
+ {
+ items.CopyTo(array, index);
+ }
+
+ public bool Contains(T item)
+ {
+ return items.Contains(item);
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return items.GetEnumerator();
+ }
+
+ public int IndexOf(T item)
+ {
+ return items.IndexOf(item);
+ }
+
+ public void Insert(int index, T item)
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ if (index < 0 || index > items.Count)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
+ }
+
+ InsertItem(index, item);
+ }
+
+ public bool Remove(T item)
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ int index = items.IndexOf(item);
+ if (index < 0) return false;
+ RemoveItem(index);
+ return true;
+ }
+
+ public void RemoveAt(int index)
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ if (index < 0 || index >= items.Count)
+ {
+ ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+ }
+
+ RemoveItem(index);
+ }
+
+ protected virtual void ClearItems()
+ {
+ items.Clear();
+ }
+
+ protected virtual void InsertItem(int index, T item)
+ {
+ items.Insert(index, item);
+ }
+
+ protected virtual void RemoveItem(int index)
+ {
+ items.RemoveAt(index);
+ }
+
+ protected virtual void SetItem(int index, T item)
+ {
+ items[index] = item;
+ }
+
+ bool ICollection<T>.IsReadOnly
+ {
+ get
+ {
+ return items.IsReadOnly;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)items).GetEnumerator();
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get { return false; }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ ICollection c = items as ICollection;
+ if (c != null)
+ {
+ _syncRoot = c.SyncRoot;
+ }
+ else
+ {
+ System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);
+ }
+ }
+ return _syncRoot;
+ }
+ }
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ }
+
+ if (array.Rank != 1)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ }
+
+ if (array.GetLowerBound(0) != 0)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
+ }
+
+ if (index < 0)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (array.Length - index < Count)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+ }
+
+ T[] tArray = array as T[];
+ if (tArray != null)
+ {
+ items.CopyTo(tArray, index);
+ }
+ else
+ {
+ //
+ // Catch the obvious case assignment will fail.
+ // We can't find all possible problems by doing the check though.
+ // For example, if the element type of the Array is derived from T,
+ // we can't figure out if we can successfully copy the element beforehand.
+ //
+ Type targetType = array.GetType().GetElementType();
+ Type sourceType = typeof(T);
+ if (!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType)))
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+
+ //
+ // We can't cast array of value type to object[], so we don't support
+ // widening of primitive types here.
+ //
+ object[] objects = array as object[];
+ if (objects == null)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+
+ int count = items.Count;
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ objects[index++] = items[i];
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+ }
+
+ object IList.this[int index]
+ {
+ get { return items[index]; }
+ set
+ {
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value);
+
+ try
+ {
+ this[index] = (T)value;
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T));
+ }
+ }
+ }
+
+ bool IList.IsReadOnly
+ {
+ get
+ {
+ return items.IsReadOnly;
+ }
+ }
+
+ bool IList.IsFixedSize
+ {
+ get
+ {
+ // There is no IList<T>.IsFixedSize, so we must assume that only
+ // readonly collections are fixed size, if our internal item
+ // collection does not implement IList. Note that Array implements
+ // IList, and therefore T[] and U[] will be fixed-size.
+ IList list = items as IList;
+ if (list != null)
+ {
+ return list.IsFixedSize;
+ }
+ return items.IsReadOnly;
+ }
+ }
+
+ int IList.Add(object value)
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value);
+
+ try
+ {
+ Add((T)value);
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T));
+ }
+
+ return this.Count - 1;
+ }
+
+ bool IList.Contains(object value)
+ {
+ if (IsCompatibleObject(value))
+ {
+ return Contains((T)value);
+ }
+ return false;
+ }
+
+ int IList.IndexOf(object value)
+ {
+ if (IsCompatibleObject(value))
+ {
+ return IndexOf((T)value);
+ }
+ return -1;
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value);
+
+ try
+ {
+ Insert(index, (T)value);
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T));
+ }
+ }
+
+ void IList.Remove(object value)
+ {
+ if (items.IsReadOnly)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ if (IsCompatibleObject(value))
+ {
+ Remove((T)value);
+ }
+ }
+
+ private static bool IsCompatibleObject(object value)
+ {
+ // Non-null values are fine. Only accept nulls if T is a class or Nullable<U>.
+ // Note that default(T) is not equal to null for value types except when T is Nullable<U>.
+ return ((value is T) || (value == null && default(T) == null));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Collections/ObjectModel/ReadOnlyCollection.cs b/src/System.Private.CoreLib/shared/System/Collections/ObjectModel/ReadOnlyCollection.cs
new file mode 100644
index 0000000000..dbf88d8b8d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Collections/ObjectModel/ReadOnlyCollection.cs
@@ -0,0 +1,279 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Collections.ObjectModel
+{
+ [Serializable]
+ [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
+ [DebuggerDisplay("Count = {Count}")]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ReadOnlyCollection<T> : IList<T>, IList, IReadOnlyList<T>
+ {
+ private IList<T> list; // Do not rename (binary serialization)
+ [NonSerialized]
+ private Object _syncRoot;
+
+ public ReadOnlyCollection(IList<T> list)
+ {
+ if (list == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.list);
+ }
+ this.list = list;
+ }
+
+ public int Count
+ {
+ get { return list.Count; }
+ }
+
+ public T this[int index]
+ {
+ get { return list[index]; }
+ }
+
+ public bool Contains(T value)
+ {
+ return list.Contains(value);
+ }
+
+ public void CopyTo(T[] array, int index)
+ {
+ list.CopyTo(array, index);
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return list.GetEnumerator();
+ }
+
+ public int IndexOf(T value)
+ {
+ return list.IndexOf(value);
+ }
+
+ protected IList<T> Items
+ {
+ get
+ {
+ return list;
+ }
+ }
+
+ bool ICollection<T>.IsReadOnly
+ {
+ get { return true; }
+ }
+
+ T IList<T>.this[int index]
+ {
+ get { return list[index]; }
+ set
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+ }
+
+ void ICollection<T>.Add(T value)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ void ICollection<T>.Clear()
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ void IList<T>.Insert(int index, T value)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ bool ICollection<T>.Remove(T value)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ return false;
+ }
+
+ void IList<T>.RemoveAt(int index)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)list).GetEnumerator();
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get { return false; }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ ICollection c = list as ICollection;
+ if (c != null)
+ {
+ _syncRoot = c.SyncRoot;
+ }
+ else
+ {
+ System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);
+ }
+ }
+ return _syncRoot;
+ }
+ }
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ }
+
+ if (array.Rank != 1)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ }
+
+ if (array.GetLowerBound(0) != 0)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
+ }
+
+ if (index < 0)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (array.Length - index < Count)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+ }
+
+ T[] items = array as T[];
+ if (items != null)
+ {
+ list.CopyTo(items, index);
+ }
+ else
+ {
+ //
+ // Catch the obvious case assignment will fail.
+ // We can't find all possible problems by doing the check though.
+ // For example, if the element type of the Array is derived from T,
+ // we can't figure out if we can successfully copy the element beforehand.
+ //
+ Type targetType = array.GetType().GetElementType();
+ Type sourceType = typeof(T);
+ if (!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType)))
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+
+ //
+ // We can't cast array of value type to object[], so we don't support
+ // widening of primitive types here.
+ //
+ object[] objects = array as object[];
+ if (objects == null)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+
+ int count = list.Count;
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ objects[index++] = list[i];
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+ }
+
+ bool IList.IsFixedSize
+ {
+ get { return true; }
+ }
+
+ bool IList.IsReadOnly
+ {
+ get { return true; }
+ }
+
+ object IList.this[int index]
+ {
+ get { return list[index]; }
+ set
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+ }
+
+ int IList.Add(object value)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ return -1;
+ }
+
+ void IList.Clear()
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ private static bool IsCompatibleObject(object value)
+ {
+ // Non-null values are fine. Only accept nulls if T is a class or Nullable<U>.
+ // Note that default(T) is not equal to null for value types except when T is Nullable<U>.
+ return ((value is T) || (value == null && default(T) == null));
+ }
+
+ bool IList.Contains(object value)
+ {
+ if (IsCompatibleObject(value))
+ {
+ return Contains((T)value);
+ }
+ return false;
+ }
+
+ int IList.IndexOf(object value)
+ {
+ if (IsCompatibleObject(value))
+ {
+ return IndexOf((T)value);
+ }
+ return -1;
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ void IList.Remove(object value)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ComponentModel/DefaultValueAttribute.cs b/src/System.Private.CoreLib/shared/System/ComponentModel/DefaultValueAttribute.cs
new file mode 100644
index 0000000000..3cdc907297
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ComponentModel/DefaultValueAttribute.cs
@@ -0,0 +1,228 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System.ComponentModel
+{
+ /// <devdoc>
+ /// <para>Specifies the default value for a property.</para>
+ /// </devdoc>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes")]
+ [AttributeUsage(AttributeTargets.All)]
+ public class DefaultValueAttribute : Attribute
+ {
+ /// <devdoc>
+ /// This is the default value.
+ /// </devdoc>
+ private object _value;
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class, converting the
+ /// specified value to the
+ /// specified type, and using the U.S. English culture as the
+ /// translation
+ /// context.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(Type type, string value)
+ {
+ // The try/catch here is because attributes should never throw exceptions. We would fail to
+ // load an otherwise normal class.
+ try
+ {
+ if (type.IsSubclassOf(typeof(Enum)))
+ {
+ _value = Enum.Parse(type, value, true);
+ }
+ else if (type == typeof(TimeSpan))
+ {
+ _value = TimeSpan.Parse(value);
+ }
+ else
+ {
+ _value = Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a Unicode
+ /// character.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(char value)
+ {
+ _value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using an 8-bit unsigned
+ /// integer.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(byte value)
+ {
+ _value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a 16-bit signed
+ /// integer.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(short value)
+ {
+ _value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a 32-bit signed
+ /// integer.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(int value)
+ {
+ _value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a 64-bit signed
+ /// integer.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(long value)
+ {
+ _value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a
+ /// single-precision floating point
+ /// number.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(float value)
+ {
+ _value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a
+ /// double-precision floating point
+ /// number.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(double value)
+ {
+ _value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a <see cref='System.Boolean'/>
+ /// value.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(bool value)
+ {
+ _value = value;
+ }
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a <see cref='System.String'/>.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(string value)
+ {
+ _value = value;
+ }
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/>
+ /// class.</para>
+ /// </devdoc>
+ public DefaultValueAttribute(object value)
+ {
+ _value = value;
+ }
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a <see cref='System.SByte'/>
+ /// value.</para>
+ /// </devdoc>
+ [CLSCompliant(false)]
+ public DefaultValueAttribute(sbyte value)
+ {
+ _value = value;
+ }
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a <see cref='System.UInt16'/>
+ /// value.</para>
+ /// </devdoc>
+ [CLSCompliant(false)]
+ public DefaultValueAttribute(ushort value)
+ {
+ _value = value;
+ }
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a <see cref='System.UInt32'/>
+ /// value.</para>
+ /// </devdoc>
+ [CLSCompliant(false)]
+ public DefaultValueAttribute(uint value)
+ {
+ _value = value;
+ }
+
+ /// <devdoc>
+ /// <para>Initializes a new instance of the <see cref='System.ComponentModel.DefaultValueAttribute'/> class using a <see cref='System.UInt64'/>
+ /// value.</para>
+ /// </devdoc>
+ [CLSCompliant(false)]
+ public DefaultValueAttribute(ulong value)
+ {
+ _value = value;
+ }
+
+ /// <devdoc>
+ /// <para>
+ /// Gets the default value of the property this
+ /// attribute is
+ /// bound to.
+ /// </para>
+ /// </devdoc>
+ public virtual object Value
+ {
+ get
+ {
+ return _value;
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == this)
+ {
+ return true;
+ }
+
+ DefaultValueAttribute other = obj as DefaultValueAttribute;
+
+ if (other != null)
+ {
+ if (Value != null)
+ {
+ return Value.Equals(other.Value);
+ }
+ else
+ {
+ return (other.Value == null);
+ }
+ }
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ protected void SetValue(object value)
+ {
+ _value = value;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ComponentModel/EditorBrowsableAttribute.cs b/src/System.Private.CoreLib/shared/System/ComponentModel/EditorBrowsableAttribute.cs
new file mode 100644
index 0000000000..9b4d6e626e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ComponentModel/EditorBrowsableAttribute.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.ComponentModel
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Delegate | AttributeTargets.Interface)]
+ public sealed class EditorBrowsableAttribute : Attribute
+ {
+ private EditorBrowsableState browsableState;
+
+ public EditorBrowsableAttribute(EditorBrowsableState state)
+ {
+ browsableState = state;
+ }
+
+ public EditorBrowsableAttribute() : this(EditorBrowsableState.Always) { }
+
+ public EditorBrowsableState State
+ {
+ get { return browsableState; }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == this)
+ {
+ return true;
+ }
+
+ EditorBrowsableAttribute other = obj as EditorBrowsableAttribute;
+
+ return (other != null) && other.browsableState == browsableState;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+ }
+
+ public enum EditorBrowsableState
+ {
+ Always,
+ Never,
+ Advanced
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Configuration/Assemblies/AssemblyHashAlgorithm.cs b/src/System.Private.CoreLib/shared/System/Configuration/Assemblies/AssemblyHashAlgorithm.cs
new file mode 100644
index 0000000000..aca8da5932
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Configuration/Assemblies/AssemblyHashAlgorithm.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Configuration.Assemblies
+{
+ public enum AssemblyHashAlgorithm
+ {
+ None = 0,
+ MD5 = 0x8003,
+ SHA1 = 0x8004,
+ SHA256 = 0x800c,
+ SHA384 = 0x800d,
+ SHA512 = 0x800e,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Configuration/Assemblies/AssemblyVersionCompatibility.cs b/src/System.Private.CoreLib/shared/System/Configuration/Assemblies/AssemblyVersionCompatibility.cs
new file mode 100644
index 0000000000..ef7b3eb45f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Configuration/Assemblies/AssemblyVersionCompatibility.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Configuration.Assemblies
+{
+ public enum AssemblyVersionCompatibility
+ {
+ SameMachine = 1,
+ SameProcess = 2,
+ SameDomain = 3,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Convert.Base64.cs b/src/System.Private.CoreLib/shared/System/Convert.Base64.cs
new file mode 100644
index 0000000000..7e2aee31b2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Convert.Base64.cs
@@ -0,0 +1,217 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ public static partial class Convert
+ {
+ /// <summary>
+ /// Decode the span of UTF-16 encoded text represented as base 64 into binary data.
+ /// If the input is not a multiple of 4, or contains illegal characters, it will decode as much as it can, to the largest possible multiple of 4.
+ /// This invariant allows continuation of the parse with a slower, whitespace-tolerant algorithm.
+ ///
+ /// <param name="utf16">The input span which contains UTF-16 encoded text in base 64 that needs to be decoded.</param>
+ /// <param name="bytes">The output span which contains the result of the operation, i.e. the decoded binary data.</param>
+ /// <param name="consumed">The number of input bytes consumed during the operation. This can be used to slice the input for subsequent calls, if necessary.</param>
+ /// <param name="written">The number of bytes written into the output span. This can be used to slice the output for subsequent calls, if necessary.</param>
+ /// <returns>Returns:
+ /// - true - The entire input span was successfully parsed.
+ /// - false - Only a part of the input span was successfully parsed. Failure causes may include embedded or trailing whitespace,
+ /// other illegal Base64 characters, trailing characters after an encoding pad ('='), an input span whose length is not divisible by 4
+ /// or a destination span that's too small. <paramref name="consumed"/> and <paramref name="written"/> are set so that
+ /// parsing can continue with a slower whitespace-tolerant algorithm.
+ ///
+ /// Note: This is a cut down version of the implementation of Base64.DecodeFromUtf8(), modified the accept UTF16 chars and act as a fast-path
+ /// helper for the Convert routines when the input string contains no whitespace.
+ ///
+ /// </summary>
+ private static bool TryDecodeFromUtf16(ReadOnlySpan<char> utf16, Span<byte> bytes, out int consumed, out int written)
+ {
+ ref char srcChars = ref MemoryMarshal.GetReference(utf16);
+ ref byte destBytes = ref MemoryMarshal.GetReference(bytes);
+
+ int srcLength = utf16.Length & ~0x3; // only decode input up to the closest multiple of 4.
+ int destLength = bytes.Length;
+
+ int sourceIndex = 0;
+ int destIndex = 0;
+
+ if (utf16.Length == 0)
+ goto DoneExit;
+
+ ref sbyte decodingMap = ref s_decodingMap[0];
+
+ // Last bytes could have padding characters, so process them separately and treat them as valid.
+ const int skipLastChunk = 4;
+
+ int maxSrcLength;
+ if (destLength >= (srcLength >> 2) * 3)
+ {
+ maxSrcLength = srcLength - skipLastChunk;
+ }
+ else
+ {
+ // This should never overflow since destLength here is less than int.MaxValue / 4 * 3 (i.e. 1610612733)
+ // Therefore, (destLength / 3) * 4 will always be less than 2147483641
+ maxSrcLength = (destLength / 3) * 4;
+ }
+
+ while (sourceIndex < maxSrcLength)
+ {
+ int result = Decode(ref Unsafe.Add(ref srcChars, sourceIndex), ref decodingMap);
+ if (result < 0)
+ goto InvalidExit;
+ WriteThreeLowOrderBytes(ref Unsafe.Add(ref destBytes, destIndex), result);
+ destIndex += 3;
+ sourceIndex += 4;
+ }
+
+ if (maxSrcLength != srcLength - skipLastChunk)
+ goto InvalidExit;
+
+ // If input is less than 4 bytes, srcLength == sourceIndex == 0
+ // If input is not a multiple of 4, sourceIndex == srcLength != 0
+ if (sourceIndex == srcLength)
+ {
+ goto InvalidExit;
+ }
+
+ int i0 = Unsafe.Add(ref srcChars, srcLength - 4);
+ int i1 = Unsafe.Add(ref srcChars, srcLength - 3);
+ int i2 = Unsafe.Add(ref srcChars, srcLength - 2);
+ int i3 = Unsafe.Add(ref srcChars, srcLength - 1);
+ if (((i0 | i1 | i2 | i3) & 0xffffff00) != 0)
+ goto InvalidExit;
+
+ i0 = Unsafe.Add(ref decodingMap, i0);
+ i1 = Unsafe.Add(ref decodingMap, i1);
+
+ i0 <<= 18;
+ i1 <<= 12;
+
+ i0 |= i1;
+
+ if (i3 != EncodingPad)
+ {
+ i2 = Unsafe.Add(ref decodingMap, i2);
+ i3 = Unsafe.Add(ref decodingMap, i3);
+
+ i2 <<= 6;
+
+ i0 |= i3;
+ i0 |= i2;
+
+ if (i0 < 0)
+ goto InvalidExit;
+ if (destIndex > destLength - 3)
+ goto InvalidExit;
+ WriteThreeLowOrderBytes(ref Unsafe.Add(ref destBytes, destIndex), i0);
+ destIndex += 3;
+ }
+ else if (i2 != EncodingPad)
+ {
+ i2 = Unsafe.Add(ref decodingMap, i2);
+
+ i2 <<= 6;
+
+ i0 |= i2;
+
+ if (i0 < 0)
+ goto InvalidExit;
+ if (destIndex > destLength - 2)
+ goto InvalidExit;
+ Unsafe.Add(ref destBytes, destIndex) = (byte)(i0 >> 16);
+ Unsafe.Add(ref destBytes, destIndex + 1) = (byte)(i0 >> 8);
+ destIndex += 2;
+ }
+ else
+ {
+ if (i0 < 0)
+ goto InvalidExit;
+ if (destIndex > destLength - 1)
+ goto InvalidExit;
+ Unsafe.Add(ref destBytes, destIndex) = (byte)(i0 >> 16);
+ destIndex += 1;
+ }
+
+ sourceIndex += 4;
+
+ if (srcLength != utf16.Length)
+ goto InvalidExit;
+
+ DoneExit:
+ consumed = sourceIndex;
+ written = destIndex;
+ return true;
+
+ InvalidExit:
+ consumed = sourceIndex;
+ written = destIndex;
+ Debug.Assert((consumed % 4) == 0);
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Decode(ref char encodedChars, ref sbyte decodingMap)
+ {
+ int i0 = encodedChars;
+ int i1 = Unsafe.Add(ref encodedChars, 1);
+ int i2 = Unsafe.Add(ref encodedChars, 2);
+ int i3 = Unsafe.Add(ref encodedChars, 3);
+
+ if (((i0 | i1 | i2 | i3) & 0xffffff00) != 0)
+ return -1; // One or more chars falls outside the 00..ff range. This cannot be a valid Base64 character.
+
+ i0 = Unsafe.Add(ref decodingMap, i0);
+ i1 = Unsafe.Add(ref decodingMap, i1);
+ i2 = Unsafe.Add(ref decodingMap, i2);
+ i3 = Unsafe.Add(ref decodingMap, i3);
+
+ i0 <<= 18;
+ i1 <<= 12;
+ i2 <<= 6;
+
+ i0 |= i3;
+ i1 |= i2;
+
+ i0 |= i1;
+ return i0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteThreeLowOrderBytes(ref byte destination, int value)
+ {
+ destination = (byte)(value >> 16);
+ Unsafe.Add(ref destination, 1) = (byte)(value >> 8);
+ Unsafe.Add(ref destination, 2) = (byte)value;
+ }
+
+ // Pre-computing this table using a custom string(s_characters) and GenerateDecodingMapAndVerify (found in tests)
+ private static readonly sbyte[] s_decodingMap = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, //62 is placed at index 43 (for +), 63 at index 47 (for /)
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, //52-61 are placed at index 48-57 (for 0-9), 64 at index 61 (for =)
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, //0-25 are placed at index 65-90 (for A-Z)
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, //26-51 are placed at index 97-122 (for a-z)
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Bytes over 122 ('z') are invalid and cannot be decoded
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Hence, padding the map with 255, which indicates invalid input
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ private const byte EncodingPad = (byte)'='; // '=', for padding
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Convert.cs b/src/System.Private.CoreLib/shared/System/Convert.cs
new file mode 100644
index 0000000000..63342ad000
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Convert.cs
@@ -0,0 +1,2918 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Threading;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Security;
+using System.Diagnostics;
+
+namespace System
+{
+ [Flags]
+ public enum Base64FormattingOptions
+ {
+ None = 0,
+ InsertLineBreaks = 1
+ }
+
+ // Returns the type code of this object. An implementation of this method
+ // must not return TypeCode.Empty (which represents a null reference) or
+ // TypeCode.Object (which represents an object that doesn't implement the
+ // IConvertible interface). An implementation of this method should return
+ // TypeCode.DBNull if the value of this object is a database null. For
+ // example, a nullable integer type should return TypeCode.DBNull if the
+ // value of the object is the database null. Otherwise, an implementation
+ // of this method should return the TypeCode that best describes the
+ // internal representation of the object.
+ // The Value class provides conversion and querying methods for values. The
+ // Value class contains static members only, and it is not possible to create
+ // instances of the class.
+ //
+ // The statically typed conversion methods provided by the Value class are all
+ // of the form:
+ //
+ // public static XXX ToXXX(YYY value)
+ //
+ // where XXX is the target type and YYY is the source type. The matrix below
+ // shows the set of supported conversions. The set of conversions is symmetric
+ // such that for every ToXXX(YYY) there is also a ToYYY(XXX).
+ //
+ // From: To: Bol Chr SBy Byt I16 U16 I32 U32 I64 U64 Sgl Dbl Dec Dat Str
+ // ----------------------------------------------------------------------
+ // Boolean x x x x x x x x x x x x x
+ // Char x x x x x x x x x x
+ // SByte x x x x x x x x x x x x x x
+ // Byte x x x x x x x x x x x x x x
+ // Int16 x x x x x x x x x x x x x x
+ // UInt16 x x x x x x x x x x x x x x
+ // Int32 x x x x x x x x x x x x x x
+ // UInt32 x x x x x x x x x x x x x x
+ // Int64 x x x x x x x x x x x x x x
+ // UInt64 x x x x x x x x x x x x x x
+ // Single x x x x x x x x x x x x x
+ // Double x x x x x x x x x x x x x
+ // Decimal x x x x x x x x x x x x x
+ // DateTime x x
+ // String x x x x x x x x x x x x x x x
+ // ----------------------------------------------------------------------
+ //
+ // For dynamic conversions, the Value class provides a set of methods of the
+ // form:
+ //
+ // public static XXX ToXXX(object value)
+ //
+ // where XXX is the target type (Boolean, Char, SByte, Byte, Int16, UInt16,
+ // Int32, UInt32, Int64, UInt64, Single, Double, Decimal, DateTime,
+ // or String). The implementations of these methods all take the form:
+ //
+ // public static XXX toXXX(object value) {
+ // return value == null? XXX.Default: ((IConvertible)value).ToXXX();
+ // }
+ //
+ // The code first checks if the given value is a null reference (which is the
+ // same as Value.Empty), in which case it returns the default value for type
+ // XXX. Otherwise, a cast to IConvertible is performed, and the appropriate ToXXX()
+ // method is invoked on the object. An InvalidCastException is thrown if the
+ // cast to IConvertible fails, and that exception is simply allowed to propagate out
+ // of the conversion method.
+
+ // Constant representing the database null value. This value is used in
+ // database applications to indicate the absence of a known value. Note
+ // that Value.DBNull is NOT the same as a null object reference, which is
+ // represented by Value.Empty.
+ //
+ // The Equals() method of DBNull always returns false, even when the
+ // argument is itself DBNull.
+ //
+ // When passed Value.DBNull, the Value.GetTypeCode() method returns
+ // TypeCode.DBNull.
+ //
+ // When passed Value.DBNull, the Value.ToXXX() methods all throw an
+ // InvalidCastException.
+
+ public static partial class Convert
+ {
+ //A typeof operation is fairly expensive (does a system call), so we'll cache these here
+ //statically. These are exactly lined up with the TypeCode, eg. ConvertType[TypeCode.Int16]
+ //will give you the type of an Int16.
+ internal static readonly Type[] ConvertTypes = {
+ typeof(System.Empty),
+ typeof(Object),
+ typeof(System.DBNull),
+ typeof(Boolean),
+ typeof(Char),
+ typeof(SByte),
+ typeof(Byte),
+ typeof(Int16),
+ typeof(UInt16),
+ typeof(Int32),
+ typeof(UInt32),
+ typeof(Int64),
+ typeof(UInt64),
+ typeof(Single),
+ typeof(Double),
+ typeof(Decimal),
+ typeof(DateTime),
+ typeof(Object), //TypeCode is discontinuous so we need a placeholder.
+ typeof(String)
+ };
+
+ // Need to special case Enum because typecode will be underlying type, e.g. Int32
+ private static readonly Type EnumType = typeof(Enum);
+
+ internal static readonly char[] base64Table = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
+ 'P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d',
+ 'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s',
+ 't','u','v','w','x','y','z','0','1','2','3','4','5','6','7',
+ '8','9','+','/','=' };
+
+ private const Int32 base64LineBreakPosition = 76;
+
+#if DEBUG
+ private static bool TriggerAsserts = DoAsserts();
+ private static bool DoAsserts()
+ {
+ Debug.Assert(ConvertTypes != null, "[Convert.cctor]ConvertTypes!=null");
+ Debug.Assert(ConvertTypes.Length == ((int)TypeCode.String + 1), "[Convert.cctor]ConvertTypes.Length == ((int)TypeCode.String + 1)");
+ Debug.Assert(ConvertTypes[(int)TypeCode.Empty] == typeof(System.Empty),
+ "[Convert.cctor]ConvertTypes[(int)TypeCode.Empty]==typeof(System.Empty)");
+ Debug.Assert(ConvertTypes[(int)TypeCode.String] == typeof(String),
+ "[Convert.cctor]ConvertTypes[(int)TypeCode.String]==typeof(System.String)");
+ Debug.Assert(ConvertTypes[(int)TypeCode.Int32] == typeof(int),
+ "[Convert.cctor]ConvertTypes[(int)TypeCode.Int32]==typeof(int)");
+ return true;
+ }
+#endif
+
+ public static readonly Object DBNull = System.DBNull.Value;
+
+ // Returns the type code for the given object. If the argument is null,
+ // the result is TypeCode.Empty. If the argument is not a value (i.e. if
+ // the object does not implement IConvertible), the result is TypeCode.Object.
+ // Otherwise, the result is the type code of the object, as determined by
+ // the object's implementation of IConvertible.
+ public static TypeCode GetTypeCode(object value)
+ {
+ if (value == null) return TypeCode.Empty;
+ IConvertible temp = value as IConvertible;
+ if (temp != null)
+ {
+ return temp.GetTypeCode();
+ }
+ return TypeCode.Object;
+ }
+
+ // Returns true if the given object is a database null. This operation
+ // corresponds to "value.GetTypeCode() == TypeCode.DBNull".
+ public static bool IsDBNull(object value)
+ {
+ if (value == System.DBNull.Value) return true;
+ IConvertible convertible = value as IConvertible;
+ return convertible != null ? convertible.GetTypeCode() == TypeCode.DBNull : false;
+ }
+
+ // Converts the given object to the given type. In general, this method is
+ // equivalent to calling the Value.ToXXX(value) method for the given
+ // typeCode and boxing the result.
+ //
+ // The method first checks if the given object implements IConvertible. If not,
+ // the only permitted conversion is from a null to TypeCode.Empty, the
+ // result of which is null.
+ //
+ // If the object does implement IConvertible, a check is made to see if the
+ // object already has the given type code, in which case the object is
+ // simply returned. Otherwise, the appropriate ToXXX() is invoked on the
+ // object's implementation of IConvertible.
+ public static Object ChangeType(Object value, TypeCode typeCode)
+ {
+ return ChangeType(value, typeCode, CultureInfo.CurrentCulture);
+ }
+
+ public static Object ChangeType(Object value, TypeCode typeCode, IFormatProvider provider)
+ {
+ if (value == null && (typeCode == TypeCode.Empty || typeCode == TypeCode.String || typeCode == TypeCode.Object))
+ {
+ return null;
+ }
+
+ IConvertible v = value as IConvertible;
+ if (v == null)
+ {
+ throw new InvalidCastException(SR.InvalidCast_IConvertible);
+ }
+
+ // This line is invalid for things like Enums that return a TypeCode
+ // of Int32, but the object can't actually be cast to an Int32.
+ // if (v.GetTypeCode() == typeCode) return value;
+ switch (typeCode)
+ {
+ case TypeCode.Boolean:
+ return v.ToBoolean(provider);
+ case TypeCode.Char:
+ return v.ToChar(provider);
+ case TypeCode.SByte:
+ return v.ToSByte(provider);
+ case TypeCode.Byte:
+ return v.ToByte(provider);
+ case TypeCode.Int16:
+ return v.ToInt16(provider);
+ case TypeCode.UInt16:
+ return v.ToUInt16(provider);
+ case TypeCode.Int32:
+ return v.ToInt32(provider);
+ case TypeCode.UInt32:
+ return v.ToUInt32(provider);
+ case TypeCode.Int64:
+ return v.ToInt64(provider);
+ case TypeCode.UInt64:
+ return v.ToUInt64(provider);
+ case TypeCode.Single:
+ return v.ToSingle(provider);
+ case TypeCode.Double:
+ return v.ToDouble(provider);
+ case TypeCode.Decimal:
+ return v.ToDecimal(provider);
+ case TypeCode.DateTime:
+ return v.ToDateTime(provider);
+ case TypeCode.String:
+ return v.ToString(provider);
+ case TypeCode.Object:
+ return value;
+ case TypeCode.DBNull:
+ throw new InvalidCastException(SR.InvalidCast_DBNull);
+ case TypeCode.Empty:
+ throw new InvalidCastException(SR.InvalidCast_Empty);
+ default:
+ throw new ArgumentException(SR.Arg_UnknownTypeCode);
+ }
+ }
+
+ internal static Object DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
+ {
+ Debug.Assert(value != null, "[Convert.DefaultToType]value!=null");
+ if (targetType == null)
+ {
+ throw new ArgumentNullException(nameof(targetType));
+ }
+
+ if (ReferenceEquals(value.GetType(), targetType))
+ {
+ return value;
+ }
+
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Boolean]))
+ return value.ToBoolean(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Char]))
+ return value.ToChar(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.SByte]))
+ return value.ToSByte(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Byte]))
+ return value.ToByte(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Int16]))
+ return value.ToInt16(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.UInt16]))
+ return value.ToUInt16(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Int32]))
+ return value.ToInt32(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.UInt32]))
+ return value.ToUInt32(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Int64]))
+ return value.ToInt64(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.UInt64]))
+ return value.ToUInt64(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Single]))
+ return value.ToSingle(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Double]))
+ return value.ToDouble(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Decimal]))
+ return value.ToDecimal(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.DateTime]))
+ return value.ToDateTime(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.String]))
+ return value.ToString(provider);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Object]))
+ return (Object)value;
+ // Need to special case Enum because typecode will be underlying type, e.g. Int32
+ if (ReferenceEquals(targetType, EnumType))
+ return (Enum)value;
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.DBNull]))
+ throw new InvalidCastException(SR.InvalidCast_DBNull);
+ if (ReferenceEquals(targetType, ConvertTypes[(int)TypeCode.Empty]))
+ throw new InvalidCastException(SR.InvalidCast_Empty);
+
+ throw new InvalidCastException(string.Format(SR.InvalidCast_FromTo, value.GetType().FullName, targetType.FullName));
+ }
+
+ public static Object ChangeType(Object value, Type conversionType)
+ {
+ return ChangeType(value, conversionType, CultureInfo.CurrentCulture);
+ }
+
+ public static Object ChangeType(Object value, Type conversionType, IFormatProvider provider)
+ {
+ if (ReferenceEquals(conversionType, null))
+ {
+ throw new ArgumentNullException(nameof(conversionType));
+ }
+
+ if (value == null)
+ {
+ if (conversionType.IsValueType)
+ {
+ throw new InvalidCastException(SR.InvalidCast_CannotCastNullToValueType);
+ }
+ return null;
+ }
+
+ IConvertible ic = value as IConvertible;
+ if (ic == null)
+ {
+ if (value.GetType() == conversionType)
+ {
+ return value;
+ }
+ throw new InvalidCastException(SR.InvalidCast_IConvertible);
+ }
+
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Boolean]))
+ return ic.ToBoolean(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Char]))
+ return ic.ToChar(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.SByte]))
+ return ic.ToSByte(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Byte]))
+ return ic.ToByte(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Int16]))
+ return ic.ToInt16(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.UInt16]))
+ return ic.ToUInt16(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Int32]))
+ return ic.ToInt32(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.UInt32]))
+ return ic.ToUInt32(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Int64]))
+ return ic.ToInt64(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.UInt64]))
+ return ic.ToUInt64(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Single]))
+ return ic.ToSingle(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Double]))
+ return ic.ToDouble(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Decimal]))
+ return ic.ToDecimal(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.DateTime]))
+ return ic.ToDateTime(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.String]))
+ return ic.ToString(provider);
+ if (ReferenceEquals(conversionType, ConvertTypes[(int)TypeCode.Object]))
+ return (Object)value;
+
+ return ic.ToType(conversionType, provider);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowCharOverflowException() { throw new OverflowException(SR.Overflow_Char); }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowByteOverflowException() { throw new OverflowException(SR.Overflow_Byte); }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowSByteOverflowException() { throw new OverflowException(SR.Overflow_SByte); }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowInt16OverflowException() { throw new OverflowException(SR.Overflow_Int16); }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowUInt16OverflowException() { throw new OverflowException(SR.Overflow_UInt16); }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowInt32OverflowException() { throw new OverflowException(SR.Overflow_Int32); }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowUInt32OverflowException() { throw new OverflowException(SR.Overflow_UInt32); }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowInt64OverflowException() { throw new OverflowException(SR.Overflow_Int64); }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowUInt64OverflowException() { throw new OverflowException(SR.Overflow_UInt64); }
+
+ // Conversions to Boolean
+ public static bool ToBoolean(Object value)
+ {
+ return value == null ? false : ((IConvertible)value).ToBoolean(null);
+ }
+
+ public static bool ToBoolean(Object value, IFormatProvider provider)
+ {
+ return value == null ? false : ((IConvertible)value).ToBoolean(provider);
+ }
+
+
+ public static bool ToBoolean(bool value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static bool ToBoolean(sbyte value)
+ {
+ return value != 0;
+ }
+
+ // To be consistent with IConvertible in the base data types else we get different semantics
+ // with widening operations. Without this operator this widen succeeds,with this API the widening throws.
+ public static bool ToBoolean(char value)
+ {
+ return ((IConvertible)value).ToBoolean(null);
+ }
+
+ public static bool ToBoolean(byte value)
+ {
+ return value != 0;
+ }
+
+
+ public static bool ToBoolean(short value)
+ {
+ return value != 0;
+ }
+
+ [CLSCompliant(false)]
+ public static bool ToBoolean(ushort value)
+ {
+ return value != 0;
+ }
+
+ public static bool ToBoolean(int value)
+ {
+ return value != 0;
+ }
+
+ [CLSCompliant(false)]
+ public static bool ToBoolean(uint value)
+ {
+ return value != 0;
+ }
+
+ public static bool ToBoolean(long value)
+ {
+ return value != 0;
+ }
+
+ [CLSCompliant(false)]
+ public static bool ToBoolean(ulong value)
+ {
+ return value != 0;
+ }
+
+ public static bool ToBoolean(String value)
+ {
+ if (value == null)
+ return false;
+ return Boolean.Parse(value);
+ }
+
+ public static bool ToBoolean(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return false;
+ return Boolean.Parse(value);
+ }
+
+ public static bool ToBoolean(float value)
+ {
+ return value != 0;
+ }
+
+ public static bool ToBoolean(double value)
+ {
+ return value != 0;
+ }
+
+ public static bool ToBoolean(decimal value)
+ {
+ return value != 0;
+ }
+
+ public static bool ToBoolean(DateTime value)
+ {
+ return ((IConvertible)value).ToBoolean(null);
+ }
+
+ // Disallowed conversions to Boolean
+ // public static bool ToBoolean(TimeSpan value)
+
+ // Conversions to Char
+
+
+ public static char ToChar(object value)
+ {
+ return value == null ? (char)0 : ((IConvertible)value).ToChar(null);
+ }
+
+ public static char ToChar(object value, IFormatProvider provider)
+ {
+ return value == null ? (char)0 : ((IConvertible)value).ToChar(provider);
+ }
+
+ public static char ToChar(bool value)
+ {
+ return ((IConvertible)value).ToChar(null);
+ }
+
+ public static char ToChar(char value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static char ToChar(sbyte value)
+ {
+ if (value < 0) ThrowCharOverflowException();
+ return (char)value;
+ }
+
+ public static char ToChar(byte value)
+ {
+ return (char)value;
+ }
+
+ public static char ToChar(short value)
+ {
+ if (value < 0) ThrowCharOverflowException();
+ return (char)value;
+ }
+
+ [CLSCompliant(false)]
+ public static char ToChar(ushort value)
+ {
+ return (char)value;
+ }
+
+ public static char ToChar(int value)
+ {
+ if (value < 0 || value > Char.MaxValue) ThrowCharOverflowException();
+ return (char)value;
+ }
+
+ [CLSCompliant(false)]
+ public static char ToChar(uint value)
+ {
+ if (value > Char.MaxValue) ThrowCharOverflowException();
+ return (char)value;
+ }
+
+ public static char ToChar(long value)
+ {
+ if (value < 0 || value > Char.MaxValue) ThrowCharOverflowException();
+ return (char)value;
+ }
+
+ [CLSCompliant(false)]
+ public static char ToChar(ulong value)
+ {
+ if (value > Char.MaxValue) ThrowCharOverflowException();
+ return (char)value;
+ }
+
+ //
+ // @VariantSwitch
+ // Remove FormatExceptions;
+ //
+ public static char ToChar(String value)
+ {
+ return ToChar(value, null);
+ }
+
+ public static char ToChar(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (value.Length != 1)
+ throw new FormatException(SR.Format_NeedSingleChar);
+
+ return value[0];
+ }
+
+ // To be consistent with IConvertible in the base data types else we get different semantics
+ // with widening operations. Without this operator this widen succeeds,with this API the widening throws.
+ public static char ToChar(float value)
+ {
+ return ((IConvertible)value).ToChar(null);
+ }
+
+ // To be consistent with IConvertible in the base data types else we get different semantics
+ // with widening operations. Without this operator this widen succeeds,with this API the widening throws.
+ public static char ToChar(double value)
+ {
+ return ((IConvertible)value).ToChar(null);
+ }
+
+ // To be consistent with IConvertible in the base data types else we get different semantics
+ // with widening operations. Without this operator this widen succeeds,with this API the widening throws.
+ public static char ToChar(decimal value)
+ {
+ return ((IConvertible)value).ToChar(null);
+ }
+
+ public static char ToChar(DateTime value)
+ {
+ return ((IConvertible)value).ToChar(null);
+ }
+
+
+ // Disallowed conversions to Char
+ // public static char ToChar(TimeSpan value)
+
+ // Conversions to SByte
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(object value)
+ {
+ return value == null ? (sbyte)0 : ((IConvertible)value).ToSByte(null);
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(object value, IFormatProvider provider)
+ {
+ return value == null ? (sbyte)0 : ((IConvertible)value).ToSByte(provider);
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(bool value)
+ {
+ return value ? (sbyte)Boolean.True : (sbyte)Boolean.False;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(sbyte value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(char value)
+ {
+ if (value > SByte.MaxValue) ThrowSByteOverflowException();
+ return (sbyte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(byte value)
+ {
+ if (value > SByte.MaxValue) ThrowSByteOverflowException();
+ return (sbyte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(short value)
+ {
+ if (value < SByte.MinValue || value > SByte.MaxValue) ThrowSByteOverflowException();
+ return (sbyte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(ushort value)
+ {
+ if (value > SByte.MaxValue) ThrowSByteOverflowException();
+ return (sbyte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(int value)
+ {
+ if (value < SByte.MinValue || value > SByte.MaxValue) ThrowSByteOverflowException();
+ return (sbyte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(uint value)
+ {
+ if (value > SByte.MaxValue) ThrowSByteOverflowException();
+ return (sbyte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(long value)
+ {
+ if (value < SByte.MinValue || value > SByte.MaxValue) ThrowSByteOverflowException();
+ return (sbyte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(ulong value)
+ {
+ if (value > (ulong)SByte.MaxValue) ThrowSByteOverflowException();
+ return (sbyte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(float value)
+ {
+ return ToSByte((double)value);
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(double value)
+ {
+ return ToSByte(ToInt32(value));
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(decimal value)
+ {
+ return Decimal.ToSByte(Decimal.Round(value, 0));
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(String value)
+ {
+ if (value == null)
+ return 0;
+ return SByte.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(String value, IFormatProvider provider)
+ {
+ return SByte.Parse(value, NumberStyles.Integer, provider);
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(DateTime value)
+ {
+ return ((IConvertible)value).ToSByte(null);
+ }
+
+ // Disallowed conversions to SByte
+ // public static sbyte ToSByte(TimeSpan value)
+
+ // Conversions to Byte
+
+ public static byte ToByte(object value)
+ {
+ return value == null ? (byte)0 : ((IConvertible)value).ToByte(null);
+ }
+
+ public static byte ToByte(object value, IFormatProvider provider)
+ {
+ return value == null ? (byte)0 : ((IConvertible)value).ToByte(provider);
+ }
+
+ public static byte ToByte(bool value)
+ {
+ return value ? (byte)Boolean.True : (byte)Boolean.False;
+ }
+
+ public static byte ToByte(byte value)
+ {
+ return value;
+ }
+
+ public static byte ToByte(char value)
+ {
+ if (value > Byte.MaxValue) ThrowByteOverflowException();
+ return (byte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static byte ToByte(sbyte value)
+ {
+ if (value < Byte.MinValue) ThrowByteOverflowException();
+ return (byte)value;
+ }
+
+ public static byte ToByte(short value)
+ {
+ if (value < Byte.MinValue || value > Byte.MaxValue) ThrowByteOverflowException();
+ return (byte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static byte ToByte(ushort value)
+ {
+ if (value > Byte.MaxValue) ThrowByteOverflowException();
+ return (byte)value;
+ }
+
+ public static byte ToByte(int value)
+ {
+ if (value < Byte.MinValue || value > Byte.MaxValue) ThrowByteOverflowException();
+ return (byte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static byte ToByte(uint value)
+ {
+ if (value > Byte.MaxValue) ThrowByteOverflowException();
+ return (byte)value;
+ }
+
+ public static byte ToByte(long value)
+ {
+ if (value < Byte.MinValue || value > Byte.MaxValue) ThrowByteOverflowException();
+ return (byte)value;
+ }
+
+ [CLSCompliant(false)]
+ public static byte ToByte(ulong value)
+ {
+ if (value > Byte.MaxValue) ThrowByteOverflowException();
+ return (byte)value;
+ }
+
+ public static byte ToByte(float value)
+ {
+ return ToByte((double)value);
+ }
+
+ public static byte ToByte(double value)
+ {
+ return ToByte(ToInt32(value));
+ }
+
+ public static byte ToByte(decimal value)
+ {
+ return Decimal.ToByte(Decimal.Round(value, 0));
+ }
+
+ public static byte ToByte(String value)
+ {
+ if (value == null)
+ return 0;
+ return Byte.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ public static byte ToByte(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return Byte.Parse(value, NumberStyles.Integer, provider);
+ }
+
+ public static byte ToByte(DateTime value)
+ {
+ return ((IConvertible)value).ToByte(null);
+ }
+
+
+ // Disallowed conversions to Byte
+ // public static byte ToByte(TimeSpan value)
+
+ // Conversions to Int16
+
+ public static short ToInt16(object value)
+ {
+ return value == null ? (short)0 : ((IConvertible)value).ToInt16(null);
+ }
+
+ public static short ToInt16(object value, IFormatProvider provider)
+ {
+ return value == null ? (short)0 : ((IConvertible)value).ToInt16(provider);
+ }
+
+ public static short ToInt16(bool value)
+ {
+ return value ? (short)Boolean.True : (short)Boolean.False;
+ }
+
+ public static short ToInt16(char value)
+ {
+ if (value > Int16.MaxValue) ThrowInt16OverflowException();
+ return (short)value;
+ }
+
+ [CLSCompliant(false)]
+ public static short ToInt16(sbyte value)
+ {
+ return value;
+ }
+
+ public static short ToInt16(byte value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static short ToInt16(ushort value)
+ {
+ if (value > Int16.MaxValue) ThrowInt16OverflowException();
+ return (short)value;
+ }
+
+ public static short ToInt16(int value)
+ {
+ if (value < Int16.MinValue || value > Int16.MaxValue) ThrowInt16OverflowException();
+ return (short)value;
+ }
+
+ [CLSCompliant(false)]
+ public static short ToInt16(uint value)
+ {
+ if (value > Int16.MaxValue) ThrowInt16OverflowException();
+ return (short)value;
+ }
+
+ public static short ToInt16(short value)
+ {
+ return value;
+ }
+
+ public static short ToInt16(long value)
+ {
+ if (value < Int16.MinValue || value > Int16.MaxValue) ThrowInt16OverflowException();
+ return (short)value;
+ }
+
+ [CLSCompliant(false)]
+ public static short ToInt16(ulong value)
+ {
+ if (value > (ulong)Int16.MaxValue) ThrowInt16OverflowException();
+ return (short)value;
+ }
+
+ public static short ToInt16(float value)
+ {
+ return ToInt16((double)value);
+ }
+
+ public static short ToInt16(double value)
+ {
+ return ToInt16(ToInt32(value));
+ }
+
+ public static short ToInt16(decimal value)
+ {
+ return Decimal.ToInt16(Decimal.Round(value, 0));
+ }
+
+ public static short ToInt16(String value)
+ {
+ if (value == null)
+ return 0;
+ return Int16.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ public static short ToInt16(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return Int16.Parse(value, NumberStyles.Integer, provider);
+ }
+
+ public static short ToInt16(DateTime value)
+ {
+ return ((IConvertible)value).ToInt16(null);
+ }
+
+
+ // Disallowed conversions to Int16
+ // public static short ToInt16(TimeSpan value)
+
+ // Conversions to UInt16
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(object value)
+ {
+ return value == null ? (ushort)0 : ((IConvertible)value).ToUInt16(null);
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(object value, IFormatProvider provider)
+ {
+ return value == null ? (ushort)0 : ((IConvertible)value).ToUInt16(provider);
+ }
+
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(bool value)
+ {
+ return value ? (ushort)Boolean.True : (ushort)Boolean.False;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(char value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(sbyte value)
+ {
+ if (value < 0) ThrowUInt16OverflowException();
+ return (ushort)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(byte value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(short value)
+ {
+ if (value < 0) ThrowUInt16OverflowException();
+ return (ushort)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(int value)
+ {
+ if (value < 0 || value > UInt16.MaxValue) ThrowUInt16OverflowException();
+ return (ushort)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(ushort value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(uint value)
+ {
+ if (value > UInt16.MaxValue) ThrowUInt16OverflowException();
+ return (ushort)value;
+ }
+
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(long value)
+ {
+ if (value < 0 || value > UInt16.MaxValue) ThrowUInt16OverflowException();
+ return (ushort)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(ulong value)
+ {
+ if (value > UInt16.MaxValue) ThrowUInt16OverflowException();
+ return (ushort)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(float value)
+ {
+ return ToUInt16((double)value);
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(double value)
+ {
+ return ToUInt16(ToInt32(value));
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(decimal value)
+ {
+ return Decimal.ToUInt16(Decimal.Round(value, 0));
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(String value)
+ {
+ if (value == null)
+ return 0;
+ return UInt16.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return UInt16.Parse(value, NumberStyles.Integer, provider);
+ }
+
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(DateTime value)
+ {
+ return ((IConvertible)value).ToUInt16(null);
+ }
+
+ // Disallowed conversions to UInt16
+ // public static ushort ToUInt16(TimeSpan value)
+
+ // Conversions to Int32
+
+ public static int ToInt32(object value)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToInt32(null);
+ }
+
+ public static int ToInt32(object value, IFormatProvider provider)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToInt32(provider);
+ }
+
+
+ public static int ToInt32(bool value)
+ {
+ return value ? Boolean.True : Boolean.False;
+ }
+
+ public static int ToInt32(char value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static int ToInt32(sbyte value)
+ {
+ return value;
+ }
+
+ public static int ToInt32(byte value)
+ {
+ return value;
+ }
+
+ public static int ToInt32(short value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static int ToInt32(ushort value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static int ToInt32(uint value)
+ {
+ if (value > Int32.MaxValue) ThrowInt32OverflowException();
+ return (int)value;
+ }
+
+ public static int ToInt32(int value)
+ {
+ return value;
+ }
+
+ public static int ToInt32(long value)
+ {
+ if (value < Int32.MinValue || value > Int32.MaxValue) ThrowInt32OverflowException();
+ return (int)value;
+ }
+
+ [CLSCompliant(false)]
+ public static int ToInt32(ulong value)
+ {
+ if (value > Int32.MaxValue) ThrowInt32OverflowException();
+ return (int)value;
+ }
+
+ public static int ToInt32(float value)
+ {
+ return ToInt32((double)value);
+ }
+
+ public static int ToInt32(double value)
+ {
+ if (value >= 0)
+ {
+ if (value < 2147483647.5)
+ {
+ int result = (int)value;
+ double dif = value - result;
+ if (dif > 0.5 || dif == 0.5 && (result & 1) != 0) result++;
+ return result;
+ }
+ }
+ else
+ {
+ if (value >= -2147483648.5)
+ {
+ int result = (int)value;
+ double dif = value - result;
+ if (dif < -0.5 || dif == -0.5 && (result & 1) != 0) result--;
+ return result;
+ }
+ }
+ throw new OverflowException(SR.Overflow_Int32);
+ }
+
+ public static int ToInt32(decimal value)
+ {
+ return Decimal.ToInt32(Decimal.Round(value, 0));
+ }
+
+ public static int ToInt32(String value)
+ {
+ if (value == null)
+ return 0;
+ return Int32.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ public static int ToInt32(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return Int32.Parse(value, NumberStyles.Integer, provider);
+ }
+
+ public static int ToInt32(DateTime value)
+ {
+ return ((IConvertible)value).ToInt32(null);
+ }
+
+
+ // Disallowed conversions to Int32
+ // public static int ToInt32(TimeSpan value)
+
+ // Conversions to UInt32
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(object value)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToUInt32(null);
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(object value, IFormatProvider provider)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToUInt32(provider);
+ }
+
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(bool value)
+ {
+ return value ? (uint)Boolean.True : (uint)Boolean.False;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(char value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(sbyte value)
+ {
+ if (value < 0) ThrowUInt32OverflowException();
+ return (uint)value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(byte value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(short value)
+ {
+ if (value < 0) ThrowUInt32OverflowException();
+ return (uint)value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(ushort value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(int value)
+ {
+ if (value < 0) ThrowUInt32OverflowException();
+ return (uint)value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(uint value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(long value)
+ {
+ if (value < 0 || value > UInt32.MaxValue) ThrowUInt32OverflowException();
+ return (uint)value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(ulong value)
+ {
+ if (value > UInt32.MaxValue) ThrowUInt32OverflowException();
+ return (uint)value;
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(float value)
+ {
+ return ToUInt32((double)value);
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(double value)
+ {
+ if (value >= -0.5 && value < 4294967295.5)
+ {
+ uint result = (uint)value;
+ double dif = value - result;
+ if (dif > 0.5 || dif == 0.5 && (result & 1) != 0) result++;
+ return result;
+ }
+ throw new OverflowException(SR.Overflow_UInt32);
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(decimal value)
+ {
+ return Decimal.ToUInt32(Decimal.Round(value, 0));
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(String value)
+ {
+ if (value == null)
+ return 0;
+ return UInt32.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return UInt32.Parse(value, NumberStyles.Integer, provider);
+ }
+
+ [CLSCompliant(false)]
+ public static uint ToUInt32(DateTime value)
+ {
+ return ((IConvertible)value).ToUInt32(null);
+ }
+
+ // Disallowed conversions to UInt32
+ // public static uint ToUInt32(TimeSpan value)
+
+ // Conversions to Int64
+
+ public static long ToInt64(object value)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToInt64(null);
+ }
+
+ public static long ToInt64(object value, IFormatProvider provider)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToInt64(provider);
+ }
+
+
+ public static long ToInt64(bool value)
+ {
+ return value ? Boolean.True : Boolean.False;
+ }
+
+ public static long ToInt64(char value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static long ToInt64(sbyte value)
+ {
+ return value;
+ }
+
+ public static long ToInt64(byte value)
+ {
+ return value;
+ }
+
+ public static long ToInt64(short value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static long ToInt64(ushort value)
+ {
+ return value;
+ }
+
+ public static long ToInt64(int value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static long ToInt64(uint value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static long ToInt64(ulong value)
+ {
+ if (value > Int64.MaxValue) ThrowInt64OverflowException();
+ return (long)value;
+ }
+
+ public static long ToInt64(long value)
+ {
+ return value;
+ }
+
+
+ public static long ToInt64(float value)
+ {
+ return ToInt64((double)value);
+ }
+
+ public static long ToInt64(double value)
+ {
+ return checked((long)Math.Round(value));
+ }
+
+ public static long ToInt64(decimal value)
+ {
+ return Decimal.ToInt64(Decimal.Round(value, 0));
+ }
+
+ public static long ToInt64(string value)
+ {
+ if (value == null)
+ return 0;
+ return Int64.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ public static long ToInt64(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return Int64.Parse(value, NumberStyles.Integer, provider);
+ }
+
+ public static long ToInt64(DateTime value)
+ {
+ return ((IConvertible)value).ToInt64(null);
+ }
+
+ // Disallowed conversions to Int64
+ // public static long ToInt64(TimeSpan value)
+
+ // Conversions to UInt64
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(object value)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToUInt64(null);
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(object value, IFormatProvider provider)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToUInt64(provider);
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(bool value)
+ {
+ return value ? (ulong)Boolean.True : (ulong)Boolean.False;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(char value)
+ {
+ return value;
+ }
+
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(sbyte value)
+ {
+ if (value < 0) ThrowUInt64OverflowException();
+ return (ulong)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(byte value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(short value)
+ {
+ if (value < 0) ThrowUInt64OverflowException();
+ return (ulong)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(ushort value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(int value)
+ {
+ if (value < 0) ThrowUInt64OverflowException();
+ return (ulong)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(uint value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(long value)
+ {
+ if (value < 0) ThrowUInt64OverflowException();
+ return (ulong)value;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(UInt64 value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(float value)
+ {
+ return ToUInt64((double)value);
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(double value)
+ {
+ return checked((ulong)Math.Round(value));
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(decimal value)
+ {
+ return Decimal.ToUInt64(Decimal.Round(value, 0));
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(String value)
+ {
+ if (value == null)
+ return 0;
+ return UInt64.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return UInt64.Parse(value, NumberStyles.Integer, provider);
+ }
+
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(DateTime value)
+ {
+ return ((IConvertible)value).ToUInt64(null);
+ }
+
+ // Disallowed conversions to UInt64
+ // public static ulong ToUInt64(TimeSpan value)
+
+ // Conversions to Single
+
+ public static float ToSingle(object value)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToSingle(null);
+ }
+
+ public static float ToSingle(object value, IFormatProvider provider)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToSingle(provider);
+ }
+
+ [CLSCompliant(false)]
+ public static float ToSingle(sbyte value)
+ {
+ return value;
+ }
+
+ public static float ToSingle(byte value)
+ {
+ return value;
+ }
+
+ public static float ToSingle(char value)
+ {
+ return ((IConvertible)value).ToSingle(null);
+ }
+
+ public static float ToSingle(short value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static float ToSingle(ushort value)
+ {
+ return value;
+ }
+
+ public static float ToSingle(int value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static float ToSingle(uint value)
+ {
+ return value;
+ }
+
+ public static float ToSingle(long value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static float ToSingle(ulong value)
+ {
+ return value;
+ }
+
+ public static float ToSingle(float value)
+ {
+ return value;
+ }
+
+ public static float ToSingle(double value)
+ {
+ return (float)value;
+ }
+
+ public static float ToSingle(decimal value)
+ {
+ return (float)value;
+ }
+
+ public static float ToSingle(String value)
+ {
+ if (value == null)
+ return 0;
+ return Single.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ public static float ToSingle(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return Single.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, provider);
+ }
+
+
+ public static float ToSingle(bool value)
+ {
+ return value ? Boolean.True : Boolean.False;
+ }
+
+ public static float ToSingle(DateTime value)
+ {
+ return ((IConvertible)value).ToSingle(null);
+ }
+
+ // Disallowed conversions to Single
+ // public static float ToSingle(TimeSpan value)
+
+ // Conversions to Double
+
+ public static double ToDouble(object value)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToDouble(null);
+ }
+
+ public static double ToDouble(object value, IFormatProvider provider)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToDouble(provider);
+ }
+
+
+ [CLSCompliant(false)]
+ public static double ToDouble(sbyte value)
+ {
+ return value;
+ }
+
+ public static double ToDouble(byte value)
+ {
+ return value;
+ }
+
+ public static double ToDouble(short value)
+ {
+ return value;
+ }
+
+ public static double ToDouble(char value)
+ {
+ return ((IConvertible)value).ToDouble(null);
+ }
+
+ [CLSCompliant(false)]
+ public static double ToDouble(ushort value)
+ {
+ return value;
+ }
+
+ public static double ToDouble(int value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static double ToDouble(uint value)
+ {
+ return value;
+ }
+
+ public static double ToDouble(long value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static double ToDouble(ulong value)
+ {
+ return value;
+ }
+
+ public static double ToDouble(float value)
+ {
+ return value;
+ }
+
+ public static double ToDouble(double value)
+ {
+ return value;
+ }
+
+ public static double ToDouble(decimal value)
+ {
+ return (double)value;
+ }
+
+ public static double ToDouble(String value)
+ {
+ if (value == null)
+ return 0;
+ return Double.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ public static double ToDouble(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0;
+ return Double.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, provider);
+ }
+
+ public static double ToDouble(bool value)
+ {
+ return value ? Boolean.True : Boolean.False;
+ }
+
+ public static double ToDouble(DateTime value)
+ {
+ return ((IConvertible)value).ToDouble(null);
+ }
+
+ // Disallowed conversions to Double
+ // public static double ToDouble(TimeSpan value)
+
+ // Conversions to Decimal
+
+ public static decimal ToDecimal(object value)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToDecimal(null);
+ }
+
+ public static decimal ToDecimal(object value, IFormatProvider provider)
+ {
+ return value == null ? 0 : ((IConvertible)value).ToDecimal(provider);
+ }
+
+ [CLSCompliant(false)]
+ public static decimal ToDecimal(sbyte value)
+ {
+ return value;
+ }
+
+ public static decimal ToDecimal(byte value)
+ {
+ return value;
+ }
+
+ public static decimal ToDecimal(char value)
+ {
+ return ((IConvertible)value).ToDecimal(null);
+ }
+
+ public static decimal ToDecimal(short value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static decimal ToDecimal(ushort value)
+ {
+ return value;
+ }
+
+ public static decimal ToDecimal(int value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static decimal ToDecimal(uint value)
+ {
+ return value;
+ }
+
+ public static decimal ToDecimal(long value)
+ {
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public static decimal ToDecimal(ulong value)
+ {
+ return value;
+ }
+
+ public static decimal ToDecimal(float value)
+ {
+ return (decimal)value;
+ }
+
+ public static decimal ToDecimal(double value)
+ {
+ return (decimal)value;
+ }
+
+ public static decimal ToDecimal(String value)
+ {
+ if (value == null)
+ return 0m;
+ return Decimal.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ public static Decimal ToDecimal(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return 0m;
+ return Decimal.Parse(value, NumberStyles.Number, provider);
+ }
+
+ public static decimal ToDecimal(decimal value)
+ {
+ return value;
+ }
+
+ public static decimal ToDecimal(bool value)
+ {
+ return value ? Boolean.True : Boolean.False;
+ }
+
+ public static decimal ToDecimal(DateTime value)
+ {
+ return ((IConvertible)value).ToDecimal(null);
+ }
+
+ // Disallowed conversions to Decimal
+ // public static decimal ToDecimal(TimeSpan value)
+
+ // Conversions to DateTime
+
+ public static DateTime ToDateTime(DateTime value)
+ {
+ return value;
+ }
+
+ public static DateTime ToDateTime(object value)
+ {
+ return value == null ? DateTime.MinValue : ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(object value, IFormatProvider provider)
+ {
+ return value == null ? DateTime.MinValue : ((IConvertible)value).ToDateTime(provider);
+ }
+
+ public static DateTime ToDateTime(String value)
+ {
+ if (value == null)
+ return new DateTime(0);
+ return DateTime.Parse(value, CultureInfo.CurrentCulture);
+ }
+
+ public static DateTime ToDateTime(String value, IFormatProvider provider)
+ {
+ if (value == null)
+ return new DateTime(0);
+ return DateTime.Parse(value, provider);
+ }
+
+ [CLSCompliant(false)]
+ public static DateTime ToDateTime(sbyte value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(byte value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(short value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ [CLSCompliant(false)]
+ public static DateTime ToDateTime(ushort value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(int value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ [CLSCompliant(false)]
+ public static DateTime ToDateTime(uint value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(long value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ [CLSCompliant(false)]
+ public static DateTime ToDateTime(ulong value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(bool value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(char value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(float value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(double value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ public static DateTime ToDateTime(decimal value)
+ {
+ return ((IConvertible)value).ToDateTime(null);
+ }
+
+ // Disallowed conversions to DateTime
+ // public static DateTime ToDateTime(TimeSpan value)
+
+ // Conversions to String
+
+ public static string ToString(Object value)
+ {
+ return ToString(value, null);
+ }
+
+ public static string ToString(Object value, IFormatProvider provider)
+ {
+ IConvertible ic = value as IConvertible;
+ if (ic != null)
+ return ic.ToString(provider);
+ IFormattable formattable = value as IFormattable;
+ if (formattable != null)
+ return formattable.ToString(null, provider);
+ return value == null ? String.Empty : value.ToString();
+ }
+
+ public static string ToString(bool value)
+ {
+ return value.ToString();
+ }
+
+ public static string ToString(bool value, IFormatProvider provider)
+ {
+ return value.ToString();
+ }
+
+ public static string ToString(char value)
+ {
+ return Char.ToString(value);
+ }
+
+ public static string ToString(char value, IFormatProvider provider)
+ {
+ return value.ToString();
+ }
+
+ [CLSCompliant(false)]
+ public static string ToString(sbyte value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ [CLSCompliant(false)]
+ public static string ToString(sbyte value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static string ToString(byte value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ public static string ToString(byte value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static string ToString(short value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ public static string ToString(short value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ [CLSCompliant(false)]
+ public static string ToString(ushort value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ [CLSCompliant(false)]
+ public static string ToString(ushort value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static string ToString(int value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ public static string ToString(int value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ [CLSCompliant(false)]
+ public static string ToString(uint value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ [CLSCompliant(false)]
+ public static string ToString(uint value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static string ToString(long value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ public static string ToString(long value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ [CLSCompliant(false)]
+ public static string ToString(ulong value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ [CLSCompliant(false)]
+ public static string ToString(ulong value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static string ToString(float value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ public static string ToString(float value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static string ToString(double value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ public static string ToString(double value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static string ToString(decimal value)
+ {
+ return value.ToString(CultureInfo.CurrentCulture);
+ }
+
+ public static string ToString(Decimal value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static string ToString(DateTime value)
+ {
+ return value.ToString();
+ }
+
+ public static string ToString(DateTime value, IFormatProvider provider)
+ {
+ return value.ToString(provider);
+ }
+
+ public static String ToString(String value)
+ {
+ return value;
+ }
+
+ public static String ToString(String value, IFormatProvider provider)
+ {
+ return value; // avoid the null check
+ }
+
+
+ //
+ // Conversions which understand Base XXX numbers.
+ //
+ // Parses value in base base. base can only
+ // be 2, 8, 10, or 16. If base is 16, the number may be preceded
+ // by 0x; any other leading or trailing characters cause an error.
+ //
+ public static byte ToByte(String value, int fromBase)
+ {
+ if (fromBase != 2 && fromBase != 8 && fromBase != 10 && fromBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+
+ if (value == null)
+ {
+ return 0;
+ }
+
+ int r = ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsUnsigned);
+ if (r < Byte.MinValue || r > Byte.MaxValue)
+ ThrowByteOverflowException();
+ return (byte)r;
+ }
+
+ // Parses value in base fromBase. fromBase can only
+ // be 2, 8, 10, or 16. If fromBase is 16, the number may be preceded
+ // by 0x; any other leading or trailing characters cause an error.
+ //
+ [CLSCompliant(false)]
+ public static sbyte ToSByte(String value, int fromBase)
+ {
+ if (fromBase != 2 && fromBase != 8 && fromBase != 10 && fromBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+
+ if (value == null)
+ {
+ return 0;
+ }
+
+ int r = ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsI1);
+ if (fromBase != 10 && r <= Byte.MaxValue)
+ return (sbyte)r;
+
+ if (r < SByte.MinValue || r > SByte.MaxValue)
+ ThrowSByteOverflowException();
+ return (sbyte)r;
+ }
+
+ // Parses value in base fromBase. fromBase can only
+ // be 2, 8, 10, or 16. If fromBase is 16, the number may be preceded
+ // by 0x; any other leading or trailing characters cause an error.
+ //
+ public static short ToInt16(String value, int fromBase)
+ {
+ if (fromBase != 2 && fromBase != 8 && fromBase != 10 && fromBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+
+ if (value == null)
+ {
+ return 0;
+ }
+
+ int r = ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsI2);
+ if (fromBase != 10 && r <= UInt16.MaxValue)
+ return (short)r;
+
+ if (r < Int16.MinValue || r > Int16.MaxValue)
+ ThrowInt16OverflowException();
+ return (short)r;
+ }
+
+ // Parses value in base fromBase. fromBase can only
+ // be 2, 8, 10, or 16. If fromBase is 16, the number may be preceded
+ // by 0x; any other leading or trailing characters cause an error.
+ //
+ [CLSCompliant(false)]
+ public static ushort ToUInt16(String value, int fromBase)
+ {
+ if (fromBase != 2 && fromBase != 8 && fromBase != 10 && fromBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+
+ if (value == null)
+ {
+ return 0;
+ }
+
+ int r = ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight | ParseNumbers.TreatAsUnsigned);
+ if (r < UInt16.MinValue || r > UInt16.MaxValue)
+ ThrowUInt16OverflowException();
+ return (ushort)r;
+ }
+
+ // Parses value in base fromBase. fromBase can only
+ // be 2, 8, 10, or 16. If fromBase is 16, the number may be preceded
+ // by 0x; any other leading or trailing characters cause an error.
+ //
+ public static int ToInt32(String value, int fromBase)
+ {
+ if (fromBase != 2 && fromBase != 8 && fromBase != 10 && fromBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+ return value != null ?
+ ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.IsTight) :
+ 0;
+ }
+
+ // Parses value in base fromBase. fromBase can only
+ // be 2, 8, 10, or 16. If fromBase is 16, the number may be preceded
+ // by 0x; any other leading or trailing characters cause an error.
+ //
+ [CLSCompliant(false)]
+ public static uint ToUInt32(String value, int fromBase)
+ {
+ if (fromBase != 2 && fromBase != 8 && fromBase != 10 && fromBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+ return value != null ?
+ (uint)ParseNumbers.StringToInt(value.AsSpan(), fromBase, ParseNumbers.TreatAsUnsigned | ParseNumbers.IsTight) :
+ 0;
+ }
+
+ // Parses value in base fromBase. fromBase can only
+ // be 2, 8, 10, or 16. If fromBase is 16, the number may be preceded
+ // by 0x; any other leading or trailing characters cause an error.
+ //
+ public static long ToInt64(String value, int fromBase)
+ {
+ if (fromBase != 2 && fromBase != 8 && fromBase != 10 && fromBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+ return value != null ?
+ ParseNumbers.StringToLong(value.AsSpan(), fromBase, ParseNumbers.IsTight) :
+ 0;
+ }
+
+ // Parses value in base fromBase. fromBase can only
+ // be 2, 8, 10, or 16. If fromBase is 16, the number may be preceded
+ // by 0x; any other leading or trailing characters cause an error.
+ //
+ [CLSCompliant(false)]
+ public static ulong ToUInt64(String value, int fromBase)
+ {
+ if (fromBase != 2 && fromBase != 8 && fromBase != 10 && fromBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+ return value != null ?
+ (ulong)ParseNumbers.StringToLong(value.AsSpan(), fromBase, ParseNumbers.TreatAsUnsigned | ParseNumbers.IsTight) :
+ 0;
+ }
+
+ // Convert the byte value to a string in base fromBase
+ public static String ToString(byte value, int toBase)
+ {
+ if (toBase != 2 && toBase != 8 && toBase != 10 && toBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+ return ParseNumbers.IntToString((int)value, toBase, -1, ' ', ParseNumbers.PrintAsI1);
+ }
+
+ // Convert the Int16 value to a string in base fromBase
+ public static String ToString(short value, int toBase)
+ {
+ if (toBase != 2 && toBase != 8 && toBase != 10 && toBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+ return ParseNumbers.IntToString((int)value, toBase, -1, ' ', ParseNumbers.PrintAsI2);
+ }
+
+ // Convert the Int32 value to a string in base toBase
+ public static String ToString(int value, int toBase)
+ {
+ if (toBase != 2 && toBase != 8 && toBase != 10 && toBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+ return ParseNumbers.IntToString(value, toBase, -1, ' ', 0);
+ }
+
+ // Convert the Int64 value to a string in base toBase
+ public static String ToString(long value, int toBase)
+ {
+ if (toBase != 2 && toBase != 8 && toBase != 10 && toBase != 16)
+ {
+ throw new ArgumentException(SR.Arg_InvalidBase);
+ }
+ return ParseNumbers.LongToString(value, toBase, -1, ' ', 0);
+ }
+
+ public static String ToBase64String(byte[] inArray)
+ {
+ if (inArray == null)
+ {
+ throw new ArgumentNullException(nameof(inArray));
+ }
+ return ToBase64String(new ReadOnlySpan<byte>(inArray), Base64FormattingOptions.None);
+ }
+
+ public static String ToBase64String(byte[] inArray, Base64FormattingOptions options)
+ {
+ if (inArray == null)
+ {
+ throw new ArgumentNullException(nameof(inArray));
+ }
+ return ToBase64String(new ReadOnlySpan<byte>(inArray), options);
+ }
+
+ public static String ToBase64String(byte[] inArray, int offset, int length)
+ {
+ return ToBase64String(inArray, offset, length, Base64FormattingOptions.None);
+ }
+
+ public static String ToBase64String(byte[] inArray, int offset, int length, Base64FormattingOptions options)
+ {
+ if (inArray == null)
+ throw new ArgumentNullException(nameof(inArray));
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Index);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_GenericPositive);
+ if (offset > (inArray.Length - length))
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_OffsetLength);
+
+ return ToBase64String(new ReadOnlySpan<byte>(inArray, offset, length), options);
+ }
+
+ public static string ToBase64String(ReadOnlySpan<byte> bytes, Base64FormattingOptions options = Base64FormattingOptions.None)
+ {
+ if (options < Base64FormattingOptions.None || options > Base64FormattingOptions.InsertLineBreaks)
+ {
+ throw new ArgumentException(string.Format(SR.Arg_EnumIllegalVal, (int)options), nameof(options));
+ }
+
+ if (bytes.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ bool insertLineBreaks = (options == Base64FormattingOptions.InsertLineBreaks);
+ string result = string.FastAllocateString(ToBase64_CalculateAndValidateOutputLength(bytes.Length, insertLineBreaks));
+
+ unsafe
+ {
+ fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes))
+ fixed (char* charsPtr = result)
+ {
+ int charsWritten = ConvertToBase64Array(charsPtr, bytesPtr, 0, bytes.Length, insertLineBreaks);
+ Debug.Assert(result.Length == charsWritten, $"Expected {result.Length} == {charsWritten}");
+ }
+ }
+
+ return result;
+ }
+
+ public static int ToBase64CharArray(byte[] inArray, int offsetIn, int length, char[] outArray, int offsetOut)
+ {
+ return ToBase64CharArray(inArray, offsetIn, length, outArray, offsetOut, Base64FormattingOptions.None);
+ }
+
+ public static unsafe int ToBase64CharArray(byte[] inArray, int offsetIn, int length, char[] outArray, int offsetOut, Base64FormattingOptions options)
+ {
+ //Do data verfication
+ if (inArray == null)
+ throw new ArgumentNullException(nameof(inArray));
+ if (outArray == null)
+ throw new ArgumentNullException(nameof(outArray));
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Index);
+ if (offsetIn < 0)
+ throw new ArgumentOutOfRangeException(nameof(offsetIn), SR.ArgumentOutOfRange_GenericPositive);
+ if (offsetOut < 0)
+ throw new ArgumentOutOfRangeException(nameof(offsetOut), SR.ArgumentOutOfRange_GenericPositive);
+
+ if (options < Base64FormattingOptions.None || options > Base64FormattingOptions.InsertLineBreaks)
+ {
+ throw new ArgumentException(string.Format(SR.Arg_EnumIllegalVal, (int)options), nameof(options));
+ }
+
+
+ int retVal;
+
+ int inArrayLength;
+ int outArrayLength;
+ int numElementsToCopy;
+
+ inArrayLength = inArray.Length;
+
+ if (offsetIn > (int)(inArrayLength - length))
+ throw new ArgumentOutOfRangeException(nameof(offsetIn), SR.ArgumentOutOfRange_OffsetLength);
+
+ if (inArrayLength == 0)
+ return 0;
+
+ bool insertLineBreaks = (options == Base64FormattingOptions.InsertLineBreaks);
+ //This is the maximally required length that must be available in the char array
+ outArrayLength = outArray.Length;
+
+ // Length of the char buffer required
+ numElementsToCopy = ToBase64_CalculateAndValidateOutputLength(length, insertLineBreaks);
+
+ if (offsetOut > (int)(outArrayLength - numElementsToCopy))
+ throw new ArgumentOutOfRangeException(nameof(offsetOut), SR.ArgumentOutOfRange_OffsetOut);
+
+ fixed (char* outChars = &outArray[offsetOut])
+ {
+ fixed (byte* inData = &inArray[0])
+ {
+ retVal = ConvertToBase64Array(outChars, inData, offsetIn, length, insertLineBreaks);
+ }
+ }
+
+ return retVal;
+ }
+
+ public static unsafe bool TryToBase64Chars(ReadOnlySpan<byte> bytes, Span<char> chars, out int charsWritten, Base64FormattingOptions options = Base64FormattingOptions.None)
+ {
+ if (options < Base64FormattingOptions.None || options > Base64FormattingOptions.InsertLineBreaks)
+ {
+ throw new ArgumentException(string.Format(SR.Arg_EnumIllegalVal, (int)options), nameof(options));
+ }
+
+ if (bytes.Length == 0)
+ {
+ charsWritten = 0;
+ return true;
+ }
+
+ bool insertLineBreaks = (options == Base64FormattingOptions.InsertLineBreaks);
+
+ int charLengthRequired = ToBase64_CalculateAndValidateOutputLength(bytes.Length, insertLineBreaks);
+ if (charLengthRequired > chars.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ fixed (char* outChars = &MemoryMarshal.GetReference(chars))
+ fixed (byte* inData = &MemoryMarshal.GetReference(bytes))
+ {
+ charsWritten = ConvertToBase64Array(outChars, inData, 0, bytes.Length, insertLineBreaks);
+ return true;
+ }
+ }
+
+ private static unsafe int ConvertToBase64Array(char* outChars, byte* inData, int offset, int length, bool insertLineBreaks)
+ {
+ int lengthmod3 = length % 3;
+ int calcLength = offset + (length - lengthmod3);
+ int j = 0;
+ int charcount = 0;
+ //Convert three bytes at a time to base64 notation. This will consume 4 chars.
+ int i;
+
+ // get a pointer to the base64Table to avoid unnecessary range checking
+ fixed (char* base64 = &base64Table[0])
+ {
+ for (i = offset; i < calcLength; i += 3)
+ {
+ if (insertLineBreaks)
+ {
+ if (charcount == base64LineBreakPosition)
+ {
+ outChars[j++] = '\r';
+ outChars[j++] = '\n';
+ charcount = 0;
+ }
+ charcount += 4;
+ }
+ outChars[j] = base64[(inData[i] & 0xfc) >> 2];
+ outChars[j + 1] = base64[((inData[i] & 0x03) << 4) | ((inData[i + 1] & 0xf0) >> 4)];
+ outChars[j + 2] = base64[((inData[i + 1] & 0x0f) << 2) | ((inData[i + 2] & 0xc0) >> 6)];
+ outChars[j + 3] = base64[(inData[i + 2] & 0x3f)];
+ j += 4;
+ }
+
+ //Where we left off before
+ i = calcLength;
+
+ if (insertLineBreaks && (lengthmod3 != 0) && (charcount == base64LineBreakPosition))
+ {
+ outChars[j++] = '\r';
+ outChars[j++] = '\n';
+ }
+
+ switch (lengthmod3)
+ {
+ case 2: //One character padding needed
+ outChars[j] = base64[(inData[i] & 0xfc) >> 2];
+ outChars[j + 1] = base64[((inData[i] & 0x03) << 4) | ((inData[i + 1] & 0xf0) >> 4)];
+ outChars[j + 2] = base64[(inData[i + 1] & 0x0f) << 2];
+ outChars[j + 3] = base64[64]; //Pad
+ j += 4;
+ break;
+ case 1: // Two character padding needed
+ outChars[j] = base64[(inData[i] & 0xfc) >> 2];
+ outChars[j + 1] = base64[(inData[i] & 0x03) << 4];
+ outChars[j + 2] = base64[64]; //Pad
+ outChars[j + 3] = base64[64]; //Pad
+ j += 4;
+ break;
+ }
+ }
+
+ return j;
+ }
+
+ private static int ToBase64_CalculateAndValidateOutputLength(int inputLength, bool insertLineBreaks)
+ {
+ long outlen = ((long)inputLength) / 3 * 4; // the base length - we want integer division here.
+ outlen += ((inputLength % 3) != 0) ? 4 : 0; // at most 4 more chars for the remainder
+
+ if (outlen == 0)
+ return 0;
+
+ if (insertLineBreaks)
+ {
+ long newLines = outlen / base64LineBreakPosition;
+ if ((outlen % base64LineBreakPosition) == 0)
+ {
+ --newLines;
+ }
+ outlen += newLines * 2; // the number of line break chars we'll add, "\r\n"
+ }
+
+ // If we overflow an int then we cannot allocate enough
+ // memory to output the value so throw
+ if (outlen > int.MaxValue)
+ throw new OutOfMemoryException();
+
+ return (int)outlen;
+ }
+
+
+ /// <summary>
+ /// Converts the specified string, which encodes binary data as Base64 digits, to the equivalent byte array.
+ /// </summary>
+ /// <param name="s">The string to convert</param>
+ /// <returns>The array of bytes represented by the specified Base64 string.</returns>
+ public static Byte[] FromBase64String(String s)
+ {
+ // "s" is an unfortunate parameter name, but we need to keep it for backward compat.
+
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+
+
+ unsafe
+ {
+ fixed (Char* sPtr = s)
+ {
+ return FromBase64CharPtr(sPtr, s.Length);
+ }
+ }
+ }
+
+ public static bool TryFromBase64String(string s, Span<byte> bytes, out int bytesWritten)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+
+ return TryFromBase64Chars(s.AsSpan(), bytes, out bytesWritten);
+ }
+
+ public static bool TryFromBase64Chars(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
+ {
+ // This is actually local to one of the nested blocks but is being declared at the top as we don't want multiple stackallocs
+ // for each iteraton of the loop.
+ Span<char> tempBuffer = stackalloc char[4]; // Note: The tempBuffer size could be made larger than 4 but the size must be a multiple of 4.
+
+ bytesWritten = 0;
+
+ while (chars.Length != 0)
+ {
+ // Attempt to decode a segment that doesn't contain whitespace.
+ bool complete = TryDecodeFromUtf16(chars, bytes, out int consumedInThisIteration, out int bytesWrittenInThisIteration);
+ bytesWritten += bytesWrittenInThisIteration;
+ if (complete)
+ return true;
+
+ chars = chars.Slice(consumedInThisIteration);
+ bytes = bytes.Slice(bytesWrittenInThisIteration);
+
+ Debug.Assert(chars.Length != 0); // If TryDecodeFromUtf16() consumed the entire buffer, it could not have returned false.
+ if (chars[0].IsSpace())
+ {
+ // If we got here, the very first character not consumed was a whitespace. We can skip past any consecutive whitespace, then continue decoding.
+
+ int indexOfFirstNonSpace = 1;
+ for (; ; )
+ {
+ if (indexOfFirstNonSpace == chars.Length)
+ break;
+ if (!chars[indexOfFirstNonSpace].IsSpace())
+ break;
+ indexOfFirstNonSpace++;
+ }
+
+ chars = chars.Slice(indexOfFirstNonSpace);
+
+ if ((bytesWrittenInThisIteration % 3) != 0 && chars.Length != 0)
+ {
+ // If we got here, the last successfully decoded block encountered an end-marker, yet we have trailing non-whitespace characters.
+ // That is not allowed.
+ bytesWritten = default;
+ return false;
+ }
+
+ // We now loop again to decode the next run of non-space characters.
+ }
+ else
+ {
+ Debug.Assert(chars.Length != 0 && !chars[0].IsSpace());
+
+ // If we got here, it is possible that there is whitespace that occurred in the middle of a 4-byte chunk. That is, we still have
+ // up to three Base64 characters that were left undecoded by the fast-path helper because they didn't form a complete 4-byte chunk.
+ // This is hopefully the rare case (multiline-formatted base64 message with a non-space character width that's not a multiple of 4.)
+ // We'll filter out whitespace and copy the remaining characters into a temporary buffer.
+ CopyToTempBufferWithoutWhiteSpace(chars, tempBuffer, out int consumedFromChars, out int charsWritten);
+ if ((charsWritten & 0x3) != 0)
+ {
+ // Even after stripping out whitespace, the number of characters is not divisible by 4. This cannot be a legal Base64 string.
+ bytesWritten = default;
+ return false;
+ }
+
+ tempBuffer = tempBuffer.Slice(0, charsWritten);
+ if (!TryDecodeFromUtf16(tempBuffer, bytes, out int consumedFromTempBuffer, out int bytesWrittenFromTempBuffer))
+ {
+ bytesWritten = default;
+ return false;
+ }
+ bytesWritten += bytesWrittenFromTempBuffer;
+ chars = chars.Slice(consumedFromChars);
+ bytes = bytes.Slice(bytesWrittenFromTempBuffer);
+
+ if ((bytesWrittenFromTempBuffer % 3) != 0)
+ {
+ // If we got here, this decode contained one or more padding characters ('='). We can accept trailing whitespace after this
+ // but nothing else.
+ for (int i = 0; i < chars.Length; i++)
+ {
+ if (!chars[i].IsSpace())
+ {
+ bytesWritten = default;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // We now loop again to decode the next run of non-space characters.
+ }
+ }
+
+ return true;
+ }
+
+ private static void CopyToTempBufferWithoutWhiteSpace(ReadOnlySpan<char> chars, Span<char> tempBuffer, out int consumed, out int charsWritten)
+ {
+ Debug.Assert(tempBuffer.Length != 0); // We only bound-check after writing a character to the tempBuffer.
+
+ charsWritten = 0;
+ for (int i = 0; i < chars.Length; i++)
+ {
+ char c = chars[i];
+ if (!c.IsSpace())
+ {
+ tempBuffer[charsWritten++] = c;
+ if (charsWritten == tempBuffer.Length)
+ {
+ consumed = i + 1;
+ return;
+ }
+ }
+ }
+ consumed = chars.Length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsSpace(this char c) => c == ' ' || c == '\t' || c == '\r' || c == '\n';
+
+ /// <summary>
+ /// Converts the specified range of a Char array, which encodes binary data as Base64 digits, to the equivalent byte array.
+ /// </summary>
+ /// <param name="inArray">Chars representing Base64 encoding characters</param>
+ /// <param name="offset">A position within the input array.</param>
+ /// <param name="length">Number of element to convert.</param>
+ /// <returns>The array of bytes represented by the specified Base64 encoding characters.</returns>
+ public static Byte[] FromBase64CharArray(Char[] inArray, Int32 offset, Int32 length)
+ {
+ if (inArray == null)
+ throw new ArgumentNullException(nameof(inArray));
+
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Index);
+
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_GenericPositive);
+
+ if (offset > inArray.Length - length)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_OffsetLength);
+
+
+ if (inArray.Length == 0)
+ {
+ return Array.Empty<byte>();
+ }
+
+ unsafe
+ {
+ fixed (Char* inArrayPtr = &inArray[0])
+ {
+ return FromBase64CharPtr(inArrayPtr + offset, length);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Convert Base64 encoding characters to bytes:
+ /// - Compute result length exactly by actually walking the input;
+ /// - Allocate new result array based on computation;
+ /// - Decode input into the new array;
+ /// </summary>
+ /// <param name="inputPtr">Pointer to the first input char</param>
+ /// <param name="inputLength">Number of input chars</param>
+ /// <returns></returns>
+ private static unsafe Byte[] FromBase64CharPtr(Char* inputPtr, Int32 inputLength)
+ {
+ // The validity of parameters much be checked by callers, thus we are Critical here.
+
+ Debug.Assert(0 <= inputLength);
+
+ // We need to get rid of any trailing white spaces.
+ // Otherwise we would be rejecting input such as "abc= ":
+ while (inputLength > 0)
+ {
+ Int32 lastChar = inputPtr[inputLength - 1];
+ if (lastChar != (Int32)' ' && lastChar != (Int32)'\n' && lastChar != (Int32)'\r' && lastChar != (Int32)'\t')
+ break;
+ inputLength--;
+ }
+
+ // Compute the output length:
+ Int32 resultLength = FromBase64_ComputeResultLength(inputPtr, inputLength);
+
+ Debug.Assert(0 <= resultLength);
+
+ // resultLength can be zero. We will still enter FromBase64_Decode and process the input.
+ // It may either simply write no bytes (e.g. input = " ") or throw (e.g. input = "ab").
+
+ // Create result byte blob:
+ Byte[] decodedBytes = new Byte[resultLength];
+
+ // Convert Base64 chars into bytes:
+ if (!TryFromBase64Chars(new ReadOnlySpan<char>(inputPtr, inputLength), decodedBytes, out int _))
+ throw new FormatException(SR.Format_BadBase64Char);
+
+ // Note that the number of bytes written can differ from resultLength if the caller is modifying the array
+ // as it is being converted. Silently ignore the failure.
+ // Consider throwing exception in an non in-place release.
+
+ // We are done:
+ return decodedBytes;
+ }
+
+ /// <summary>
+ /// Compute the number of bytes encoded in the specified Base 64 char array:
+ /// Walk the entire input counting white spaces and padding chars, then compute result length
+ /// based on 3 bytes per 4 chars.
+ /// </summary>
+ private static unsafe Int32 FromBase64_ComputeResultLength(Char* inputPtr, Int32 inputLength)
+ {
+ const UInt32 intEq = (UInt32)'=';
+ const UInt32 intSpace = (UInt32)' ';
+
+ Debug.Assert(0 <= inputLength);
+
+ Char* inputEndPtr = inputPtr + inputLength;
+ Int32 usefulInputLength = inputLength;
+ Int32 padding = 0;
+
+ while (inputPtr < inputEndPtr)
+ {
+ UInt32 c = (UInt32)(*inputPtr);
+ inputPtr++;
+
+ // We want to be as fast as possible and filter out spaces with as few comparisons as possible.
+ // We end up accepting a number of illegal chars as legal white-space chars.
+ // This is ok: as soon as we hit them during actual decode we will recognise them as illegal and throw.
+ if (c <= intSpace)
+ usefulInputLength--;
+
+ else if (c == intEq)
+ {
+ usefulInputLength--;
+ padding++;
+ }
+ }
+
+ Debug.Assert(0 <= usefulInputLength);
+
+ // For legal input, we can assume that 0 <= padding < 3. But it may be more for illegal input.
+ // We will notice it at decode when we see a '=' at the wrong place.
+ Debug.Assert(0 <= padding);
+
+ // Perf: reuse the variable that stored the number of '=' to store the number of bytes encoded by the
+ // last group that contains the '=':
+ if (padding != 0)
+ {
+ if (padding == 1)
+ padding = 2;
+ else if (padding == 2)
+ padding = 1;
+ else
+ throw new FormatException(SR.Format_BadBase64Char);
+ }
+
+ // Done:
+ return (usefulInputLength / 4) * 3 + padding;
+ }
+ } // class Convert
+} // namespace
+
diff --git a/src/System.Private.CoreLib/shared/System/CurrentSystemTimeZone.cs b/src/System.Private.CoreLib/shared/System/CurrentSystemTimeZone.cs
new file mode 100644
index 0000000000..bab6a92bf5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/CurrentSystemTimeZone.cs
@@ -0,0 +1,197 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose:
+** This class represents the current system timezone. It is
+** the only meaningful implementation of the TimeZone class
+** available in this version.
+**
+** The only TimeZone that we support in version 1 is the
+** CurrentTimeZone as determined by the system timezone.
+**
+**
+============================================================*/
+
+using System;
+using System.Text;
+using System.Collections;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Obsolete("System.CurrentSystemTimeZone has been deprecated. Please investigate the use of System.TimeZoneInfo.Local instead.")]
+ internal partial class CurrentSystemTimeZone : TimeZone
+ {
+ // Standard offset in ticks to the Universal time if
+ // no daylight saving is in used.
+ // E.g. the offset for PST (Pacific Standard time) should be -8 * 60 * 60 * 1000 * 10000.
+ // (1 millisecond = 10000 ticks)
+ private long m_ticksOffset;
+ private String m_standardName;
+ private String m_daylightName;
+
+ internal CurrentSystemTimeZone()
+ {
+ TimeZoneInfo local = TimeZoneInfo.Local;
+
+ m_ticksOffset = local.BaseUtcOffset.Ticks;
+ m_standardName = local.StandardName;
+ m_daylightName = local.DaylightName;
+ }
+
+ public override String StandardName
+ {
+ get
+ {
+ return m_standardName;
+ }
+ }
+
+ public override String DaylightName
+ {
+ get
+ {
+ return m_daylightName;
+ }
+ }
+
+ internal long GetUtcOffsetFromUniversalTime(DateTime time, ref Boolean isAmbiguousLocalDst)
+ {
+ // Get the daylight changes for the year of the specified time.
+ TimeSpan offset = new TimeSpan(m_ticksOffset);
+ DaylightTime daylightTime = GetDaylightChanges(time.Year);
+ isAmbiguousLocalDst = false;
+
+ if (daylightTime == null || daylightTime.Delta.Ticks == 0)
+ {
+ return offset.Ticks;
+ }
+
+ // The start and end times represent the range of universal times that are in DST for that year.
+ // Within that there is an ambiguous hour, usually right at the end, but at the beginning in
+ // the unusual case of a negative daylight savings delta.
+ DateTime startTime = daylightTime.Start - offset;
+ DateTime endTime = daylightTime.End - offset - daylightTime.Delta;
+ DateTime ambiguousStart;
+ DateTime ambiguousEnd;
+
+ if (daylightTime.Delta.Ticks > 0)
+ {
+ ambiguousStart = endTime - daylightTime.Delta;
+ ambiguousEnd = endTime;
+ }
+ else
+ {
+ ambiguousStart = startTime;
+ ambiguousEnd = startTime - daylightTime.Delta;
+ }
+
+ Boolean isDst = false;
+ if (startTime > endTime)
+ {
+ // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year.
+ // Note, the summer in the southern hemisphere begins late in the year.
+ isDst = (time < endTime || time >= startTime);
+ }
+ else
+ {
+ // In northern hemisphere, the daylight saving time starts in the middle of the year.
+ isDst = (time >= startTime && time < endTime);
+ }
+
+ if (isDst)
+ {
+ offset += daylightTime.Delta;
+
+ // See if the resulting local time becomes ambiguous. This must be captured here or the
+ // DateTime will not be able to round-trip back to UTC accurately.
+ if (time >= ambiguousStart && time < ambiguousEnd)
+ {
+ isAmbiguousLocalDst = true;
+ }
+ }
+ return offset.Ticks;
+ }
+
+ public override DateTime ToLocalTime(DateTime time)
+ {
+ if (time.Kind == DateTimeKind.Local)
+ {
+ return time;
+ }
+ Boolean isAmbiguousLocalDst = false;
+ Int64 offset = GetUtcOffsetFromUniversalTime(time, ref isAmbiguousLocalDst);
+ long tick = time.Ticks + offset;
+ if (tick > DateTime.MaxTicks)
+ {
+ return new DateTime(DateTime.MaxTicks, DateTimeKind.Local);
+ }
+ if (tick < DateTime.MinTicks)
+ {
+ return new DateTime(DateTime.MinTicks, DateTimeKind.Local);
+ }
+ return new DateTime(tick, DateTimeKind.Local, isAmbiguousLocalDst);
+ }
+
+ public override DaylightTime GetDaylightChanges(int year)
+ {
+ if (year < 1 || year > 9999)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year), SR.Format(SR.ArgumentOutOfRange_Range, 1, 9999));
+ }
+
+ return GetCachedDaylightChanges(year);
+ }
+
+ private static DaylightTime CreateDaylightChanges(int year)
+ {
+ DaylightTime currentDaylightChanges = null;
+
+ if (TimeZoneInfo.Local.SupportsDaylightSavingTime)
+ {
+ DateTime start;
+ DateTime end;
+ TimeSpan delta;
+
+ foreach (var rule in TimeZoneInfo.Local.GetAdjustmentRules())
+ {
+ if (rule.DateStart.Year <= year && rule.DateEnd.Year >= year && rule.DaylightDelta != TimeSpan.Zero)
+ {
+ start = TimeZoneInfo.TransitionTimeToDateTime(year, rule.DaylightTransitionStart);
+ end = TimeZoneInfo.TransitionTimeToDateTime(year, rule.DaylightTransitionEnd);
+ delta = rule.DaylightDelta;
+
+ currentDaylightChanges = new DaylightTime(start, end, delta);
+ break;
+ }
+ }
+ }
+
+ if (currentDaylightChanges == null)
+ {
+ currentDaylightChanges = new DaylightTime(DateTime.MinValue, DateTime.MinValue, TimeSpan.Zero);
+ }
+
+ return currentDaylightChanges;
+ }
+
+ public override TimeSpan GetUtcOffset(DateTime time)
+ {
+ if (time.Kind == DateTimeKind.Utc)
+ {
+ return TimeSpan.Zero;
+ }
+ else
+ {
+ return new TimeSpan(TimeZone.CalculateUtcOffset(time, GetDaylightChanges(time.Year)).Ticks + m_ticksOffset);
+ }
+ }
+ } // class CurrentSystemTimeZone
+}
diff --git a/src/System.Private.CoreLib/shared/System/DBNull.cs b/src/System.Private.CoreLib/shared/System/DBNull.cs
new file mode 100644
index 0000000000..3cee2b15c8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DBNull.cs
@@ -0,0 +1,119 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ public sealed class DBNull : ISerializable, IConvertible
+ {
+ private DBNull()
+ {
+ }
+
+ private DBNull(SerializationInfo info, StreamingContext context)
+ {
+ throw new NotSupportedException(SR.NotSupported_DBNullSerial);
+ }
+
+ public static readonly DBNull Value = new DBNull();
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ UnitySerializationHolder.GetUnitySerializationInfo(info, UnitySerializationHolder.NullUnity);
+ }
+
+ public override string ToString()
+ {
+ return string.Empty;
+ }
+
+ public string ToString(IFormatProvider provider)
+ {
+ return string.Empty;
+ }
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.DBNull;
+ }
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.InvalidCast_FromDBNull);
+ }
+
+ object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/DataMisalignedException.cs b/src/System.Private.CoreLib/shared/System/DataMisalignedException.cs
new file mode 100644
index 0000000000..2a245b6ef7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DataMisalignedException.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+** Purpose: The exception class for a misaligned access exception
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class DataMisalignedException : SystemException
+ {
+ public DataMisalignedException()
+ : base(SR.Arg_DataMisalignedException)
+ {
+ HResult = HResults.COR_E_DATAMISALIGNED;
+ }
+
+ public DataMisalignedException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_DATAMISALIGNED;
+ }
+
+ public DataMisalignedException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_DATAMISALIGNED;
+ }
+
+ internal DataMisalignedException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/DateTime.cs b/src/System.Private.CoreLib/shared/System/DateTime.cs
new file mode 100644
index 0000000000..9c3b3989e4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DateTime.cs
@@ -0,0 +1,1586 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Globalization;
+using System.Runtime;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Runtime.Versioning;
+using System.Security;
+using CultureInfo = System.Globalization.CultureInfo;
+using Calendar = System.Globalization.Calendar;
+
+namespace System
+{
+ // This value type represents a date and time. Every DateTime
+ // object has a private field (Ticks) of type Int64 that stores the
+ // date and time as the number of 100 nanosecond intervals since
+ // 12:00 AM January 1, year 1 A.D. in the proleptic Gregorian Calendar.
+ //
+ // Starting from V2.0, DateTime also stored some context about its time
+ // zone in the form of a 3-state value representing Unspecified, Utc or
+ // Local. This is stored in the two top bits of the 64-bit numeric value
+ // with the remainder of the bits storing the tick count. This information
+ // is only used during time zone conversions and is not part of the
+ // identity of the DateTime. Thus, operations like Compare and Equals
+ // ignore this state. This is to stay compatible with earlier behavior
+ // and performance characteristics and to avoid forcing people into dealing
+ // with the effects of daylight savings. Note, that this has little effect
+ // on how the DateTime works except in a context where its specific time
+ // zone is needed, such as during conversions and some parsing and formatting
+ // cases.
+ //
+ // There is also 4th state stored that is a special type of Local value that
+ // is used to avoid data loss when round-tripping between local and UTC time.
+ // See below for more information on this 4th state, although it is
+ // effectively hidden from most users, who just see the 3-state DateTimeKind
+ // enumeration.
+ //
+ // For compatibility, DateTime does not serialize the Kind data when used in
+ // binary serialization.
+ //
+ // For a description of various calendar issues, look at
+ //
+ // Calendar Studies web site, at
+ // http://serendipity.nofadz.com/hermetic/cal_stud.htm.
+ //
+ //
+ [StructLayout(LayoutKind.Auto)]
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public readonly partial struct DateTime : IComparable, IFormattable, IConvertible, IComparable<DateTime>, IEquatable<DateTime>, ISerializable, ISpanFormattable
+ {
+ // Number of 100ns ticks per time unit
+ private const long TicksPerMillisecond = 10000;
+ private const long TicksPerSecond = TicksPerMillisecond * 1000;
+ private const long TicksPerMinute = TicksPerSecond * 60;
+ private const long TicksPerHour = TicksPerMinute * 60;
+ private const long TicksPerDay = TicksPerHour * 24;
+
+ // Number of milliseconds per time unit
+ private const int MillisPerSecond = 1000;
+ private const int MillisPerMinute = MillisPerSecond * 60;
+ private const int MillisPerHour = MillisPerMinute * 60;
+ private const int MillisPerDay = MillisPerHour * 24;
+
+ // Number of days in a non-leap year
+ private const int DaysPerYear = 365;
+ // Number of days in 4 years
+ private const int DaysPer4Years = DaysPerYear * 4 + 1; // 1461
+ // Number of days in 100 years
+ private const int DaysPer100Years = DaysPer4Years * 25 - 1; // 36524
+ // Number of days in 400 years
+ private const int DaysPer400Years = DaysPer100Years * 4 + 1; // 146097
+
+ // Number of days from 1/1/0001 to 12/31/1600
+ private const int DaysTo1601 = DaysPer400Years * 4; // 584388
+ // Number of days from 1/1/0001 to 12/30/1899
+ private const int DaysTo1899 = DaysPer400Years * 4 + DaysPer100Years * 3 - 367;
+ // Number of days from 1/1/0001 to 12/31/1969
+ internal const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; // 719,162
+ // Number of days from 1/1/0001 to 12/31/9999
+ private const int DaysTo10000 = DaysPer400Years * 25 - 366; // 3652059
+
+ internal const long MinTicks = 0;
+ internal const long MaxTicks = DaysTo10000 * TicksPerDay - 1;
+ private const long MaxMillis = (long)DaysTo10000 * MillisPerDay;
+
+ internal const long UnixEpochTicks = DaysTo1970 * TicksPerDay;
+ private const long FileTimeOffset = DaysTo1601 * TicksPerDay;
+ private const long DoubleDateOffset = DaysTo1899 * TicksPerDay;
+ // The minimum OA date is 0100/01/01 (Note it's year 100).
+ // The maximum OA date is 9999/12/31
+ private const long OADateMinAsTicks = (DaysPer100Years - DaysPerYear) * TicksPerDay;
+ // All OA dates must be greater than (not >=) OADateMinAsDouble
+ private const double OADateMinAsDouble = -657435.0;
+ // All OA dates must be less than (not <=) OADateMaxAsDouble
+ private const double OADateMaxAsDouble = 2958466.0;
+
+ private const int DatePartYear = 0;
+ private const int DatePartDayOfYear = 1;
+ private const int DatePartMonth = 2;
+ private const int DatePartDay = 3;
+
+ private static readonly int[] s_daysToMonth365 = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
+ private static readonly int[] s_daysToMonth366 = {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
+
+ public static readonly DateTime MinValue = new DateTime(MinTicks, DateTimeKind.Unspecified);
+ public static readonly DateTime MaxValue = new DateTime(MaxTicks, DateTimeKind.Unspecified);
+ public static readonly DateTime UnixEpoch = new DateTime(UnixEpochTicks, DateTimeKind.Utc);
+
+ private const UInt64 TicksMask = 0x3FFFFFFFFFFFFFFF;
+ private const UInt64 FlagsMask = 0xC000000000000000;
+ private const UInt64 LocalMask = 0x8000000000000000;
+ private const Int64 TicksCeiling = 0x4000000000000000;
+ private const UInt64 KindUnspecified = 0x0000000000000000;
+ private const UInt64 KindUtc = 0x4000000000000000;
+ private const UInt64 KindLocal = 0x8000000000000000;
+ private const UInt64 KindLocalAmbiguousDst = 0xC000000000000000;
+ private const Int32 KindShift = 62;
+
+ private const String TicksField = "ticks"; // Do not rename (binary serialization)
+ private const String DateDataField = "dateData"; // Do not rename (binary serialization)
+
+ // The data is stored as an unsigned 64-bit integer
+ // Bits 01-62: The value of 100-nanosecond ticks where 0 represents 1/1/0001 12:00am, up until the value
+ // 12/31/9999 23:59:59.9999999
+ // Bits 63-64: A four-state value that describes the DateTimeKind value of the date time, with a 2nd
+ // value for the rare case where the date time is local, but is in an overlapped daylight
+ // savings time hour and it is in daylight savings time. This allows distinction of these
+ // otherwise ambiguous local times and prevents data loss when round tripping from Local to
+ // UTC time.
+ private readonly UInt64 _dateData;
+
+ // Constructs a DateTime from a tick count. The ticks
+ // argument specifies the date as the number of 100-nanosecond intervals
+ // that have elapsed since 1/1/0001 12:00am.
+ //
+ public DateTime(long ticks)
+ {
+ if (ticks < MinTicks || ticks > MaxTicks)
+ throw new ArgumentOutOfRangeException(nameof(ticks), SR.ArgumentOutOfRange_DateTimeBadTicks);
+ _dateData = (UInt64)ticks;
+ }
+
+ private DateTime(UInt64 dateData)
+ {
+ this._dateData = dateData;
+ }
+
+ public DateTime(long ticks, DateTimeKind kind)
+ {
+ if (ticks < MinTicks || ticks > MaxTicks)
+ {
+ throw new ArgumentOutOfRangeException(nameof(ticks), SR.ArgumentOutOfRange_DateTimeBadTicks);
+ }
+ if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local)
+ {
+ throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind));
+ }
+ _dateData = ((UInt64)ticks | ((UInt64)kind << KindShift));
+ }
+
+ internal DateTime(long ticks, DateTimeKind kind, Boolean isAmbiguousDst)
+ {
+ if (ticks < MinTicks || ticks > MaxTicks)
+ {
+ throw new ArgumentOutOfRangeException(nameof(ticks), SR.ArgumentOutOfRange_DateTimeBadTicks);
+ }
+ Debug.Assert(kind == DateTimeKind.Local, "Internal Constructor is for local times only");
+ _dateData = ((UInt64)ticks | (isAmbiguousDst ? KindLocalAmbiguousDst : KindLocal));
+ }
+
+ // Constructs a DateTime from a given year, month, and day. The
+ // time-of-day of the resulting DateTime is always midnight.
+ //
+ public DateTime(int year, int month, int day)
+ {
+ _dateData = (UInt64)DateToTicks(year, month, day);
+ }
+
+ // Constructs a DateTime from a given year, month, and day for
+ // the specified calendar. The
+ // time-of-day of the resulting DateTime is always midnight.
+ //
+ public DateTime(int year, int month, int day, Calendar calendar)
+ : this(year, month, day, 0, 0, 0, calendar)
+ {
+ }
+
+ // Constructs a DateTime from a given year, month, day, hour,
+ // minute, and second.
+ //
+ public DateTime(int year, int month, int day, int hour, int minute, int second)
+ {
+ _dateData = (UInt64)(DateToTicks(year, month, day) + TimeToTicks(hour, minute, second));
+ }
+
+ public DateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind)
+ {
+ if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local)
+ {
+ throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind));
+ }
+ Int64 ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second);
+ _dateData = ((UInt64)ticks | ((UInt64)kind << KindShift));
+ }
+
+ // Constructs a DateTime from a given year, month, day, hour,
+ // minute, and second for the specified calendar.
+ //
+ public DateTime(int year, int month, int day, int hour, int minute, int second, Calendar calendar)
+ {
+ if (calendar == null)
+ throw new ArgumentNullException(nameof(calendar));
+ _dateData = (UInt64)calendar.ToDateTime(year, month, day, hour, minute, second, 0).Ticks;
+ }
+
+ // Constructs a DateTime from a given year, month, day, hour,
+ // minute, and second.
+ //
+ public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond)
+ {
+ if (millisecond < 0 || millisecond >= MillisPerSecond)
+ {
+ throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1));
+ }
+ Int64 ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second);
+ ticks += millisecond * TicksPerMillisecond;
+ if (ticks < MinTicks || ticks > MaxTicks)
+ throw new ArgumentException(SR.Arg_DateTimeRange);
+ _dateData = (UInt64)ticks;
+ }
+
+ public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind)
+ {
+ if (millisecond < 0 || millisecond >= MillisPerSecond)
+ {
+ throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1));
+ }
+ if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local)
+ {
+ throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind));
+ }
+ Int64 ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second);
+ ticks += millisecond * TicksPerMillisecond;
+ if (ticks < MinTicks || ticks > MaxTicks)
+ throw new ArgumentException(SR.Arg_DateTimeRange);
+ _dateData = ((UInt64)ticks | ((UInt64)kind << KindShift));
+ }
+
+ // Constructs a DateTime from a given year, month, day, hour,
+ // minute, and second for the specified calendar.
+ //
+ public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar)
+ {
+ if (calendar == null)
+ throw new ArgumentNullException(nameof(calendar));
+ if (millisecond < 0 || millisecond >= MillisPerSecond)
+ {
+ throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1));
+ }
+ Int64 ticks = calendar.ToDateTime(year, month, day, hour, minute, second, 0).Ticks;
+ ticks += millisecond * TicksPerMillisecond;
+ if (ticks < MinTicks || ticks > MaxTicks)
+ throw new ArgumentException(SR.Arg_DateTimeRange);
+ _dateData = (UInt64)ticks;
+ }
+
+ public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, DateTimeKind kind)
+ {
+ if (calendar == null)
+ throw new ArgumentNullException(nameof(calendar));
+ if (millisecond < 0 || millisecond >= MillisPerSecond)
+ {
+ throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1));
+ }
+ if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local)
+ {
+ throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind));
+ }
+ Int64 ticks = calendar.ToDateTime(year, month, day, hour, minute, second, 0).Ticks;
+ ticks += millisecond * TicksPerMillisecond;
+ if (ticks < MinTicks || ticks > MaxTicks)
+ throw new ArgumentException(SR.Arg_DateTimeRange);
+ _dateData = ((UInt64)ticks | ((UInt64)kind << KindShift));
+ }
+
+ private DateTime(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ throw new ArgumentNullException(nameof(info));
+
+ Boolean foundTicks = false;
+ Boolean foundDateData = false;
+ Int64 serializedTicks = 0;
+ UInt64 serializedDateData = 0;
+
+
+ // Get the data
+ SerializationInfoEnumerator enumerator = info.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ switch (enumerator.Name)
+ {
+ case TicksField:
+ serializedTicks = Convert.ToInt64(enumerator.Value, CultureInfo.InvariantCulture);
+ foundTicks = true;
+ break;
+ case DateDataField:
+ serializedDateData = Convert.ToUInt64(enumerator.Value, CultureInfo.InvariantCulture);
+ foundDateData = true;
+ break;
+ default:
+ // Ignore other fields for forward compatibility.
+ break;
+ }
+ }
+ if (foundDateData)
+ {
+ _dateData = serializedDateData;
+ }
+ else if (foundTicks)
+ {
+ _dateData = (UInt64)serializedTicks;
+ }
+ else
+ {
+ throw new SerializationException(SR.Serialization_MissingDateTimeData);
+ }
+ Int64 ticks = InternalTicks;
+ if (ticks < MinTicks || ticks > MaxTicks)
+ {
+ throw new SerializationException(SR.Serialization_DateTimeTicksOutOfRange);
+ }
+ }
+
+
+
+ internal Int64 InternalTicks
+ {
+ get
+ {
+ return (Int64)(_dateData & TicksMask);
+ }
+ }
+
+ private UInt64 InternalKind
+ {
+ get
+ {
+ return (_dateData & FlagsMask);
+ }
+ }
+
+ // Returns the DateTime resulting from adding the given
+ // TimeSpan to this DateTime.
+ //
+ public DateTime Add(TimeSpan value)
+ {
+ return AddTicks(value._ticks);
+ }
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // time units to this DateTime.
+ private DateTime Add(double value, int scale)
+ {
+ long millis = (long)(value * scale + (value >= 0 ? 0.5 : -0.5));
+ if (millis <= -MaxMillis || millis >= MaxMillis)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_AddValue);
+ return AddTicks(millis * TicksPerMillisecond);
+ }
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // days to this DateTime. The result is computed by rounding the
+ // fractional number of days given by value to the nearest
+ // millisecond, and adding that interval to this DateTime. The
+ // value argument is permitted to be negative.
+ //
+ public DateTime AddDays(double value)
+ {
+ return Add(value, MillisPerDay);
+ }
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // hours to this DateTime. The result is computed by rounding the
+ // fractional number of hours given by value to the nearest
+ // millisecond, and adding that interval to this DateTime. The
+ // value argument is permitted to be negative.
+ //
+ public DateTime AddHours(double value)
+ {
+ return Add(value, MillisPerHour);
+ }
+
+ // Returns the DateTime resulting from the given number of
+ // milliseconds to this DateTime. The result is computed by rounding
+ // the number of milliseconds given by value to the nearest integer,
+ // and adding that interval to this DateTime. The value
+ // argument is permitted to be negative.
+ //
+ public DateTime AddMilliseconds(double value)
+ {
+ return Add(value, 1);
+ }
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // minutes to this DateTime. The result is computed by rounding the
+ // fractional number of minutes given by value to the nearest
+ // millisecond, and adding that interval to this DateTime. The
+ // value argument is permitted to be negative.
+ //
+ public DateTime AddMinutes(double value)
+ {
+ return Add(value, MillisPerMinute);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to this DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of this DateTime by
+ // months months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of this DateTime.
+ //
+ // In more precise terms, considering this DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding months months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+ public DateTime AddMonths(int months)
+ {
+ if (months < -120000 || months > 120000) throw new ArgumentOutOfRangeException(nameof(months), SR.ArgumentOutOfRange_DateTimeBadMonths);
+ GetDatePart(out int y, out int m, out int d);
+ int i = m - 1 + months;
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ if (y < 1 || y > 9999)
+ {
+ throw new ArgumentOutOfRangeException(nameof(months), SR.ArgumentOutOfRange_DateArithmetic);
+ }
+ int days = DaysInMonth(y, m);
+ if (d > days) d = days;
+ return new DateTime((UInt64)(DateToTicks(y, m, d) + InternalTicks % TicksPerDay) | InternalKind);
+ }
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // seconds to this DateTime. The result is computed by rounding the
+ // fractional number of seconds given by value to the nearest
+ // millisecond, and adding that interval to this DateTime. The
+ // value argument is permitted to be negative.
+ //
+ public DateTime AddSeconds(double value)
+ {
+ return Add(value, MillisPerSecond);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // 100-nanosecond ticks to this DateTime. The value argument
+ // is permitted to be negative.
+ //
+ public DateTime AddTicks(long value)
+ {
+ long ticks = InternalTicks;
+ if (value > MaxTicks - ticks || value < MinTicks - ticks)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_DateArithmetic);
+ }
+ return new DateTime((UInt64)(ticks + value) | InternalKind);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to this DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of this DateTime by value
+ // years. If the month and day of this DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of this DateTime.
+ //
+ public DateTime AddYears(int value)
+ {
+ if (value < -10000 || value > 10000)
+ {
+ // DateTimeOffset.AddYears(int years) is implemented on top of DateTime.AddYears(int value). Use the more appropriate
+ // parameter name out of the two for the exception.
+ throw new ArgumentOutOfRangeException("years", SR.ArgumentOutOfRange_DateTimeBadYears);
+ }
+ return AddMonths(value * 12);
+ }
+
+ // Compares two DateTime values, returning an integer that indicates
+ // their relationship.
+ //
+ public static int Compare(DateTime t1, DateTime t2)
+ {
+ Int64 ticks1 = t1.InternalTicks;
+ Int64 ticks2 = t2.InternalTicks;
+ if (ticks1 > ticks2) return 1;
+ if (ticks1 < ticks2) return -1;
+ return 0;
+ }
+
+ // Compares this DateTime to a given object. This method provides an
+ // implementation of the IComparable interface. The object
+ // argument must be another DateTime, or otherwise an exception
+ // occurs. Null is considered less than any instance.
+ //
+ // Returns a value less than zero if this object
+ public int CompareTo(Object value)
+ {
+ if (value == null) return 1;
+ if (!(value is DateTime))
+ {
+ throw new ArgumentException(SR.Arg_MustBeDateTime);
+ }
+
+ return Compare(this, (DateTime)value);
+ }
+
+ public int CompareTo(DateTime value)
+ {
+ return Compare(this, value);
+ }
+
+ // Returns the tick count corresponding to the given year, month, and day.
+ // Will check the if the parameters are valid.
+ private static long DateToTicks(int year, int month, int day)
+ {
+ if (year >= 1 && year <= 9999 && month >= 1 && month <= 12)
+ {
+ int[] days = IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365;
+ if (day >= 1 && day <= days[month] - days[month - 1])
+ {
+ int y = year - 1;
+ int n = y * 365 + y / 4 - y / 100 + y / 400 + days[month - 1] + day - 1;
+ return n * TicksPerDay;
+ }
+ }
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+
+ // Return the tick count corresponding to the given hour, minute, second.
+ // Will check the if the parameters are valid.
+ private static long TimeToTicks(int hour, int minute, int second)
+ {
+ //TimeSpan.TimeToTicks is a family access function which does no error checking, so
+ //we need to put some error checking out here.
+ if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60)
+ {
+ return (TimeSpan.TimeToTicks(hour, minute, second));
+ }
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+ public static int DaysInMonth(int year, int month)
+ {
+ if (month < 1 || month > 12) throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ // IsLeapYear checks the year argument
+ int[] days = IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365;
+ return days[month] - days[month - 1];
+ }
+
+ // Converts an OLE Date to a tick count.
+ // This function is duplicated in COMDateTime.cpp
+ internal static long DoubleDateToTicks(double value)
+ {
+ // The check done this way will take care of NaN
+ if (!(value < OADateMaxAsDouble) || !(value > OADateMinAsDouble))
+ throw new ArgumentException(SR.Arg_OleAutDateInvalid);
+
+ // Conversion to long will not cause an overflow here, as at this point the "value" is in between OADateMinAsDouble and OADateMaxAsDouble
+ long millis = (long)(value * MillisPerDay + (value >= 0 ? 0.5 : -0.5));
+ // The interesting thing here is when you have a value like 12.5 it all positive 12 days and 12 hours from 01/01/1899
+ // However if you a value of -12.25 it is minus 12 days but still positive 6 hours, almost as though you meant -11.75 all negative
+ // This line below fixes up the millis in the negative case
+ if (millis < 0)
+ {
+ millis -= (millis % MillisPerDay) * 2;
+ }
+
+ millis += DoubleDateOffset / TicksPerMillisecond;
+
+ if (millis < 0 || millis >= MaxMillis) throw new ArgumentException(SR.Arg_OleAutDateScale);
+ return millis * TicksPerMillisecond;
+ }
+
+ // Checks if this DateTime is equal to a given object. Returns
+ // true if the given object is a boxed DateTime and its value
+ // is equal to the value of this DateTime. Returns false
+ // otherwise.
+ //
+ public override bool Equals(Object value)
+ {
+ if (value is DateTime)
+ {
+ return InternalTicks == ((DateTime)value).InternalTicks;
+ }
+ return false;
+ }
+
+ public bool Equals(DateTime value)
+ {
+ return InternalTicks == value.InternalTicks;
+ }
+
+ // Compares two DateTime values for equality. Returns true if
+ // the two DateTime values are equal, or false if they are
+ // not equal.
+ //
+ public static bool Equals(DateTime t1, DateTime t2)
+ {
+ return t1.InternalTicks == t2.InternalTicks;
+ }
+
+ public static DateTime FromBinary(Int64 dateData)
+ {
+ if ((dateData & (unchecked((Int64)LocalMask))) != 0)
+ {
+ // Local times need to be adjusted as you move from one time zone to another,
+ // just as they are when serializing in text. As such the format for local times
+ // changes to store the ticks of the UTC time, but with flags that look like a
+ // local date.
+ Int64 ticks = dateData & (unchecked((Int64)TicksMask));
+ // Negative ticks are stored in the top part of the range and should be converted back into a negative number
+ if (ticks > TicksCeiling - TicksPerDay)
+ {
+ ticks = ticks - TicksCeiling;
+ }
+ // Convert the ticks back to local. If the UTC ticks are out of range, we need to default to
+ // the UTC offset from MinValue and MaxValue to be consistent with Parse.
+ Boolean isAmbiguousLocalDst = false;
+ Int64 offsetTicks;
+ if (ticks < MinTicks)
+ {
+ offsetTicks = TimeZoneInfo.GetLocalUtcOffset(DateTime.MinValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks;
+ }
+ else if (ticks > MaxTicks)
+ {
+ offsetTicks = TimeZoneInfo.GetLocalUtcOffset(DateTime.MaxValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks;
+ }
+ else
+ {
+ // Because the ticks conversion between UTC and local is lossy, we need to capture whether the
+ // time is in a repeated hour so that it can be passed to the DateTime constructor.
+ DateTime utcDt = new DateTime(ticks, DateTimeKind.Utc);
+ Boolean isDaylightSavings = false;
+ offsetTicks = TimeZoneInfo.GetUtcOffsetFromUtc(utcDt, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst).Ticks;
+ }
+ ticks += offsetTicks;
+ // Another behaviour of parsing is to cause small times to wrap around, so that they can be used
+ // to compare times of day
+ if (ticks < 0)
+ {
+ ticks += TicksPerDay;
+ }
+ if (ticks < MinTicks || ticks > MaxTicks)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeBadBinaryData, nameof(dateData));
+ }
+ return new DateTime(ticks, DateTimeKind.Local, isAmbiguousLocalDst);
+ }
+ else
+ {
+ return DateTime.FromBinaryRaw(dateData);
+ }
+ }
+
+ // A version of ToBinary that uses the real representation and does not adjust local times. This is needed for
+ // scenarios where the serialized data must maintain compatibility
+ internal static DateTime FromBinaryRaw(Int64 dateData)
+ {
+ Int64 ticks = dateData & (Int64)TicksMask;
+ if (ticks < MinTicks || ticks > MaxTicks)
+ throw new ArgumentException(SR.Argument_DateTimeBadBinaryData, nameof(dateData));
+ return new DateTime((UInt64)dateData);
+ }
+
+ // Creates a DateTime from a Windows filetime. A Windows filetime is
+ // a long representing the date and time as the number of
+ // 100-nanosecond intervals that have elapsed since 1/1/1601 12:00am.
+ //
+ public static DateTime FromFileTime(long fileTime)
+ {
+ return FromFileTimeUtc(fileTime).ToLocalTime();
+ }
+
+ public static DateTime FromFileTimeUtc(long fileTime)
+ {
+ if (fileTime < 0 || fileTime > MaxTicks - FileTimeOffset)
+ {
+ throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_FileTimeInvalid);
+ }
+
+ // This is the ticks in Universal time for this fileTime.
+ long universalTicks = fileTime + FileTimeOffset;
+ return new DateTime(universalTicks, DateTimeKind.Utc);
+ }
+
+ // Creates a DateTime from an OLE Automation Date.
+ //
+ public static DateTime FromOADate(double d)
+ {
+ return new DateTime(DoubleDateToTicks(d), DateTimeKind.Unspecified);
+ }
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ // Serialize both the old and the new format
+ info.AddValue(TicksField, InternalTicks);
+ info.AddValue(DateDataField, _dateData);
+ }
+
+ public Boolean IsDaylightSavingTime()
+ {
+ if (Kind == DateTimeKind.Utc)
+ {
+ return false;
+ }
+ return TimeZoneInfo.Local.IsDaylightSavingTime(this, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+
+ public static DateTime SpecifyKind(DateTime value, DateTimeKind kind)
+ {
+ return new DateTime(value.InternalTicks, kind);
+ }
+
+ public Int64 ToBinary()
+ {
+ if (Kind == DateTimeKind.Local)
+ {
+ // Local times need to be adjusted as you move from one time zone to another,
+ // just as they are when serializing in text. As such the format for local times
+ // changes to store the ticks of the UTC time, but with flags that look like a
+ // local date.
+
+ // To match serialization in text we need to be able to handle cases where
+ // the UTC value would be out of range. Unused parts of the ticks range are
+ // used for this, so that values just past max value are stored just past the
+ // end of the maximum range, and values just below minimum value are stored
+ // at the end of the ticks area, just below 2^62.
+ TimeSpan offset = TimeZoneInfo.GetLocalUtcOffset(this, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ Int64 ticks = Ticks;
+ Int64 storedTicks = ticks - offset.Ticks;
+ if (storedTicks < 0)
+ {
+ storedTicks = TicksCeiling + storedTicks;
+ }
+ return storedTicks | (unchecked((Int64)LocalMask));
+ }
+ else
+ {
+ return (Int64)_dateData;
+ }
+ }
+
+ // Returns the date part of this DateTime. The resulting value
+ // corresponds to this DateTime with the time-of-day part set to
+ // zero (midnight).
+ //
+ public DateTime Date
+ {
+ get
+ {
+ Int64 ticks = InternalTicks;
+ return new DateTime((UInt64)(ticks - ticks % TicksPerDay) | InternalKind);
+ }
+ }
+
+ // Returns a given date part of this DateTime. This method is used
+ // to compute the year, day-of-year, month, or day part.
+ private int GetDatePart(int part)
+ {
+ Int64 ticks = InternalTicks;
+ // n = number of days since 1/1/0001
+ int n = (int)(ticks / TicksPerDay);
+ // y400 = number of whole 400-year periods since 1/1/0001
+ int y400 = n / DaysPer400Years;
+ // n = day number within 400-year period
+ n -= y400 * DaysPer400Years;
+ // y100 = number of whole 100-year periods within 400-year period
+ int y100 = n / DaysPer100Years;
+ // Last 100-year period has an extra day, so decrement result if 4
+ if (y100 == 4) y100 = 3;
+ // n = day number within 100-year period
+ n -= y100 * DaysPer100Years;
+ // y4 = number of whole 4-year periods within 100-year period
+ int y4 = n / DaysPer4Years;
+ // n = day number within 4-year period
+ n -= y4 * DaysPer4Years;
+ // y1 = number of whole years within 4-year period
+ int y1 = n / DaysPerYear;
+ // Last year has an extra day, so decrement result if 4
+ if (y1 == 4) y1 = 3;
+ // If year was requested, compute and return it
+ if (part == DatePartYear)
+ {
+ return y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1;
+ }
+ // n = day number within year
+ n -= y1 * DaysPerYear;
+ // If day-of-year was requested, return it
+ if (part == DatePartDayOfYear) return n + 1;
+ // Leap year calculation looks different from IsLeapYear since y1, y4,
+ // and y100 are relative to year 1, not year 0
+ bool leapYear = y1 == 3 && (y4 != 24 || y100 == 3);
+ int[] days = leapYear ? s_daysToMonth366 : s_daysToMonth365;
+ // All months have less than 32 days, so n >> 5 is a good conservative
+ // estimate for the month
+ int m = (n >> 5) + 1;
+ // m = 1-based month number
+ while (n >= days[m]) m++;
+ // If month was requested, return it
+ if (part == DatePartMonth) return m;
+ // Return 1-based day-of-month
+ return n - days[m - 1] + 1;
+ }
+
+ // Exactly the same as GetDatePart(int part), except computing all of
+ // year/month/day rather than just one of them. Used when all three
+ // are needed rather than redoing the computations for each.
+ internal void GetDatePart(out int year, out int month, out int day)
+ {
+ Int64 ticks = InternalTicks;
+ // n = number of days since 1/1/0001
+ int n = (int)(ticks / TicksPerDay);
+ // y400 = number of whole 400-year periods since 1/1/0001
+ int y400 = n / DaysPer400Years;
+ // n = day number within 400-year period
+ n -= y400 * DaysPer400Years;
+ // y100 = number of whole 100-year periods within 400-year period
+ int y100 = n / DaysPer100Years;
+ // Last 100-year period has an extra day, so decrement result if 4
+ if (y100 == 4) y100 = 3;
+ // n = day number within 100-year period
+ n -= y100 * DaysPer100Years;
+ // y4 = number of whole 4-year periods within 100-year period
+ int y4 = n / DaysPer4Years;
+ // n = day number within 4-year period
+ n -= y4 * DaysPer4Years;
+ // y1 = number of whole years within 4-year period
+ int y1 = n / DaysPerYear;
+ // Last year has an extra day, so decrement result if 4
+ if (y1 == 4) y1 = 3;
+ // compute year
+ year = y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1;
+ // n = day number within year
+ n -= y1 * DaysPerYear;
+ // dayOfYear = n + 1;
+ // Leap year calculation looks different from IsLeapYear since y1, y4,
+ // and y100 are relative to year 1, not year 0
+ bool leapYear = y1 == 3 && (y4 != 24 || y100 == 3);
+ int[] days = leapYear ? s_daysToMonth366 : s_daysToMonth365;
+ // All months have less than 32 days, so n >> 5 is a good conservative
+ // estimate for the month
+ int m = (n >> 5) + 1;
+ // m = 1-based month number
+ while (n >= days[m]) m++;
+ // compute month and day
+ month = m;
+ day = n - days[m - 1] + 1;
+ }
+
+ // Returns the day-of-month part of this DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+ public int Day
+ {
+ get
+ {
+ return GetDatePart(DatePartDay);
+ }
+ }
+
+ // Returns the day-of-week part of this DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+ public DayOfWeek DayOfWeek
+ {
+ get
+ {
+ return (DayOfWeek)((InternalTicks / TicksPerDay + 1) % 7);
+ }
+ }
+
+ // Returns the day-of-year part of this DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+ public int DayOfYear
+ {
+ get
+ {
+ return GetDatePart(DatePartDayOfYear);
+ }
+ }
+
+ // Returns the hash code for this DateTime.
+ //
+ public override int GetHashCode()
+ {
+ Int64 ticks = InternalTicks;
+ return unchecked((int)ticks) ^ (int)(ticks >> 32);
+ }
+
+ // Returns the hour part of this DateTime. The returned value is an
+ // integer between 0 and 23.
+ //
+ public int Hour
+ {
+ get
+ {
+ return (int)((InternalTicks / TicksPerHour) % 24);
+ }
+ }
+
+ internal Boolean IsAmbiguousDaylightSavingTime()
+ {
+ return (InternalKind == KindLocalAmbiguousDst);
+ }
+
+ public DateTimeKind Kind
+ {
+ get
+ {
+ switch (InternalKind)
+ {
+ case KindUnspecified:
+ return DateTimeKind.Unspecified;
+ case KindUtc:
+ return DateTimeKind.Utc;
+ default:
+ return DateTimeKind.Local;
+ }
+ }
+ }
+
+ // Returns the millisecond part of this DateTime. The returned value
+ // is an integer between 0 and 999.
+ //
+ public int Millisecond
+ {
+ get
+ {
+ return (int)((InternalTicks / TicksPerMillisecond) % 1000);
+ }
+ }
+
+ // Returns the minute part of this DateTime. The returned value is
+ // an integer between 0 and 59.
+ //
+ public int Minute
+ {
+ get
+ {
+ return (int)((InternalTicks / TicksPerMinute) % 60);
+ }
+ }
+
+ // Returns the month part of this DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+ public int Month
+ {
+ get
+ {
+ return GetDatePart(DatePartMonth);
+ }
+ }
+
+ // Returns a DateTime representing the current date and time. The
+ // resolution of the returned value depends on the system timer.
+ public static DateTime Now
+ {
+ get
+ {
+ DateTime utc = UtcNow;
+ Boolean isAmbiguousLocalDst = false;
+ Int64 offset = TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(utc, out isAmbiguousLocalDst).Ticks;
+ long tick = utc.Ticks + offset;
+ if (tick > DateTime.MaxTicks)
+ {
+ return new DateTime(DateTime.MaxTicks, DateTimeKind.Local);
+ }
+ if (tick < DateTime.MinTicks)
+ {
+ return new DateTime(DateTime.MinTicks, DateTimeKind.Local);
+ }
+ return new DateTime(tick, DateTimeKind.Local, isAmbiguousLocalDst);
+ }
+ }
+
+ // Returns the second part of this DateTime. The returned value is
+ // an integer between 0 and 59.
+ //
+ public int Second
+ {
+ get
+ {
+ return (int)((InternalTicks / TicksPerSecond) % 60);
+ }
+ }
+
+ // Returns the tick count for this DateTime. The returned value is
+ // the number of 100-nanosecond intervals that have elapsed since 1/1/0001
+ // 12:00am.
+ //
+ public long Ticks
+ {
+ get
+ {
+ return InternalTicks;
+ }
+ }
+
+ // Returns the time-of-day part of this DateTime. The returned value
+ // is a TimeSpan that indicates the time elapsed since midnight.
+ //
+ public TimeSpan TimeOfDay
+ {
+ get
+ {
+ return new TimeSpan(InternalTicks % TicksPerDay);
+ }
+ }
+
+ // Returns a DateTime representing the current date. The date part
+ // of the returned value is the current date, and the time-of-day part of
+ // the returned value is zero (midnight).
+ //
+ public static DateTime Today
+ {
+ get
+ {
+ return DateTime.Now.Date;
+ }
+ }
+
+ // Returns the year part of this DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+ public int Year
+ {
+ get
+ {
+ return GetDatePart(DatePartYear);
+ }
+ }
+
+ // Checks whether a given year is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+ public static bool IsLeapYear(int year)
+ {
+ if (year < 1 || year > 9999)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_Year);
+ }
+ return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+ }
+
+ // Constructs a DateTime from a string. The string must specify a
+ // date and optionally a time in a culture-specific or universal format.
+ // Leading and trailing whitespace characters are allowed.
+ //
+ public static DateTime Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return (DateTimeParse.Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None));
+ }
+
+ // Constructs a DateTime from a string. The string must specify a
+ // date and optionally a time in a culture-specific or universal format.
+ // Leading and trailing whitespace characters are allowed.
+ //
+ public static DateTime Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return (DateTimeParse.Parse(s, DateTimeFormatInfo.GetInstance(provider), DateTimeStyles.None));
+ }
+
+ public static DateTime Parse(String s, IFormatProvider provider, DateTimeStyles styles)
+ {
+ DateTimeFormatInfo.ValidateStyles(styles, nameof(styles));
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return (DateTimeParse.Parse(s, DateTimeFormatInfo.GetInstance(provider), styles));
+ }
+
+ public static DateTime Parse(ReadOnlySpan<char> s, IFormatProvider provider = null, DateTimeStyles styles = DateTimeStyles.None)
+ {
+ DateTimeFormatInfo.ValidateStyles(styles, nameof(styles));
+ return DateTimeParse.Parse(s, DateTimeFormatInfo.GetInstance(provider), styles);
+ }
+
+ // Constructs a DateTime from a string. The string must specify a
+ // date and optionally a time in a culture-specific or universal format.
+ // Leading and trailing whitespace characters are allowed.
+ //
+ public static DateTime ParseExact(String s, String format, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
+ return (DateTimeParse.ParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), DateTimeStyles.None));
+ }
+
+ // Constructs a DateTime from a string. The string must specify a
+ // date and optionally a time in a culture-specific or universal format.
+ // Leading and trailing whitespace characters are allowed.
+ //
+ public static DateTime ParseExact(String s, String format, IFormatProvider provider, DateTimeStyles style)
+ {
+ DateTimeFormatInfo.ValidateStyles(style, nameof(style));
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
+ return (DateTimeParse.ParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style));
+ }
+
+ public static DateTime ParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, IFormatProvider provider, DateTimeStyles style = DateTimeStyles.None)
+ {
+ DateTimeFormatInfo.ValidateStyles(style, nameof(style));
+ return DateTimeParse.ParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style);
+ }
+
+ public static DateTime ParseExact(String s, String[] formats, IFormatProvider provider, DateTimeStyles style)
+ {
+ DateTimeFormatInfo.ValidateStyles(style, nameof(style));
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return DateTimeParse.ParseExactMultiple(s, formats, DateTimeFormatInfo.GetInstance(provider), style);
+ }
+
+ public static DateTime ParseExact(ReadOnlySpan<char> s, string[] formats, IFormatProvider provider, DateTimeStyles style = DateTimeStyles.None)
+ {
+ DateTimeFormatInfo.ValidateStyles(style, nameof(style));
+ return DateTimeParse.ParseExactMultiple(s, formats, DateTimeFormatInfo.GetInstance(provider), style);
+ }
+
+ public TimeSpan Subtract(DateTime value)
+ {
+ return new TimeSpan(InternalTicks - value.InternalTicks);
+ }
+
+ public DateTime Subtract(TimeSpan value)
+ {
+ long ticks = InternalTicks;
+ long valueTicks = value._ticks;
+ if (ticks - MinTicks < valueTicks || ticks - MaxTicks > valueTicks)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_DateArithmetic);
+ }
+ return new DateTime((UInt64)(ticks - valueTicks) | InternalKind);
+ }
+
+ // This function is duplicated in COMDateTime.cpp
+ private static double TicksToOADate(long value)
+ {
+ if (value == 0)
+ return 0.0; // Returns OleAut's zero'ed date value.
+ if (value < TicksPerDay) // This is a fix for VB. They want the default day to be 1/1/0001 rathar then 12/30/1899.
+ value += DoubleDateOffset; // We could have moved this fix down but we would like to keep the bounds check.
+ if (value < OADateMinAsTicks)
+ throw new OverflowException(SR.Arg_OleAutDateInvalid);
+ // Currently, our max date == OA's max date (12/31/9999), so we don't
+ // need an overflow check in that direction.
+ long millis = (value - DoubleDateOffset) / TicksPerMillisecond;
+ if (millis < 0)
+ {
+ long frac = millis % MillisPerDay;
+ if (frac != 0) millis -= (MillisPerDay + frac) * 2;
+ }
+ return (double)millis / MillisPerDay;
+ }
+
+ // Converts the DateTime instance into an OLE Automation compatible
+ // double date.
+ public double ToOADate()
+ {
+ return TicksToOADate(InternalTicks);
+ }
+
+ public long ToFileTime()
+ {
+ // Treats the input as local if it is not specified
+ return ToUniversalTime().ToFileTimeUtc();
+ }
+
+ public long ToFileTimeUtc()
+ {
+ // Treats the input as universal if it is not specified
+ long ticks = ((InternalKind & LocalMask) != 0) ? ToUniversalTime().InternalTicks : this.InternalTicks;
+ ticks -= FileTimeOffset;
+ if (ticks < 0)
+ {
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_FileTimeInvalid);
+ }
+ return ticks;
+ }
+
+ public DateTime ToLocalTime()
+ {
+ return ToLocalTime(false);
+ }
+
+ internal DateTime ToLocalTime(bool throwOnOverflow)
+ {
+ if (Kind == DateTimeKind.Local)
+ {
+ return this;
+ }
+ Boolean isDaylightSavings = false;
+ Boolean isAmbiguousLocalDst = false;
+ Int64 offset = TimeZoneInfo.GetUtcOffsetFromUtc(this, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst).Ticks;
+ long tick = Ticks + offset;
+ if (tick > DateTime.MaxTicks)
+ {
+ if (throwOnOverflow)
+ throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException);
+ else
+ return new DateTime(DateTime.MaxTicks, DateTimeKind.Local);
+ }
+ if (tick < DateTime.MinTicks)
+ {
+ if (throwOnOverflow)
+ throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException);
+ else
+ return new DateTime(DateTime.MinTicks, DateTimeKind.Local);
+ }
+ return new DateTime(tick, DateTimeKind.Local, isAmbiguousLocalDst);
+ }
+
+ public String ToLongDateString()
+ {
+ return DateTimeFormat.Format(this, "D", null);
+ }
+
+ public String ToLongTimeString()
+ {
+ return DateTimeFormat.Format(this, "T", null);
+ }
+
+ public String ToShortDateString()
+ {
+ return DateTimeFormat.Format(this, "d", null);
+ }
+
+ public String ToShortTimeString()
+ {
+ return DateTimeFormat.Format(this, "t", null);
+ }
+
+ public override String ToString()
+ {
+ return DateTimeFormat.Format(this, null, null);
+ }
+
+ public String ToString(String format)
+ {
+ return DateTimeFormat.Format(this, format, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return DateTimeFormat.Format(this, null, provider);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return DateTimeFormat.Format(this, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null) =>
+ DateTimeFormat.TryFormat(this, destination, out charsWritten, format, provider);
+
+ public DateTime ToUniversalTime()
+ {
+ return TimeZoneInfo.ConvertTimeToUtc(this, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+
+ public static Boolean TryParse(String s, out DateTime result)
+ {
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+ return DateTimeParse.TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, out DateTime result)
+ {
+ return DateTimeParse.TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result);
+ }
+
+ public static Boolean TryParse(String s, IFormatProvider provider, DateTimeStyles styles, out DateTime result)
+ {
+ DateTimeFormatInfo.ValidateStyles(styles, nameof(styles));
+
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+
+ return DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), styles, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider provider, DateTimeStyles styles, out DateTime result)
+ {
+ DateTimeFormatInfo.ValidateStyles(styles, nameof(styles));
+ return DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), styles, out result);
+ }
+
+ public static Boolean TryParseExact(String s, String format, IFormatProvider provider, DateTimeStyles style, out DateTime result)
+ {
+ DateTimeFormatInfo.ValidateStyles(style, nameof(style));
+
+ if (s == null || format == null)
+ {
+ result = default;
+ return false;
+ }
+
+ return DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, out result);
+ }
+
+ public static bool TryParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, IFormatProvider provider, DateTimeStyles style, out DateTime result)
+ {
+ DateTimeFormatInfo.ValidateStyles(style, nameof(style));
+ return DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, out result);
+ }
+
+ public static Boolean TryParseExact(String s, String[] formats, IFormatProvider provider, DateTimeStyles style, out DateTime result)
+ {
+ DateTimeFormatInfo.ValidateStyles(style, nameof(style));
+
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+
+ return DateTimeParse.TryParseExactMultiple(s, formats, DateTimeFormatInfo.GetInstance(provider), style, out result);
+ }
+
+ public static bool TryParseExact(ReadOnlySpan<char> s, string[] formats, IFormatProvider provider, DateTimeStyles style, out DateTime result)
+ {
+ DateTimeFormatInfo.ValidateStyles(style, nameof(style));
+ return DateTimeParse.TryParseExactMultiple(s, formats, DateTimeFormatInfo.GetInstance(provider), style, out result);
+ }
+
+ public static DateTime operator +(DateTime d, TimeSpan t)
+ {
+ long ticks = d.InternalTicks;
+ long valueTicks = t._ticks;
+ if (valueTicks > MaxTicks - ticks || valueTicks < MinTicks - ticks)
+ {
+ throw new ArgumentOutOfRangeException(nameof(t), SR.ArgumentOutOfRange_DateArithmetic);
+ }
+ return new DateTime((UInt64)(ticks + valueTicks) | d.InternalKind);
+ }
+
+ public static DateTime operator -(DateTime d, TimeSpan t)
+ {
+ long ticks = d.InternalTicks;
+ long valueTicks = t._ticks;
+ if (ticks - MinTicks < valueTicks || ticks - MaxTicks > valueTicks)
+ {
+ throw new ArgumentOutOfRangeException(nameof(t), SR.ArgumentOutOfRange_DateArithmetic);
+ }
+ return new DateTime((UInt64)(ticks - valueTicks) | d.InternalKind);
+ }
+
+ public static TimeSpan operator -(DateTime d1, DateTime d2)
+ {
+ return new TimeSpan(d1.InternalTicks - d2.InternalTicks);
+ }
+
+ public static bool operator ==(DateTime d1, DateTime d2)
+ {
+ return d1.InternalTicks == d2.InternalTicks;
+ }
+
+ public static bool operator !=(DateTime d1, DateTime d2)
+ {
+ return d1.InternalTicks != d2.InternalTicks;
+ }
+
+ public static bool operator <(DateTime t1, DateTime t2)
+ {
+ return t1.InternalTicks < t2.InternalTicks;
+ }
+
+ public static bool operator <=(DateTime t1, DateTime t2)
+ {
+ return t1.InternalTicks <= t2.InternalTicks;
+ }
+
+ public static bool operator >(DateTime t1, DateTime t2)
+ {
+ return t1.InternalTicks > t2.InternalTicks;
+ }
+
+ public static bool operator >=(DateTime t1, DateTime t2)
+ {
+ return t1.InternalTicks >= t2.InternalTicks;
+ }
+
+
+ // Returns a string array containing all of the known date and time options for the
+ // current culture. The strings returned are properly formatted date and
+ // time strings for the current instance of DateTime.
+ public String[] GetDateTimeFormats()
+ {
+ return (GetDateTimeFormats(CultureInfo.CurrentCulture));
+ }
+
+ // Returns a string array containing all of the known date and time options for the
+ // using the information provided by IFormatProvider. The strings returned are properly formatted date and
+ // time strings for the current instance of DateTime.
+ public String[] GetDateTimeFormats(IFormatProvider provider)
+ {
+ return (DateTimeFormat.GetAllDateTimes(this, DateTimeFormatInfo.GetInstance(provider)));
+ }
+
+
+ // Returns a string array containing all of the date and time options for the
+ // given format format and current culture. The strings returned are properly formatted date and
+ // time strings for the current instance of DateTime.
+ public String[] GetDateTimeFormats(char format)
+ {
+ return (GetDateTimeFormats(format, CultureInfo.CurrentCulture));
+ }
+
+ // Returns a string array containing all of the date and time options for the
+ // given format format and given culture. The strings returned are properly formatted date and
+ // time strings for the current instance of DateTime.
+ public String[] GetDateTimeFormats(char format, IFormatProvider provider)
+ {
+ return (DateTimeFormat.GetAllDateTimes(this, format, DateTimeFormatInfo.GetInstance(provider)));
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.DateTime;
+ }
+
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Boolean"));
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Char"));
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "SByte"));
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Byte"));
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Int16"));
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "UInt16"));
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Int32"));
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "UInt32"));
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Int64"));
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "UInt64"));
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Single"));
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Double"));
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Decimal"));
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ return this;
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+
+ // Tries to construct a DateTime from a given year, month, day, hour,
+ // minute, second and millisecond.
+ //
+ internal static Boolean TryCreate(int year, int month, int day, int hour, int minute, int second, int millisecond, out DateTime result)
+ {
+ result = DateTime.MinValue;
+ if (year < 1 || year > 9999 || month < 1 || month > 12)
+ {
+ return false;
+ }
+ int[] days = IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365;
+ if (day < 1 || day > days[month] - days[month - 1])
+ {
+ return false;
+ }
+ if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second >= 60)
+ {
+ return false;
+ }
+ if (millisecond < 0 || millisecond >= MillisPerSecond)
+ {
+ return false;
+ }
+ long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second);
+
+ ticks += millisecond * TicksPerMillisecond;
+ if (ticks < MinTicks || ticks > MaxTicks)
+ {
+ return false;
+ }
+ result = new DateTime(ticks, DateTimeKind.Unspecified);
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/DateTimeKind.cs b/src/System.Private.CoreLib/shared/System/DateTimeKind.cs
new file mode 100644
index 0000000000..33c9bd925f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DateTimeKind.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ // This enum is used to indentify DateTime instances in cases when they are known to be in local time,
+ // UTC time or if this information has not been specified or is not applicable.
+
+ public enum DateTimeKind
+ {
+ Unspecified = 0,
+ Utc = 1,
+ Local = 2,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs b/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs
new file mode 100644
index 0000000000..3c3f3f42a2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs
@@ -0,0 +1,1005 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // DateTimeOffset is a value type that consists of a DateTime and a time zone offset,
+ // ie. how far away the time is from GMT. The DateTime is stored whole, and the offset
+ // is stored as an Int16 internally to save space, but presented as a TimeSpan.
+ //
+ // The range is constrained so that both the represented clock time and the represented
+ // UTC time fit within the boundaries of MaxValue. This gives it the same range as DateTime
+ // for actual UTC times, and a slightly constrained range on one end when an offset is
+ // present.
+ //
+ // This class should be substitutable for date time in most cases; so most operations
+ // effectively work on the clock time. However, the underlying UTC time is what counts
+ // for the purposes of identity, sorting and subtracting two instances.
+ //
+ //
+ // There are theoretically two date times stored, the UTC and the relative local representation
+ // or the 'clock' time. It actually does not matter which is stored in m_dateTime, so it is desirable
+ // for most methods to go through the helpers UtcDateTime and ClockDateTime both to abstract this
+ // out and for internal readability.
+
+ [StructLayout(LayoutKind.Auto)]
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct DateTimeOffset : IComparable, IFormattable, IComparable<DateTimeOffset>, IEquatable<DateTimeOffset>, ISerializable, IDeserializationCallback, ISpanFormattable
+ {
+ // Constants
+ internal const Int64 MaxOffset = TimeSpan.TicksPerHour * 14;
+ internal const Int64 MinOffset = -MaxOffset;
+
+ private const long UnixEpochSeconds = DateTime.UnixEpochTicks / TimeSpan.TicksPerSecond; // 62,135,596,800
+ private const long UnixEpochMilliseconds = DateTime.UnixEpochTicks / TimeSpan.TicksPerMillisecond; // 62,135,596,800,000
+
+ internal const long UnixMinSeconds = DateTime.MinTicks / TimeSpan.TicksPerSecond - UnixEpochSeconds;
+ internal const long UnixMaxSeconds = DateTime.MaxTicks / TimeSpan.TicksPerSecond - UnixEpochSeconds;
+
+ // Static Fields
+ public static readonly DateTimeOffset MinValue = new DateTimeOffset(DateTime.MinTicks, TimeSpan.Zero);
+ public static readonly DateTimeOffset MaxValue = new DateTimeOffset(DateTime.MaxTicks, TimeSpan.Zero);
+ public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(DateTime.UnixEpochTicks, TimeSpan.Zero);
+
+ // Instance Fields
+ private DateTime _dateTime;
+ private Int16 _offsetMinutes;
+
+ // Constructors
+
+ // Constructs a DateTimeOffset from a tick count and offset
+ public DateTimeOffset(long ticks, TimeSpan offset)
+ {
+ _offsetMinutes = ValidateOffset(offset);
+ // Let the DateTime constructor do the range checks
+ DateTime dateTime = new DateTime(ticks);
+ _dateTime = ValidateDate(dateTime, offset);
+ }
+
+ // Constructs a DateTimeOffset from a DateTime. For Local and Unspecified kinds,
+ // extracts the local offset. For UTC, creates a UTC instance with a zero offset.
+ public DateTimeOffset(DateTime dateTime)
+ {
+ TimeSpan offset;
+ if (dateTime.Kind != DateTimeKind.Utc)
+ {
+ // Local and Unspecified are both treated as Local
+ offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+ else
+ {
+ offset = new TimeSpan(0);
+ }
+ _offsetMinutes = ValidateOffset(offset);
+ _dateTime = ValidateDate(dateTime, offset);
+ }
+
+ // Constructs a DateTimeOffset from a DateTime. And an offset. Always makes the clock time
+ // consistent with the DateTime. For Utc ensures the offset is zero. For local, ensures that
+ // the offset corresponds to the local.
+ public DateTimeOffset(DateTime dateTime, TimeSpan offset)
+ {
+ if (dateTime.Kind == DateTimeKind.Local)
+ {
+ if (offset != TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime))
+ {
+ throw new ArgumentException(SR.Argument_OffsetLocalMismatch, nameof(offset));
+ }
+ }
+ else if (dateTime.Kind == DateTimeKind.Utc)
+ {
+ if (offset != TimeSpan.Zero)
+ {
+ throw new ArgumentException(SR.Argument_OffsetUtcMismatch, nameof(offset));
+ }
+ }
+ _offsetMinutes = ValidateOffset(offset);
+ _dateTime = ValidateDate(dateTime, offset);
+ }
+
+ // Constructs a DateTimeOffset from a given year, month, day, hour,
+ // minute, second and offset.
+ public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, TimeSpan offset)
+ {
+ _offsetMinutes = ValidateOffset(offset);
+ _dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second), offset);
+ }
+
+ // Constructs a DateTimeOffset from a given year, month, day, hour,
+ // minute, second, millsecond and offset
+ public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSpan offset)
+ {
+ _offsetMinutes = ValidateOffset(offset);
+ _dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second, millisecond), offset);
+ }
+
+ // Constructs a DateTimeOffset from a given year, month, day, hour,
+ // minute, second, millsecond, Calendar and offset.
+ public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, TimeSpan offset)
+ {
+ _offsetMinutes = ValidateOffset(offset);
+ _dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second, millisecond, calendar), offset);
+ }
+
+ // Returns a DateTimeOffset representing the current date and time. The
+ // resolution of the returned value depends on the system timer.
+ public static DateTimeOffset Now
+ {
+ get
+ {
+ return new DateTimeOffset(DateTime.Now);
+ }
+ }
+
+ public static DateTimeOffset UtcNow
+ {
+ get
+ {
+ return new DateTimeOffset(DateTime.UtcNow);
+ }
+ }
+
+ public DateTime DateTime
+ {
+ get
+ {
+ return ClockDateTime;
+ }
+ }
+
+ public DateTime UtcDateTime
+ {
+ get
+ {
+ return DateTime.SpecifyKind(_dateTime, DateTimeKind.Utc);
+ }
+ }
+
+ public DateTime LocalDateTime
+ {
+ get
+ {
+ return UtcDateTime.ToLocalTime();
+ }
+ }
+
+ // Adjust to a given offset with the same UTC time. Can throw ArgumentException
+ //
+ public DateTimeOffset ToOffset(TimeSpan offset)
+ {
+ return new DateTimeOffset((_dateTime + offset).Ticks, offset);
+ }
+
+
+ // Instance Properties
+
+ // The clock or visible time represented. This is just a wrapper around the internal date because this is
+ // the chosen storage mechanism. Going through this helper is good for readability and maintainability.
+ // This should be used for display but not identity.
+ private DateTime ClockDateTime
+ {
+ get
+ {
+ return new DateTime((_dateTime + Offset).Ticks, DateTimeKind.Unspecified);
+ }
+ }
+
+ // Returns the date part of this DateTimeOffset. The resulting value
+ // corresponds to this DateTimeOffset with the time-of-day part set to
+ // zero (midnight).
+ //
+ public DateTime Date
+ {
+ get
+ {
+ return ClockDateTime.Date;
+ }
+ }
+
+ // Returns the day-of-month part of this DateTimeOffset. The returned
+ // value is an integer between 1 and 31.
+ //
+ public int Day
+ {
+ get
+ {
+ return ClockDateTime.Day;
+ }
+ }
+
+ // Returns the day-of-week part of this DateTimeOffset. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+ public DayOfWeek DayOfWeek
+ {
+ get
+ {
+ return ClockDateTime.DayOfWeek;
+ }
+ }
+
+ // Returns the day-of-year part of this DateTimeOffset. The returned value
+ // is an integer between 1 and 366.
+ //
+ public int DayOfYear
+ {
+ get
+ {
+ return ClockDateTime.DayOfYear;
+ }
+ }
+
+ // Returns the hour part of this DateTimeOffset. The returned value is an
+ // integer between 0 and 23.
+ //
+ public int Hour
+ {
+ get
+ {
+ return ClockDateTime.Hour;
+ }
+ }
+
+
+ // Returns the millisecond part of this DateTimeOffset. The returned value
+ // is an integer between 0 and 999.
+ //
+ public int Millisecond
+ {
+ get
+ {
+ return ClockDateTime.Millisecond;
+ }
+ }
+
+ // Returns the minute part of this DateTimeOffset. The returned value is
+ // an integer between 0 and 59.
+ //
+ public int Minute
+ {
+ get
+ {
+ return ClockDateTime.Minute;
+ }
+ }
+
+ // Returns the month part of this DateTimeOffset. The returned value is an
+ // integer between 1 and 12.
+ //
+ public int Month
+ {
+ get
+ {
+ return ClockDateTime.Month;
+ }
+ }
+
+ public TimeSpan Offset
+ {
+ get
+ {
+ return new TimeSpan(0, _offsetMinutes, 0);
+ }
+ }
+
+ // Returns the second part of this DateTimeOffset. The returned value is
+ // an integer between 0 and 59.
+ //
+ public int Second
+ {
+ get
+ {
+ return ClockDateTime.Second;
+ }
+ }
+
+ // Returns the tick count for this DateTimeOffset. The returned value is
+ // the number of 100-nanosecond intervals that have elapsed since 1/1/0001
+ // 12:00am.
+ //
+ public long Ticks
+ {
+ get
+ {
+ return ClockDateTime.Ticks;
+ }
+ }
+
+ public long UtcTicks
+ {
+ get
+ {
+ return UtcDateTime.Ticks;
+ }
+ }
+
+ // Returns the time-of-day part of this DateTimeOffset. The returned value
+ // is a TimeSpan that indicates the time elapsed since midnight.
+ //
+ public TimeSpan TimeOfDay
+ {
+ get
+ {
+ return ClockDateTime.TimeOfDay;
+ }
+ }
+
+ // Returns the year part of this DateTimeOffset. The returned value is an
+ // integer between 1 and 9999.
+ //
+ public int Year
+ {
+ get
+ {
+ return ClockDateTime.Year;
+ }
+ }
+
+ // Returns the DateTimeOffset resulting from adding the given
+ // TimeSpan to this DateTimeOffset.
+ //
+ public DateTimeOffset Add(TimeSpan timeSpan)
+ {
+ return new DateTimeOffset(ClockDateTime.Add(timeSpan), Offset);
+ }
+
+ // Returns the DateTimeOffset resulting from adding a fractional number of
+ // days to this DateTimeOffset. The result is computed by rounding the
+ // fractional number of days given by value to the nearest
+ // millisecond, and adding that interval to this DateTimeOffset. The
+ // value argument is permitted to be negative.
+ //
+ public DateTimeOffset AddDays(double days)
+ {
+ return new DateTimeOffset(ClockDateTime.AddDays(days), Offset);
+ }
+
+ // Returns the DateTimeOffset resulting from adding a fractional number of
+ // hours to this DateTimeOffset. The result is computed by rounding the
+ // fractional number of hours given by value to the nearest
+ // millisecond, and adding that interval to this DateTimeOffset. The
+ // value argument is permitted to be negative.
+ //
+ public DateTimeOffset AddHours(double hours)
+ {
+ return new DateTimeOffset(ClockDateTime.AddHours(hours), Offset);
+ }
+
+ // Returns the DateTimeOffset resulting from the given number of
+ // milliseconds to this DateTimeOffset. The result is computed by rounding
+ // the number of milliseconds given by value to the nearest integer,
+ // and adding that interval to this DateTimeOffset. The value
+ // argument is permitted to be negative.
+ //
+ public DateTimeOffset AddMilliseconds(double milliseconds)
+ {
+ return new DateTimeOffset(ClockDateTime.AddMilliseconds(milliseconds), Offset);
+ }
+
+ // Returns the DateTimeOffset resulting from adding a fractional number of
+ // minutes to this DateTimeOffset. The result is computed by rounding the
+ // fractional number of minutes given by value to the nearest
+ // millisecond, and adding that interval to this DateTimeOffset. The
+ // value argument is permitted to be negative.
+ //
+ public DateTimeOffset AddMinutes(double minutes)
+ {
+ return new DateTimeOffset(ClockDateTime.AddMinutes(minutes), Offset);
+ }
+
+ public DateTimeOffset AddMonths(int months)
+ {
+ return new DateTimeOffset(ClockDateTime.AddMonths(months), Offset);
+ }
+
+ // Returns the DateTimeOffset resulting from adding a fractional number of
+ // seconds to this DateTimeOffset. The result is computed by rounding the
+ // fractional number of seconds given by value to the nearest
+ // millisecond, and adding that interval to this DateTimeOffset. The
+ // value argument is permitted to be negative.
+ //
+ public DateTimeOffset AddSeconds(double seconds)
+ {
+ return new DateTimeOffset(ClockDateTime.AddSeconds(seconds), Offset);
+ }
+
+ // Returns the DateTimeOffset resulting from adding the given number of
+ // 100-nanosecond ticks to this DateTimeOffset. The value argument
+ // is permitted to be negative.
+ //
+ public DateTimeOffset AddTicks(long ticks)
+ {
+ return new DateTimeOffset(ClockDateTime.AddTicks(ticks), Offset);
+ }
+
+ // Returns the DateTimeOffset resulting from adding the given number of
+ // years to this DateTimeOffset. The result is computed by incrementing
+ // (or decrementing) the year part of this DateTimeOffset by value
+ // years. If the month and day of this DateTimeOffset is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTimeOffset becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of this DateTimeOffset.
+ //
+ public DateTimeOffset AddYears(int years)
+ {
+ return new DateTimeOffset(ClockDateTime.AddYears(years), Offset);
+ }
+
+ // Compares two DateTimeOffset values, returning an integer that indicates
+ // their relationship.
+ //
+ public static int Compare(DateTimeOffset first, DateTimeOffset second)
+ {
+ return DateTime.Compare(first.UtcDateTime, second.UtcDateTime);
+ }
+
+ // Compares this DateTimeOffset to a given object. This method provides an
+ // implementation of the IComparable interface. The object
+ // argument must be another DateTimeOffset, or otherwise an exception
+ // occurs. Null is considered less than any instance.
+ //
+ int IComparable.CompareTo(Object obj)
+ {
+ if (obj == null) return 1;
+ if (!(obj is DateTimeOffset))
+ {
+ throw new ArgumentException(SR.Arg_MustBeDateTimeOffset);
+ }
+
+ DateTime objUtc = ((DateTimeOffset)obj).UtcDateTime;
+ DateTime utc = UtcDateTime;
+ if (utc > objUtc) return 1;
+ if (utc < objUtc) return -1;
+ return 0;
+ }
+
+ public int CompareTo(DateTimeOffset other)
+ {
+ DateTime otherUtc = other.UtcDateTime;
+ DateTime utc = UtcDateTime;
+ if (utc > otherUtc) return 1;
+ if (utc < otherUtc) return -1;
+ return 0;
+ }
+
+
+ // Checks if this DateTimeOffset is equal to a given object. Returns
+ // true if the given object is a boxed DateTimeOffset and its value
+ // is equal to the value of this DateTimeOffset. Returns false
+ // otherwise.
+ //
+ public override bool Equals(Object obj)
+ {
+ if (obj is DateTimeOffset)
+ {
+ return UtcDateTime.Equals(((DateTimeOffset)obj).UtcDateTime);
+ }
+ return false;
+ }
+
+ public bool Equals(DateTimeOffset other)
+ {
+ return UtcDateTime.Equals(other.UtcDateTime);
+ }
+
+ public bool EqualsExact(DateTimeOffset other)
+ {
+ //
+ // returns true when the ClockDateTime, Kind, and Offset match
+ //
+ // currently the Kind should always be Unspecified, but there is always the possibility that a future version
+ // of DateTimeOffset overloads the Kind field
+ //
+ return (ClockDateTime == other.ClockDateTime && Offset == other.Offset && ClockDateTime.Kind == other.ClockDateTime.Kind);
+ }
+
+ // Compares two DateTimeOffset values for equality. Returns true if
+ // the two DateTimeOffset values are equal, or false if they are
+ // not equal.
+ //
+ public static bool Equals(DateTimeOffset first, DateTimeOffset second)
+ {
+ return DateTime.Equals(first.UtcDateTime, second.UtcDateTime);
+ }
+
+ // Creates a DateTimeOffset from a Windows filetime. A Windows filetime is
+ // a long representing the date and time as the number of
+ // 100-nanosecond intervals that have elapsed since 1/1/1601 12:00am.
+ //
+ public static DateTimeOffset FromFileTime(long fileTime)
+ {
+ return new DateTimeOffset(DateTime.FromFileTime(fileTime));
+ }
+
+ public static DateTimeOffset FromUnixTimeSeconds(long seconds)
+ {
+ if (seconds < UnixMinSeconds || seconds > UnixMaxSeconds)
+ {
+ throw new ArgumentOutOfRangeException(nameof(seconds),
+ SR.Format(SR.ArgumentOutOfRange_Range, UnixMinSeconds, UnixMaxSeconds));
+ }
+
+ long ticks = seconds * TimeSpan.TicksPerSecond + DateTime.UnixEpochTicks;
+ return new DateTimeOffset(ticks, TimeSpan.Zero);
+ }
+
+ public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds)
+ {
+ const long MinMilliseconds = DateTime.MinTicks / TimeSpan.TicksPerMillisecond - UnixEpochMilliseconds;
+ const long MaxMilliseconds = DateTime.MaxTicks / TimeSpan.TicksPerMillisecond - UnixEpochMilliseconds;
+
+ if (milliseconds < MinMilliseconds || milliseconds > MaxMilliseconds)
+ {
+ throw new ArgumentOutOfRangeException(nameof(milliseconds),
+ SR.Format(SR.ArgumentOutOfRange_Range, MinMilliseconds, MaxMilliseconds));
+ }
+
+ long ticks = milliseconds * TimeSpan.TicksPerMillisecond + DateTime.UnixEpochTicks;
+ return new DateTimeOffset(ticks, TimeSpan.Zero);
+ }
+
+ // ----- SECTION: private serialization instance methods ----------------*
+
+ void IDeserializationCallback.OnDeserialization(Object sender)
+ {
+ try
+ {
+ _offsetMinutes = ValidateOffset(Offset);
+ _dateTime = ValidateDate(ClockDateTime, Offset);
+ }
+ catch (ArgumentException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+ }
+
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ info.AddValue("DateTime", _dateTime); // Do not rename (binary serialization)
+ info.AddValue("OffsetMinutes", _offsetMinutes); // Do not rename (binary serialization)
+ }
+
+
+ private DateTimeOffset(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ _dateTime = (DateTime)info.GetValue("DateTime", typeof(DateTime)); // Do not rename (binary serialization)
+ _offsetMinutes = (Int16)info.GetValue("OffsetMinutes", typeof(Int16)); // Do not rename (binary serialization)
+ }
+
+ // Returns the hash code for this DateTimeOffset.
+ //
+ public override int GetHashCode()
+ {
+ return UtcDateTime.GetHashCode();
+ }
+
+ // Constructs a DateTimeOffset from a string. The string must specify a
+ // date and optionally a time in a culture-specific or universal format.
+ // Leading and trailing whitespace characters are allowed.
+ //
+ public static DateTimeOffset Parse(String input)
+ {
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+
+ TimeSpan offset;
+ DateTime dateResult = DateTimeParse.Parse(input,
+ DateTimeFormatInfo.CurrentInfo,
+ DateTimeStyles.None,
+ out offset);
+ return new DateTimeOffset(dateResult.Ticks, offset);
+ }
+
+ // Constructs a DateTimeOffset from a string. The string must specify a
+ // date and optionally a time in a culture-specific or universal format.
+ // Leading and trailing whitespace characters are allowed.
+ //
+ public static DateTimeOffset Parse(String input, IFormatProvider formatProvider)
+ {
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ return Parse(input, formatProvider, DateTimeStyles.None);
+ }
+
+ public static DateTimeOffset Parse(String input, IFormatProvider formatProvider, DateTimeStyles styles)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+
+ TimeSpan offset;
+ DateTime dateResult = DateTimeParse.Parse(input,
+ DateTimeFormatInfo.GetInstance(formatProvider),
+ styles,
+ out offset);
+ return new DateTimeOffset(dateResult.Ticks, offset);
+ }
+
+ public static DateTimeOffset Parse(ReadOnlySpan<char> input, IFormatProvider formatProvider = null, DateTimeStyles styles = DateTimeStyles.None)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ DateTime dateResult = DateTimeParse.Parse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
+ return new DateTimeOffset(dateResult.Ticks, offset);
+ }
+
+ // Constructs a DateTimeOffset from a string. The string must specify a
+ // date and optionally a time in a culture-specific or universal format.
+ // Leading and trailing whitespace characters are allowed.
+ //
+ public static DateTimeOffset ParseExact(String input, String format, IFormatProvider formatProvider)
+ {
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
+ return ParseExact(input, format, formatProvider, DateTimeStyles.None);
+ }
+
+ // Constructs a DateTimeOffset from a string. The string must specify a
+ // date and optionally a time in a culture-specific or universal format.
+ // Leading and trailing whitespace characters are allowed.
+ //
+ public static DateTimeOffset ParseExact(String input, String format, IFormatProvider formatProvider, DateTimeStyles styles)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
+
+ TimeSpan offset;
+ DateTime dateResult = DateTimeParse.ParseExact(input,
+ format,
+ DateTimeFormatInfo.GetInstance(formatProvider),
+ styles,
+ out offset);
+ return new DateTimeOffset(dateResult.Ticks, offset);
+ }
+
+ public static DateTimeOffset ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, DateTimeStyles styles = DateTimeStyles.None)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ DateTime dateResult = DateTimeParse.ParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
+ return new DateTimeOffset(dateResult.Ticks, offset);
+ }
+
+ public static DateTimeOffset ParseExact(String input, String[] formats, IFormatProvider formatProvider, DateTimeStyles styles)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+
+ TimeSpan offset;
+ DateTime dateResult = DateTimeParse.ParseExactMultiple(input,
+ formats,
+ DateTimeFormatInfo.GetInstance(formatProvider),
+ styles,
+ out offset);
+ return new DateTimeOffset(dateResult.Ticks, offset);
+ }
+
+ public static DateTimeOffset ParseExact(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles = DateTimeStyles.None)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ DateTime dateResult = DateTimeParse.ParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
+ return new DateTimeOffset(dateResult.Ticks, offset);
+ }
+
+ public TimeSpan Subtract(DateTimeOffset value)
+ {
+ return UtcDateTime.Subtract(value.UtcDateTime);
+ }
+
+ public DateTimeOffset Subtract(TimeSpan value)
+ {
+ return new DateTimeOffset(ClockDateTime.Subtract(value), Offset);
+ }
+
+
+ public long ToFileTime()
+ {
+ return UtcDateTime.ToFileTime();
+ }
+
+ public long ToUnixTimeSeconds()
+ {
+ // Truncate sub-second precision before offsetting by the Unix Epoch to avoid
+ // the last digit being off by one for dates that result in negative Unix times.
+ //
+ // For example, consider the DateTimeOffset 12/31/1969 12:59:59.001 +0
+ // ticks = 621355967990010000
+ // ticksFromEpoch = ticks - DateTime.UnixEpochTicks = -9990000
+ // secondsFromEpoch = ticksFromEpoch / TimeSpan.TicksPerSecond = 0
+ //
+ // Notice that secondsFromEpoch is rounded *up* by the truncation induced by integer division,
+ // whereas we actually always want to round *down* when converting to Unix time. This happens
+ // automatically for positive Unix time values. Now the example becomes:
+ // seconds = ticks / TimeSpan.TicksPerSecond = 62135596799
+ // secondsFromEpoch = seconds - UnixEpochSeconds = -1
+ //
+ // In other words, we want to consistently round toward the time 1/1/0001 00:00:00,
+ // rather than toward the Unix Epoch (1/1/1970 00:00:00).
+ long seconds = UtcDateTime.Ticks / TimeSpan.TicksPerSecond;
+ return seconds - UnixEpochSeconds;
+ }
+
+ public long ToUnixTimeMilliseconds()
+ {
+ // Truncate sub-millisecond precision before offsetting by the Unix Epoch to avoid
+ // the last digit being off by one for dates that result in negative Unix times
+ long milliseconds = UtcDateTime.Ticks / TimeSpan.TicksPerMillisecond;
+ return milliseconds - UnixEpochMilliseconds;
+ }
+
+ public DateTimeOffset ToLocalTime()
+ {
+ return ToLocalTime(false);
+ }
+
+ internal DateTimeOffset ToLocalTime(bool throwOnOverflow)
+ {
+ return new DateTimeOffset(UtcDateTime.ToLocalTime(throwOnOverflow));
+ }
+
+ public override String ToString()
+ {
+ return DateTimeFormat.Format(ClockDateTime, null, null, Offset);
+ }
+
+ public String ToString(String format)
+ {
+ return DateTimeFormat.Format(ClockDateTime, format, null, Offset);
+ }
+
+ public String ToString(IFormatProvider formatProvider)
+ {
+ return DateTimeFormat.Format(ClockDateTime, null, formatProvider, Offset);
+ }
+
+ public String ToString(String format, IFormatProvider formatProvider)
+ {
+ return DateTimeFormat.Format(ClockDateTime, format, formatProvider, Offset);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider formatProvider = null) =>
+ DateTimeFormat.TryFormat(ClockDateTime, destination, out charsWritten, format, formatProvider, Offset);
+
+ public DateTimeOffset ToUniversalTime()
+ {
+ return new DateTimeOffset(UtcDateTime);
+ }
+
+ public static Boolean TryParse(String input, out DateTimeOffset result)
+ {
+ TimeSpan offset;
+ DateTime dateResult;
+ Boolean parsed = DateTimeParse.TryParse(input,
+ DateTimeFormatInfo.CurrentInfo,
+ DateTimeStyles.None,
+ out dateResult,
+ out offset);
+ result = new DateTimeOffset(dateResult.Ticks, offset);
+ return parsed;
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> input, out DateTimeOffset result)
+ {
+ bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out DateTime dateResult, out TimeSpan offset);
+ result = new DateTimeOffset(dateResult.Ticks, offset);
+ return parsed;
+ }
+
+ public static Boolean TryParse(String input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ if (input == null)
+ {
+ result = default(DateTimeOffset);
+ return false;
+ }
+
+ TimeSpan offset;
+ DateTime dateResult;
+ Boolean parsed = DateTimeParse.TryParse(input,
+ DateTimeFormatInfo.GetInstance(formatProvider),
+ styles,
+ out dateResult,
+ out offset);
+ result = new DateTimeOffset(dateResult.Ticks, offset);
+ return parsed;
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
+ result = new DateTimeOffset(dateResult.Ticks, offset);
+ return parsed;
+ }
+
+ public static Boolean TryParseExact(String input, String format, IFormatProvider formatProvider, DateTimeStyles styles,
+ out DateTimeOffset result)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ if (input == null || format == null)
+ {
+ result = default(DateTimeOffset);
+ return false;
+ }
+
+ TimeSpan offset;
+ DateTime dateResult;
+ Boolean parsed = DateTimeParse.TryParseExact(input,
+ format,
+ DateTimeFormatInfo.GetInstance(formatProvider),
+ styles,
+ out dateResult,
+ out offset);
+ result = new DateTimeOffset(dateResult.Ticks, offset);
+ return parsed;
+ }
+
+ public static bool TryParseExact(
+ ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ bool parsed = DateTimeParse.TryParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
+ result = new DateTimeOffset(dateResult.Ticks, offset);
+ return parsed;
+ }
+
+ public static Boolean TryParseExact(String input, String[] formats, IFormatProvider formatProvider, DateTimeStyles styles,
+ out DateTimeOffset result)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ if (input == null)
+ {
+ result = default(DateTimeOffset);
+ return false;
+ }
+
+ TimeSpan offset;
+ DateTime dateResult;
+ Boolean parsed = DateTimeParse.TryParseExactMultiple(input,
+ formats,
+ DateTimeFormatInfo.GetInstance(formatProvider),
+ styles,
+ out dateResult,
+ out offset);
+ result = new DateTimeOffset(dateResult.Ticks, offset);
+ return parsed;
+ }
+
+ public static bool TryParseExact(
+ ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
+ {
+ styles = ValidateStyles(styles, nameof(styles));
+ bool parsed = DateTimeParse.TryParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
+ result = new DateTimeOffset(dateResult.Ticks, offset);
+ return parsed;
+ }
+
+ // Ensures the TimeSpan is valid to go in a DateTimeOffset.
+ private static Int16 ValidateOffset(TimeSpan offset)
+ {
+ Int64 ticks = offset.Ticks;
+ if (ticks % TimeSpan.TicksPerMinute != 0)
+ {
+ throw new ArgumentException(SR.Argument_OffsetPrecision, nameof(offset));
+ }
+ if (ticks < MinOffset || ticks > MaxOffset)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_OffsetOutOfRange);
+ }
+ return (Int16)(offset.Ticks / TimeSpan.TicksPerMinute);
+ }
+
+ // Ensures that the time and offset are in range.
+ private static DateTime ValidateDate(DateTime dateTime, TimeSpan offset)
+ {
+ // The key validation is that both the UTC and clock times fit. The clock time is validated
+ // by the DateTime constructor.
+ Debug.Assert(offset.Ticks >= MinOffset && offset.Ticks <= MaxOffset, "Offset not validated.");
+
+ // This operation cannot overflow because offset should have already been validated to be within
+ // 14 hours and the DateTime instance is more than that distance from the boundaries of Int64.
+ Int64 utcTicks = dateTime.Ticks - offset.Ticks;
+ if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_UTCOutOfRange);
+ }
+ // make sure the Kind is set to Unspecified
+ //
+ return new DateTime(utcTicks, DateTimeKind.Unspecified);
+ }
+
+ private static DateTimeStyles ValidateStyles(DateTimeStyles style, String parameterName)
+ {
+ if ((style & DateTimeFormatInfo.InvalidDateTimeStyles) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName);
+ }
+ if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0))
+ {
+ throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName);
+ }
+ if ((style & DateTimeStyles.NoCurrentDateDefault) != 0)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeOffsetInvalidDateTimeStyles, parameterName);
+ }
+
+ // RoundtripKind does not make sense for DateTimeOffset; ignore this flag for backward compatibility with DateTime
+ style &= ~DateTimeStyles.RoundtripKind;
+
+ // AssumeLocal is also ignored as that is what we do by default with DateTimeOffset.Parse
+ style &= ~DateTimeStyles.AssumeLocal;
+
+ return style;
+ }
+
+ // Operators
+
+ public static implicit operator DateTimeOffset(DateTime dateTime)
+ {
+ return new DateTimeOffset(dateTime);
+ }
+
+ public static DateTimeOffset operator +(DateTimeOffset dateTimeOffset, TimeSpan timeSpan)
+ {
+ return new DateTimeOffset(dateTimeOffset.ClockDateTime + timeSpan, dateTimeOffset.Offset);
+ }
+
+
+ public static DateTimeOffset operator -(DateTimeOffset dateTimeOffset, TimeSpan timeSpan)
+ {
+ return new DateTimeOffset(dateTimeOffset.ClockDateTime - timeSpan, dateTimeOffset.Offset);
+ }
+
+ public static TimeSpan operator -(DateTimeOffset left, DateTimeOffset right)
+ {
+ return left.UtcDateTime - right.UtcDateTime;
+ }
+
+ public static bool operator ==(DateTimeOffset left, DateTimeOffset right)
+ {
+ return left.UtcDateTime == right.UtcDateTime;
+ }
+
+ public static bool operator !=(DateTimeOffset left, DateTimeOffset right)
+ {
+ return left.UtcDateTime != right.UtcDateTime;
+ }
+
+ public static bool operator <(DateTimeOffset left, DateTimeOffset right)
+ {
+ return left.UtcDateTime < right.UtcDateTime;
+ }
+
+ public static bool operator <=(DateTimeOffset left, DateTimeOffset right)
+ {
+ return left.UtcDateTime <= right.UtcDateTime;
+ }
+
+ public static bool operator >(DateTimeOffset left, DateTimeOffset right)
+ {
+ return left.UtcDateTime > right.UtcDateTime;
+ }
+
+ public static bool operator >=(DateTimeOffset left, DateTimeOffset right)
+ {
+ return left.UtcDateTime >= right.UtcDateTime;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/DayOfWeek.cs b/src/System.Private.CoreLib/shared/System/DayOfWeek.cs
new file mode 100644
index 0000000000..f67d10e181
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DayOfWeek.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Enum for the day of the week.
+**
+**
+============================================================*/
+
+namespace System
+{
+ public enum DayOfWeek
+ {
+ Sunday = 0,
+ Monday = 1,
+ Tuesday = 2,
+ Wednesday = 3,
+ Thursday = 4,
+ Friday = 5,
+ Saturday = 6,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/DefaultBinder.cs b/src/System.Private.CoreLib/shared/System/DefaultBinder.cs
new file mode 100644
index 0000000000..f8d8c0c168
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DefaultBinder.cs
@@ -0,0 +1,1221 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Diagnostics;
+using CultureInfo = System.Globalization.CultureInfo;
+
+namespace System
+{
+#if CORERT
+ public sealed
+#else
+ internal
+#endif
+ partial class DefaultBinder : Binder
+ {
+ // This method is passed a set of methods and must choose the best
+ // fit. The methods all have the same number of arguments and the object
+ // array args. On exit, this method will choice the best fit method
+ // and coerce the args to match that method. By match, we mean all primitive
+ // arguments are exact matchs and all object arguments are exact or subclasses
+ // of the target. If the target OR is an interface, the object must implement
+ // that interface. There are a couple of exceptions
+ // thrown when a method cannot be returned. If no method matchs the args and
+ // ArgumentException is thrown. If multiple methods match the args then
+ // an AmbiguousMatchException is thrown.
+ //
+ // The most specific match will be selected.
+ //
+ public sealed override MethodBase BindToMethod(
+ BindingFlags bindingAttr, MethodBase[] match, ref object[] args,
+ ParameterModifier[] modifiers, CultureInfo cultureInfo, string[] names, out object state)
+ {
+ if (match == null || match.Length == 0)
+ throw new ArgumentException(SR.Arg_EmptyArray, nameof(match));
+
+ MethodBase[] candidates = (MethodBase[])match.Clone();
+
+ int i;
+ int j;
+
+ state = null;
+
+#region Map named parameters to candidate parameter positions
+ // We are creating an paramOrder array to act as a mapping
+ // between the order of the args and the actual order of the
+ // parameters in the method. This order may differ because
+ // named parameters (names) may change the order. If names
+ // is not provided, then we assume the default mapping (0,1,...)
+ int[][] paramOrder = new int[candidates.Length][];
+
+ for (i = 0; i < candidates.Length; i++)
+ {
+ ParameterInfo[] par = candidates[i].GetParametersNoCopy();
+
+ // args.Length + 1 takes into account the possibility of a last paramArray that can be omitted
+ paramOrder[i] = new int[(par.Length > args.Length) ? par.Length : args.Length];
+
+ if (names == null)
+ {
+ // Default mapping
+ for (j = 0; j < args.Length; j++)
+ paramOrder[i][j] = j;
+ }
+ else
+ {
+ // Named parameters, reorder the mapping. If CreateParamOrder fails, it means that the method
+ // doesn't have a name that matchs one of the named parameters so we don't consider it any further.
+ if (!CreateParamOrder(paramOrder[i], par, names))
+ candidates[i] = null;
+ }
+ }
+#endregion
+
+ Type[] paramArrayTypes = new Type[candidates.Length];
+
+ Type[] argTypes = new Type[args.Length];
+
+#region Cache the type of the provided arguments
+ // object that contain a null are treated as if they were typeless (but match either object
+ // references or value classes). We mark this condition by placing a null in the argTypes array.
+ for (i = 0; i < args.Length; i++)
+ {
+ if (args[i] != null)
+ {
+ argTypes[i] = args[i].GetType();
+ }
+ }
+#endregion
+
+
+ // Find the method that matches...
+ int CurIdx = 0;
+ bool defaultValueBinding = ((bindingAttr & BindingFlags.OptionalParamBinding) != 0);
+
+ Type paramArrayType = null;
+
+#region Filter methods by parameter count and type
+ for (i = 0; i < candidates.Length; i++)
+ {
+ paramArrayType = null;
+
+ // If we have named parameters then we may have a hole in the candidates array.
+ if (candidates[i] == null)
+ continue;
+
+ // Validate the parameters.
+ ParameterInfo[] par = candidates[i].GetParametersNoCopy();
+
+#region Match method by parameter count
+ if (par.Length == 0)
+ {
+#region No formal parameters
+ if (args.Length != 0)
+ {
+ if ((candidates[i].CallingConvention & CallingConventions.VarArgs) == 0)
+ continue;
+ }
+
+ // This is a valid routine so we move it up the candidates list.
+ paramOrder[CurIdx] = paramOrder[i];
+ candidates[CurIdx++] = candidates[i];
+
+ continue;
+#endregion
+ }
+ else if (par.Length > args.Length)
+ {
+#region Shortage of provided parameters
+ // If the number of parameters is greater than the number of args then
+ // we are in the situation were we may be using default values.
+ for (j = args.Length; j < par.Length - 1; j++)
+ {
+ if (par[j].DefaultValue == System.DBNull.Value)
+ break;
+ }
+
+ if (j != par.Length - 1)
+ continue;
+
+ if (par[j].DefaultValue == System.DBNull.Value)
+ {
+ if (!par[j].ParameterType.IsArray)
+ continue;
+
+ if (!par[j].IsDefined(typeof(ParamArrayAttribute), true))
+ continue;
+
+ paramArrayType = par[j].ParameterType.GetElementType();
+ }
+#endregion
+ }
+ else if (par.Length < args.Length)
+ {
+#region Excess provided parameters
+ // test for the ParamArray case
+ int lastArgPos = par.Length - 1;
+
+ if (!par[lastArgPos].ParameterType.IsArray)
+ continue;
+
+ if (!par[lastArgPos].IsDefined(typeof(ParamArrayAttribute), true))
+ continue;
+
+ if (paramOrder[i][lastArgPos] != lastArgPos)
+ continue;
+
+ paramArrayType = par[lastArgPos].ParameterType.GetElementType();
+#endregion
+ }
+ else
+ {
+#region Test for paramArray, save paramArray type
+ int lastArgPos = par.Length - 1;
+
+ if (par[lastArgPos].ParameterType.IsArray
+ && par[lastArgPos].IsDefined(typeof(ParamArrayAttribute), true)
+ && paramOrder[i][lastArgPos] == lastArgPos)
+ {
+ if (!par[lastArgPos].ParameterType.IsAssignableFrom(argTypes[lastArgPos]))
+ paramArrayType = par[lastArgPos].ParameterType.GetElementType();
+ }
+#endregion
+ }
+#endregion
+
+ Type pCls = null;
+ int argsToCheck = (paramArrayType != null) ? par.Length - 1 : args.Length;
+
+#region Match method by parameter type
+ for (j = 0; j < argsToCheck; j++)
+ {
+#region Classic argument coersion checks
+ // get the formal type
+ pCls = par[j].ParameterType;
+
+ if (pCls.IsByRef)
+ pCls = pCls.GetElementType();
+
+ // the type is the same
+ if (pCls == argTypes[paramOrder[i][j]])
+ continue;
+
+ // a default value is available
+ if (defaultValueBinding && args[paramOrder[i][j]] == Type.Missing)
+ continue;
+
+ // the argument was null, so it matches with everything
+ if (args[paramOrder[i][j]] == null)
+ continue;
+
+ // the type is Object, so it will match everything
+ if (pCls == typeof(object))
+ continue;
+
+ // now do a "classic" type check
+ if (pCls.IsPrimitive)
+ {
+ if (argTypes[paramOrder[i][j]] == null || !CanChangePrimitiveObjectToType(args[paramOrder[i][j]], pCls))
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (argTypes[paramOrder[i][j]] == null)
+ continue;
+
+ if (!pCls.IsAssignableFrom(argTypes[paramOrder[i][j]]))
+ {
+ if (argTypes[paramOrder[i][j]].IsCOMObject)
+ {
+ if (pCls.IsInstanceOfType(args[paramOrder[i][j]]))
+ continue;
+ }
+ break;
+ }
+ }
+#endregion
+ }
+
+ if (paramArrayType != null && j == par.Length - 1)
+ {
+#region Check that excess arguments can be placed in the param array
+ for (; j < args.Length; j++)
+ {
+ if (paramArrayType.IsPrimitive)
+ {
+ if (argTypes[j] == null || !CanChangePrimitiveObjectToType(args[j], paramArrayType))
+ break;
+ }
+ else
+ {
+ if (argTypes[j] == null)
+ continue;
+
+ if (!paramArrayType.IsAssignableFrom(argTypes[j]))
+ {
+ if (argTypes[j].IsCOMObject)
+ {
+ if (paramArrayType.IsInstanceOfType(args[j]))
+ continue;
+ }
+
+ break;
+ }
+ }
+ }
+#endregion
+ }
+#endregion
+
+ if (j == args.Length)
+ {
+#region This is a valid routine so we move it up the candidates list
+ paramOrder[CurIdx] = paramOrder[i];
+ paramArrayTypes[CurIdx] = paramArrayType;
+ candidates[CurIdx++] = candidates[i];
+#endregion
+ }
+ }
+#endregion
+
+ // If we didn't find a method
+ if (CurIdx == 0)
+ throw new MissingMethodException(SR.MissingMember);
+
+ if (CurIdx == 1)
+ {
+#region Found only one method
+ if (names != null)
+ {
+ state = new BinderState((int[])paramOrder[0].Clone(), args.Length, paramArrayTypes[0] != null);
+ ReorderParams(paramOrder[0], args);
+ }
+
+ // If the parameters and the args are not the same length or there is a paramArray
+ // then we need to create a argument array.
+ ParameterInfo[] parms = candidates[0].GetParametersNoCopy();
+
+ if (parms.Length == args.Length)
+ {
+ if (paramArrayTypes[0] != null)
+ {
+ object[] objs = new object[parms.Length];
+ int lastPos = parms.Length - 1;
+ Array.Copy(args, 0, objs, 0, lastPos);
+ objs[lastPos] = Array.CreateInstance(paramArrayTypes[0], 1);
+ ((Array)objs[lastPos]).SetValue(args[lastPos], 0);
+ args = objs;
+ }
+ }
+ else if (parms.Length > args.Length)
+ {
+ object[] objs = new object[parms.Length];
+
+ for (i = 0; i < args.Length; i++)
+ objs[i] = args[i];
+
+ for (; i < parms.Length - 1; i++)
+ objs[i] = parms[i].DefaultValue;
+
+ if (paramArrayTypes[0] != null)
+ objs[i] = Array.CreateInstance(paramArrayTypes[0], 0); // create an empty array for the
+
+ else
+ objs[i] = parms[i].DefaultValue;
+
+ args = objs;
+ }
+ else
+ {
+ if ((candidates[0].CallingConvention & CallingConventions.VarArgs) == 0)
+ {
+ object[] objs = new object[parms.Length];
+ int paramArrayPos = parms.Length - 1;
+ Array.Copy(args, 0, objs, 0, paramArrayPos);
+ objs[paramArrayPos] = Array.CreateInstance(paramArrayTypes[0], args.Length - paramArrayPos);
+ Array.Copy(args, paramArrayPos, (System.Array)objs[paramArrayPos], 0, args.Length - paramArrayPos);
+ args = objs;
+ }
+ }
+#endregion
+
+ return candidates[0];
+ }
+
+ int currentMin = 0;
+ bool ambig = false;
+ for (i = 1; i < CurIdx; i++)
+ {
+#region Walk all of the methods looking the most specific method to invoke
+ int newMin = FindMostSpecificMethod(candidates[currentMin], paramOrder[currentMin], paramArrayTypes[currentMin],
+ candidates[i], paramOrder[i], paramArrayTypes[i], argTypes, args);
+
+ if (newMin == 0)
+ {
+ ambig = true;
+ }
+ else if (newMin == 2)
+ {
+ currentMin = i;
+ ambig = false;
+ }
+#endregion
+ }
+
+ if (ambig)
+ throw new AmbiguousMatchException(SR.Arg_AmbiguousMatchException);
+
+ // Reorder (if needed)
+ if (names != null)
+ {
+ state = new BinderState((int[])paramOrder[currentMin].Clone(), args.Length, paramArrayTypes[currentMin] != null);
+ ReorderParams(paramOrder[currentMin], args);
+ }
+
+ // If the parameters and the args are not the same length or there is a paramArray
+ // then we need to create a argument array.
+ ParameterInfo[] parameters = candidates[currentMin].GetParametersNoCopy();
+ if (parameters.Length == args.Length)
+ {
+ if (paramArrayTypes[currentMin] != null)
+ {
+ object[] objs = new object[parameters.Length];
+ int lastPos = parameters.Length - 1;
+ Array.Copy(args, 0, objs, 0, lastPos);
+ objs[lastPos] = Array.CreateInstance(paramArrayTypes[currentMin], 1);
+ ((Array)objs[lastPos]).SetValue(args[lastPos], 0);
+ args = objs;
+ }
+ }
+ else if (parameters.Length > args.Length)
+ {
+ object[] objs = new object[parameters.Length];
+
+ for (i = 0; i < args.Length; i++)
+ objs[i] = args[i];
+
+ for (; i < parameters.Length - 1; i++)
+ objs[i] = parameters[i].DefaultValue;
+
+ if (paramArrayTypes[currentMin] != null)
+ {
+ objs[i] = Array.CreateInstance(paramArrayTypes[currentMin], 0);
+ }
+ else
+ {
+ objs[i] = parameters[i].DefaultValue;
+ }
+
+ args = objs;
+ }
+ else
+ {
+ if ((candidates[currentMin].CallingConvention & CallingConventions.VarArgs) == 0)
+ {
+ object[] objs = new object[parameters.Length];
+ int paramArrayPos = parameters.Length - 1;
+ Array.Copy(args, 0, objs, 0, paramArrayPos);
+ objs[paramArrayPos] = Array.CreateInstance(paramArrayTypes[currentMin], args.Length - paramArrayPos);
+ Array.Copy(args, paramArrayPos, (System.Array)objs[paramArrayPos], 0, args.Length - paramArrayPos);
+ args = objs;
+ }
+ }
+
+ return candidates[currentMin];
+ }
+
+
+ // Given a set of fields that match the base criteria, select a field.
+ // if value is null then we have no way to select a field
+ public sealed override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, CultureInfo cultureInfo)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException(nameof(match));
+ }
+
+ int i;
+ // Find the method that match...
+ int CurIdx = 0;
+
+ Type valueType = null;
+
+ FieldInfo[] candidates = (FieldInfo[])match.Clone();
+
+ // If we are a FieldSet, then use the value's type to disambiguate
+ if ((bindingAttr & BindingFlags.SetField) != 0)
+ {
+ valueType = value.GetType();
+
+ for (i = 0; i < candidates.Length; i++)
+ {
+ Type pCls = candidates[i].FieldType;
+ if (pCls == valueType)
+ {
+ candidates[CurIdx++] = candidates[i];
+ continue;
+ }
+ if (value == Empty.Value)
+ {
+ // the object passed in was null which would match any non primitive non value type
+ if (pCls.IsClass)
+ {
+ candidates[CurIdx++] = candidates[i];
+ continue;
+ }
+ }
+ if (pCls == typeof(object))
+ {
+ candidates[CurIdx++] = candidates[i];
+ continue;
+ }
+ if (pCls.IsPrimitive)
+ {
+ if (CanChangePrimitiveObjectToType(value, pCls))
+ {
+ candidates[CurIdx++] = candidates[i];
+ continue;
+ }
+ }
+ else
+ {
+ if (pCls.IsAssignableFrom(valueType))
+ {
+ candidates[CurIdx++] = candidates[i];
+ continue;
+ }
+ }
+ }
+ if (CurIdx == 0)
+ throw new MissingFieldException(SR.MissingField);
+ if (CurIdx == 1)
+ return candidates[0];
+ }
+
+ // Walk all of the methods looking the most specific method to invoke
+ int currentMin = 0;
+ bool ambig = false;
+ for (i = 1; i < CurIdx; i++)
+ {
+ int newMin = FindMostSpecificField(candidates[currentMin], candidates[i]);
+ if (newMin == 0)
+ ambig = true;
+ else
+ {
+ if (newMin == 2)
+ {
+ currentMin = i;
+ ambig = false;
+ }
+ }
+ }
+ if (ambig)
+ throw new AmbiguousMatchException(SR.Arg_AmbiguousMatchException);
+ return candidates[currentMin];
+ }
+
+ // Given a set of methods that match the base criteria, select a method based
+ // upon an array of types. This method should return null if no method matchs
+ // the criteria.
+ public sealed override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers)
+ {
+ int i;
+ int j;
+
+ Type[] realTypes = new Type[types.Length];
+ for (i = 0; i < types.Length; i++)
+ {
+ realTypes[i] = types[i].UnderlyingSystemType;
+ if (!(realTypes[i].IsRuntimeImplemented() || realTypes[i] is SignatureType))
+ throw new ArgumentException(SR.Arg_MustBeType, nameof(types));
+ }
+ types = realTypes;
+
+ // We don't automatically jump out on exact match.
+ if (match == null || match.Length == 0)
+ throw new ArgumentException(SR.Arg_EmptyArray, nameof(match));
+
+ MethodBase[] candidates = (MethodBase[])match.Clone();
+
+ // Find all the methods that can be described by the types parameter.
+ // Remove all of them that cannot.
+ int CurIdx = 0;
+ for (i = 0; i < candidates.Length; i++)
+ {
+ ParameterInfo[] par = candidates[i].GetParametersNoCopy();
+ if (par.Length != types.Length)
+ continue;
+ for (j = 0; j < types.Length; j++)
+ {
+ Type pCls = par[j].ParameterType;
+ if (types[j].MatchesParameterTypeExactly(par[j]))
+ continue;
+ if (pCls == typeof(object))
+ continue;
+
+ Type type = types[j];
+ if (type is SignatureType signatureType)
+ {
+ if (!(candidates[i] is MethodInfo methodInfo))
+ break;
+ type = signatureType.TryResolveAgainstGenericMethod(methodInfo);
+ if (type == null)
+ break;
+ }
+
+ if (pCls.IsPrimitive)
+ {
+ if (!(type.UnderlyingSystemType.IsRuntimeImplemented()) ||
+ !CanChangePrimitive(type.UnderlyingSystemType, pCls.UnderlyingSystemType))
+ break;
+ }
+ else
+ {
+ if (!pCls.IsAssignableFrom(type))
+ break;
+ }
+ }
+ if (j == types.Length)
+ candidates[CurIdx++] = candidates[i];
+ }
+ if (CurIdx == 0)
+ return null;
+ if (CurIdx == 1)
+ return candidates[0];
+
+ // Walk all of the methods looking the most specific method to invoke
+ int currentMin = 0;
+ bool ambig = false;
+ int[] paramOrder = new int[types.Length];
+ for (i = 0; i < types.Length; i++)
+ paramOrder[i] = i;
+ for (i = 1; i < CurIdx; i++)
+ {
+ int newMin = FindMostSpecificMethod(candidates[currentMin], paramOrder, null, candidates[i], paramOrder, null, types, null);
+ if (newMin == 0)
+ ambig = true;
+ else
+ {
+ if (newMin == 2)
+ {
+ currentMin = i;
+ ambig = false;
+ currentMin = i;
+ }
+ }
+ }
+ if (ambig)
+ throw new AmbiguousMatchException(SR.Arg_AmbiguousMatchException);
+ return candidates[currentMin];
+ }
+
+ // Given a set of properties that match the base criteria, select one.
+ public sealed override PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType,
+ Type[] indexes, ParameterModifier[] modifiers)
+ {
+ // Allow a null indexes array. But if it is not null, every element must be non-null as well.
+ if (indexes != null)
+ {
+ foreach (Type index in indexes)
+ {
+ if (index == null)
+ throw new ArgumentNullException(nameof(indexes));
+ }
+ }
+
+ if (match == null || match.Length == 0)
+ throw new ArgumentException(SR.Arg_EmptyArray, nameof(match));
+
+ PropertyInfo[] candidates = (PropertyInfo[])match.Clone();
+
+ int i, j = 0;
+
+ // Find all the properties that can be described by type indexes parameter
+ int CurIdx = 0;
+ int indexesLength = (indexes != null) ? indexes.Length : 0;
+ for (i = 0; i < candidates.Length; i++)
+ {
+ if (indexes != null)
+ {
+ ParameterInfo[] par = candidates[i].GetIndexParameters();
+ if (par.Length != indexesLength)
+ continue;
+
+ for (j = 0; j < indexesLength; j++)
+ {
+ Type pCls = par[j].ParameterType;
+
+ // If the classes exactly match continue
+ if (pCls == indexes[j])
+ continue;
+ if (pCls == typeof(object))
+ continue;
+
+ if (pCls.IsPrimitive)
+ {
+ if (!(indexes[j].UnderlyingSystemType.IsRuntimeImplemented()) ||
+ !CanChangePrimitive(indexes[j].UnderlyingSystemType, pCls.UnderlyingSystemType))
+ break;
+ }
+ else
+ {
+ if (!pCls.IsAssignableFrom(indexes[j]))
+ break;
+ }
+ }
+ }
+
+ if (j == indexesLength)
+ {
+ if (returnType != null)
+ {
+ if (candidates[i].PropertyType.IsPrimitive)
+ {
+ if (!(returnType.UnderlyingSystemType.IsRuntimeImplemented()) ||
+ !CanChangePrimitive(returnType.UnderlyingSystemType, candidates[i].PropertyType.UnderlyingSystemType))
+ continue;
+ }
+ else
+ {
+ if (!candidates[i].PropertyType.IsAssignableFrom(returnType))
+ continue;
+ }
+ }
+ candidates[CurIdx++] = candidates[i];
+ }
+ }
+ if (CurIdx == 0)
+ return null;
+ if (CurIdx == 1)
+ return candidates[0];
+
+ // Walk all of the properties looking the most specific method to invoke
+ int currentMin = 0;
+ bool ambig = false;
+ int[] paramOrder = new int[indexesLength];
+ for (i = 0; i < indexesLength; i++)
+ paramOrder[i] = i;
+ for (i = 1; i < CurIdx; i++)
+ {
+ int newMin = FindMostSpecificType(candidates[currentMin].PropertyType, candidates[i].PropertyType, returnType);
+ if (newMin == 0 && indexes != null)
+ newMin = FindMostSpecific(candidates[currentMin].GetIndexParameters(),
+ paramOrder,
+ null,
+ candidates[i].GetIndexParameters(),
+ paramOrder,
+ null,
+ indexes,
+ null);
+ if (newMin == 0)
+ {
+ newMin = FindMostSpecificProperty(candidates[currentMin], candidates[i]);
+ if (newMin == 0)
+ ambig = true;
+ }
+ if (newMin == 2)
+ {
+ ambig = false;
+ currentMin = i;
+ }
+ }
+
+ if (ambig)
+ throw new AmbiguousMatchException(SR.Arg_AmbiguousMatchException);
+ return candidates[currentMin];
+ }
+
+ // ChangeType
+ // The default binder doesn't support any change type functionality.
+ // This is because the default is built into the low level invoke code.
+ public override object ChangeType(object value, Type type, CultureInfo cultureInfo)
+ {
+ throw new NotSupportedException(SR.NotSupported_ChangeType);
+ }
+
+ public sealed override void ReorderArgumentArray(ref object[] args, object state)
+ {
+ BinderState binderState = (BinderState)state;
+ ReorderParams(binderState._argsMap, args);
+ if (binderState._isParamArray)
+ {
+ int paramArrayPos = args.Length - 1;
+ if (args.Length == binderState._originalSize)
+ args[paramArrayPos] = ((object[])args[paramArrayPos])[0];
+ else
+ {
+ // must be args.Length < state.originalSize
+ object[] newArgs = new object[args.Length];
+ Array.Copy(args, 0, newArgs, 0, paramArrayPos);
+ for (int i = paramArrayPos, j = 0; i < newArgs.Length; i++, j++)
+ {
+ newArgs[i] = ((object[])args[paramArrayPos])[j];
+ }
+ args = newArgs;
+ }
+ }
+ else
+ {
+ if (args.Length > binderState._originalSize)
+ {
+ object[] newArgs = new object[binderState._originalSize];
+ Array.Copy(args, 0, newArgs, 0, binderState._originalSize);
+ args = newArgs;
+ }
+ }
+ }
+
+ // Return any exact bindings that may exist. (This method is not defined on the
+ // Binder and is used by RuntimeType.)
+ public static MethodBase ExactBinding(MethodBase[] match, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (match == null)
+ throw new ArgumentNullException(nameof(match));
+
+ MethodBase[] aExactMatches = new MethodBase[match.Length];
+ int cExactMatches = 0;
+
+ for (int i = 0; i < match.Length; i++)
+ {
+ ParameterInfo[] par = match[i].GetParametersNoCopy();
+ if (par.Length == 0)
+ {
+ continue;
+ }
+ int j;
+ for (j = 0; j < types.Length; j++)
+ {
+ Type pCls = par[j].ParameterType;
+
+ // If the classes exactly match continue
+ if (!pCls.Equals(types[j]))
+ break;
+ }
+ if (j < types.Length)
+ continue;
+
+ // Add the exact match to the array of exact matches.
+ aExactMatches[cExactMatches] = match[i];
+ cExactMatches++;
+ }
+
+ if (cExactMatches == 0)
+ return null;
+
+ if (cExactMatches == 1)
+ return aExactMatches[0];
+
+ return FindMostDerivedNewSlotMeth(aExactMatches, cExactMatches);
+ }
+
+ // Return any exact bindings that may exist. (This method is not defined on the
+ // Binder and is used by RuntimeType.)
+ public static PropertyInfo ExactPropertyBinding(PropertyInfo[] match, Type returnType, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (match == null)
+ throw new ArgumentNullException(nameof(match));
+
+ PropertyInfo bestMatch = null;
+ int typesLength = (types != null) ? types.Length : 0;
+ for (int i = 0; i < match.Length; i++)
+ {
+ ParameterInfo[] par = match[i].GetIndexParameters();
+ int j;
+ for (j = 0; j < typesLength; j++)
+ {
+ Type pCls = par[j].ParameterType;
+
+ // If the classes exactly match continue
+ if (pCls != types[j])
+ break;
+ }
+ if (j < typesLength)
+ continue;
+ if (returnType != null && returnType != match[i].PropertyType)
+ continue;
+
+ if (bestMatch != null)
+ throw new AmbiguousMatchException(SR.Arg_AmbiguousMatchException);
+
+ bestMatch = match[i];
+ }
+ return bestMatch;
+ }
+
+ private static int FindMostSpecific(ParameterInfo[] p1, int[] paramOrder1, Type paramArrayType1,
+ ParameterInfo[] p2, int[] paramOrder2, Type paramArrayType2,
+ Type[] types, object[] args)
+ {
+ // A method using params is always less specific than one not using params
+ if (paramArrayType1 != null && paramArrayType2 == null) return 2;
+ if (paramArrayType2 != null && paramArrayType1 == null) return 1;
+
+ // now either p1 and p2 both use params or neither does.
+
+ bool p1Less = false;
+ bool p2Less = false;
+
+ for (int i = 0; i < types.Length; i++)
+ {
+ if (args != null && args[i] == Type.Missing)
+ continue;
+
+ Type c1, c2;
+
+ // If a param array is present, then either
+ // the user re-ordered the parameters in which case
+ // the argument to the param array is either an array
+ // in which case the params is conceptually ignored and so paramArrayType1 == null
+ // or the argument to the param array is a single element
+ // in which case paramOrder[i] == p1.Length - 1 for that element
+ // or the user did not re-order the parameters in which case
+ // the paramOrder array could contain indexes larger than p.Length - 1 (see VSW 577286)
+ // so any index >= p.Length - 1 is being put in the param array
+
+ if (paramArrayType1 != null && paramOrder1[i] >= p1.Length - 1)
+ c1 = paramArrayType1;
+ else
+ c1 = p1[paramOrder1[i]].ParameterType;
+
+ if (paramArrayType2 != null && paramOrder2[i] >= p2.Length - 1)
+ c2 = paramArrayType2;
+ else
+ c2 = p2[paramOrder2[i]].ParameterType;
+
+ if (c1 == c2) continue;
+
+ switch (FindMostSpecificType(c1, c2, types[i]))
+ {
+ case 0: return 0;
+ case 1: p1Less = true; break;
+ case 2: p2Less = true; break;
+ }
+ }
+
+ // Two way p1Less and p2Less can be equal. All the arguments are the
+ // same they both equal false, otherwise there were things that both
+ // were the most specific type on....
+ if (p1Less == p2Less)
+ {
+ // if we cannot tell which is a better match based on parameter types (p1Less == p2Less),
+ // let's see which one has the most matches without using the params array (the longer one wins).
+ if (!p1Less && args != null)
+ {
+ if (p1.Length > p2.Length)
+ {
+ return 1;
+ }
+ else if (p2.Length > p1.Length)
+ {
+ return 2;
+ }
+ }
+
+ return 0;
+ }
+ else
+ {
+ return (p1Less == true) ? 1 : 2;
+ }
+ }
+
+ private static int FindMostSpecificType(Type c1, Type c2, Type t)
+ {
+ // If the two types are exact move on...
+ if (c1 == c2)
+ return 0;
+
+ if (t is SignatureType signatureType)
+ {
+ if (signatureType.MatchesExactly(c1))
+ return 1;
+
+ if (signatureType.MatchesExactly(c2))
+ return 2;
+ }
+ else
+ {
+ if (c1 == t)
+ return 1;
+
+ if (c2 == t)
+ return 2;
+ }
+
+ bool c1FromC2;
+ bool c2FromC1;
+
+ if (c1.IsByRef || c2.IsByRef)
+ {
+ if (c1.IsByRef && c2.IsByRef)
+ {
+ c1 = c1.GetElementType();
+ c2 = c2.GetElementType();
+ }
+ else if (c1.IsByRef)
+ {
+ if (c1.GetElementType() == c2)
+ return 2;
+
+ c1 = c1.GetElementType();
+ }
+ else
+ {
+ if (c2.GetElementType() == c1)
+ return 1;
+
+ c2 = c2.GetElementType();
+ }
+ }
+
+
+ if (c1.IsPrimitive && c2.IsPrimitive)
+ {
+ c1FromC2 = CanChangePrimitive(c2, c1);
+ c2FromC1 = CanChangePrimitive(c1, c2);
+ }
+ else
+ {
+ c1FromC2 = c1.IsAssignableFrom(c2);
+ c2FromC1 = c2.IsAssignableFrom(c1);
+ }
+
+ if (c1FromC2 == c2FromC1)
+ return 0;
+
+ if (c1FromC2)
+ {
+ return 2;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ private static int FindMostSpecificMethod(MethodBase m1, int[] paramOrder1, Type paramArrayType1,
+ MethodBase m2, int[] paramOrder2, Type paramArrayType2,
+ Type[] types, object[] args)
+ {
+ // Find the most specific method based on the parameters.
+ int res = FindMostSpecific(m1.GetParametersNoCopy(), paramOrder1, paramArrayType1,
+ m2.GetParametersNoCopy(), paramOrder2, paramArrayType2, types, args);
+
+ // If the match was not ambigous then return the result.
+ if (res != 0)
+ return res;
+
+ // Check to see if the methods have the exact same name and signature.
+ if (CompareMethodSig(m1, m2))
+ {
+ // Determine the depth of the declaring types for both methods.
+ int hierarchyDepth1 = GetHierarchyDepth(m1.DeclaringType);
+ int hierarchyDepth2 = GetHierarchyDepth(m2.DeclaringType);
+
+ // The most derived method is the most specific one.
+ if (hierarchyDepth1 == hierarchyDepth2)
+ {
+ return 0;
+ }
+ else if (hierarchyDepth1 < hierarchyDepth2)
+ {
+ return 2;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ // The match is ambigous.
+ return 0;
+ }
+
+ private static int FindMostSpecificField(FieldInfo cur1, FieldInfo cur2)
+ {
+ // Check to see if the fields have the same name.
+ if (cur1.Name == cur2.Name)
+ {
+ int hierarchyDepth1 = GetHierarchyDepth(cur1.DeclaringType);
+ int hierarchyDepth2 = GetHierarchyDepth(cur2.DeclaringType);
+
+ if (hierarchyDepth1 == hierarchyDepth2)
+ {
+ Debug.Assert(cur1.IsStatic != cur2.IsStatic, "hierarchyDepth1 == hierarchyDepth2");
+ return 0;
+ }
+ else if (hierarchyDepth1 < hierarchyDepth2)
+ return 2;
+ else
+ return 1;
+ }
+
+ // The match is ambigous.
+ return 0;
+ }
+
+ private static int FindMostSpecificProperty(PropertyInfo cur1, PropertyInfo cur2)
+ {
+ // Check to see if the fields have the same name.
+ if (cur1.Name == cur2.Name)
+ {
+ int hierarchyDepth1 = GetHierarchyDepth(cur1.DeclaringType);
+ int hierarchyDepth2 = GetHierarchyDepth(cur2.DeclaringType);
+
+ if (hierarchyDepth1 == hierarchyDepth2)
+ {
+ return 0;
+ }
+ else if (hierarchyDepth1 < hierarchyDepth2)
+ return 2;
+ else
+ return 1;
+ }
+
+ // The match is ambigous.
+ return 0;
+ }
+
+ public static bool CompareMethodSig(MethodBase m1, MethodBase m2)
+ {
+ ParameterInfo[] params1 = m1.GetParametersNoCopy();
+ ParameterInfo[] params2 = m2.GetParametersNoCopy();
+
+ if (params1.Length != params2.Length)
+ return false;
+
+ int numParams = params1.Length;
+ for (int i = 0; i < numParams; i++)
+ {
+ if (params1[i].ParameterType != params2[i].ParameterType)
+ return false;
+ }
+
+ return true;
+ }
+
+ private static int GetHierarchyDepth(Type t)
+ {
+ int depth = 0;
+
+ Type currentType = t;
+ do
+ {
+ depth++;
+ currentType = currentType.BaseType;
+ } while (currentType != null);
+
+ return depth;
+ }
+
+ internal static MethodBase FindMostDerivedNewSlotMeth(MethodBase[] match, int cMatches)
+ {
+ int deepestHierarchy = 0;
+ MethodBase methWithDeepestHierarchy = null;
+
+ for (int i = 0; i < cMatches; i++)
+ {
+ // Calculate the depth of the hierarchy of the declaring type of the
+ // current method.
+ int currentHierarchyDepth = GetHierarchyDepth(match[i].DeclaringType);
+
+ // The two methods have the same name, signature, and hierarchy depth.
+ // This can only happen if at least one is vararg or generic.
+ if (currentHierarchyDepth == deepestHierarchy)
+ {
+ throw new AmbiguousMatchException(SR.Arg_AmbiguousMatchException);
+ }
+
+ // Check to see if this method is on the most derived class.
+ if (currentHierarchyDepth > deepestHierarchy)
+ {
+ deepestHierarchy = currentHierarchyDepth;
+ methWithDeepestHierarchy = match[i];
+ }
+ }
+
+ return methWithDeepestHierarchy;
+ }
+
+ // This method will sort the vars array into the mapping order stored
+ // in the paramOrder array.
+ private static void ReorderParams(int[] paramOrder, object[] vars)
+ {
+ object[] varsCopy = new object[vars.Length];
+ for (int i = 0; i < vars.Length; i++)
+ varsCopy[i] = vars[i];
+
+ for (int i = 0; i < vars.Length; i++)
+ vars[i] = varsCopy[paramOrder[i]];
+ }
+
+ // This method will create the mapping between the Parameters and the underlying
+ // data based upon the names array. The names array is stored in the same order
+ // as the values and maps to the parameters of the method. We store the mapping
+ // from the parameters to the names in the paramOrder array. All parameters that
+ // don't have matching names are then stored in the array in order.
+ private static bool CreateParamOrder(int[] paramOrder, ParameterInfo[] pars, string[] names)
+ {
+ bool[] used = new bool[pars.Length];
+
+ // Mark which parameters have not been found in the names list
+ for (int i = 0; i < pars.Length; i++)
+ paramOrder[i] = -1;
+ // Find the parameters with names.
+ for (int i = 0; i < names.Length; i++)
+ {
+ int j;
+ for (j = 0; j < pars.Length; j++)
+ {
+ if (names[i].Equals(pars[j].Name))
+ {
+ paramOrder[j] = i;
+ used[i] = true;
+ break;
+ }
+ }
+ // This is an error condition. The name was not found. This
+ // method must not match what we sent.
+ if (j == pars.Length)
+ return false;
+ }
+
+ // Now we fill in the holes with the parameters that are unused.
+ int pos = 0;
+ for (int i = 0; i < pars.Length; i++)
+ {
+ if (paramOrder[i] == -1)
+ {
+ for (; pos < pars.Length; pos++)
+ {
+ if (!used[pos])
+ {
+ paramOrder[i] = pos;
+ pos++;
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ internal class BinderState
+ {
+ internal readonly int[] _argsMap;
+ internal readonly int _originalSize;
+ internal readonly bool _isParamArray;
+
+ internal BinderState(int[] argsMap, int originalSize, bool isParamArray)
+ {
+ _argsMap = argsMap;
+ _originalSize = originalSize;
+ _isParamArray = isParamArray;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/CodeAnalysis/SuppressMessageAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/CodeAnalysis/SuppressMessageAttribute.cs
new file mode 100644
index 0000000000..893d7b8bc9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/CodeAnalysis/SuppressMessageAttribute.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** An attribute to suppress violation messages/warnings
+** by static code analysis tools.
+**
+**
+===========================================================*/
+
+namespace System.Diagnostics.CodeAnalysis
+{
+ [AttributeUsage(
+ AttributeTargets.All,
+ Inherited = false,
+ AllowMultiple = true
+ )
+ ]
+ [Conditional("CODE_ANALYSIS")]
+ public sealed class SuppressMessageAttribute : Attribute
+ {
+ public SuppressMessageAttribute(string category, string checkId)
+ {
+ Category = category;
+ CheckId = checkId;
+ }
+
+ public string Category { get; }
+ public string CheckId { get; }
+ public string Scope { get; set; }
+ public string Target { get; set; }
+ public string MessageId { get; set; }
+ public string Justification { get; set; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/ConditionalAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/ConditionalAttribute.cs
new file mode 100644
index 0000000000..416625b779
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/ConditionalAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
+ public sealed class ConditionalAttribute : Attribute
+ {
+ public ConditionalAttribute(string conditionString)
+ {
+ ConditionString = conditionString;
+ }
+
+ public string ConditionString { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs
new file mode 100644
index 0000000000..627ea4ab7e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.Unix.cs
@@ -0,0 +1,107 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+
+namespace System.Diagnostics
+{
+ public static partial class Debug
+ {
+ private static readonly bool s_shouldWriteToStdErr = Environment.GetEnvironmentVariable("COMPlus_DebugWriteToStdErr") == "1";
+
+ private static void ShowDialog(string stackTrace, string message, string detailMessage, string errorSource)
+ {
+ if (Debugger.IsAttached)
+ {
+ Debugger.Break();
+ }
+ else
+ {
+ // In Core, we do not show a dialog.
+ // Fail in order to avoid anyone catching an exception and masking
+ // an assert failure.
+ DebugAssertException ex;
+ if (message == String.Empty)
+ {
+ ex = new DebugAssertException(stackTrace);
+ }
+ else if (detailMessage == String.Empty)
+ {
+ ex = new DebugAssertException(message, stackTrace);
+ }
+ else
+ {
+ ex = new DebugAssertException(message, detailMessage, stackTrace);
+ }
+ Environment.FailFast(ex.Message, ex, errorSource);
+ }
+ }
+
+ private static void WriteCore(string message)
+ {
+ WriteToDebugger(message);
+
+ if (s_shouldWriteToStdErr)
+ {
+ WriteToStderr(message);
+ }
+ }
+
+ private static void WriteToDebugger(string message)
+ {
+ if (Debugger.IsLogging())
+ {
+ Debugger.Log(0, null, message);
+ }
+ else
+ {
+ Interop.Sys.SysLog(Interop.Sys.SysLogPriority.LOG_USER | Interop.Sys.SysLogPriority.LOG_DEBUG, "%s", message);
+ }
+ }
+
+ private static void WriteToStderr(string message)
+ {
+ // We don't want to write UTF-16 to a file like standard error. Ideally we would transcode this
+ // to UTF8, but the downside of that is it pulls in a bunch of stuff into what is ideally
+ // a path with minimal dependencies (as to prevent re-entrency), so we'll take the strategy
+ // of just throwing away any non ASCII characters from the message and writing the rest
+
+ const int BufferLength = 256;
+
+ unsafe
+ {
+ byte* buf = stackalloc byte[BufferLength];
+ int bufCount;
+ int i = 0;
+
+ while (i < message.Length)
+ {
+ for (bufCount = 0; bufCount < BufferLength && i < message.Length; i++)
+ {
+ if (message[i] <= 0x7F)
+ {
+ buf[bufCount] = (byte)message[i];
+ bufCount++;
+ }
+ }
+
+ int totalBytesWritten = 0;
+ while (bufCount > 0)
+ {
+ int bytesWritten = Interop.Sys.Write(2 /* stderr */, buf + totalBytesWritten, bufCount);
+ if (bytesWritten < 0)
+ {
+ // On error, simply stop writing the debug output. This could commonly happen if stderr
+ // was piped to a program that ended before this program did, resulting in EPIPE errors.
+ return;
+ }
+
+ bufCount -= bytesWritten;
+ totalBytesWritten += bytesWritten;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs
new file mode 100644
index 0000000000..7bc43ccfcb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Debug.cs
@@ -0,0 +1,350 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Do not remove this, it is needed to retain calls to these conditional methods in release builds
+#define DEBUG
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+
+namespace System.Diagnostics
+{
+ /// <summary>
+ /// Provides a set of properties and methods for debugging code.
+ /// </summary>
+ public static partial class Debug
+ {
+ private static readonly object s_lock = new object();
+
+ public static bool AutoFlush { get { return true; } set { } }
+
+ [ThreadStatic]
+ private static int s_indentLevel;
+ public static int IndentLevel
+ {
+ get
+ {
+ return s_indentLevel;
+ }
+ set
+ {
+ s_indentLevel = value < 0 ? 0 : value;
+ }
+ }
+
+ private static int s_indentSize = 4;
+ public static int IndentSize
+ {
+ get
+ {
+ return s_indentSize;
+ }
+ set
+ {
+ s_indentSize = value < 0 ? 0 : value;
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Close() { }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Flush() { }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Indent()
+ {
+ IndentLevel++;
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Unindent()
+ {
+ IndentLevel--;
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Print(string message)
+ {
+ Write(message);
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Print(string format, params object[] args)
+ {
+ Write(string.Format(null, format, args));
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Assert(bool condition)
+ {
+ Assert(condition, string.Empty, string.Empty);
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Assert(bool condition, string message)
+ {
+ Assert(condition, message, string.Empty);
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Assert(bool condition, string message, string detailMessage)
+ {
+ if (!condition)
+ {
+ string stackTrace;
+ try
+ {
+ stackTrace = new StackTrace(0, true).ToString(System.Diagnostics.StackTrace.TraceFormat.Normal);
+ }
+ catch
+ {
+ stackTrace = "";
+ }
+ WriteLine(FormatAssert(stackTrace, message, detailMessage));
+ s_ShowDialog(stackTrace, message, detailMessage, "Assertion Failed");
+ }
+ }
+
+ internal static void ContractFailure(bool condition, string message, string detailMessage, string failureKindMessage)
+ {
+ if (!condition)
+ {
+ string stackTrace;
+ try
+ {
+ stackTrace = new StackTrace(2, true).ToString(System.Diagnostics.StackTrace.TraceFormat.Normal);
+ }
+ catch
+ {
+ stackTrace = "";
+ }
+ WriteLine(FormatAssert(stackTrace, message, detailMessage));
+ s_ShowDialog(stackTrace, message, detailMessage, SR.GetResourceString(failureKindMessage));
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Fail(string message)
+ {
+ Assert(false, message, string.Empty);
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Fail(string message, string detailMessage)
+ {
+ Assert(false, message, detailMessage);
+ }
+
+ private static string FormatAssert(string stackTrace, string message, string detailMessage)
+ {
+ string newLine = GetIndentString() + Environment.NewLine;
+ return SR.DebugAssertBanner + newLine
+ + SR.DebugAssertShortMessage + newLine
+ + message + newLine
+ + SR.DebugAssertLongMessage + newLine
+ + detailMessage + newLine
+ + stackTrace;
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Assert(bool condition, string message, string detailMessageFormat, params object[] args)
+ {
+ Assert(condition, message, string.Format(detailMessageFormat, args));
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLine(string message)
+ {
+ Write(message + Environment.NewLine);
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Write(string message)
+ {
+ lock (s_lock)
+ {
+ if (message == null)
+ {
+ s_WriteCore(string.Empty);
+ return;
+ }
+ if (s_needIndent)
+ {
+ message = GetIndentString() + message;
+ s_needIndent = false;
+ }
+ s_WriteCore(message);
+ if (message.EndsWith(Environment.NewLine))
+ {
+ s_needIndent = true;
+ }
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLine(object value)
+ {
+ WriteLine(value?.ToString());
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLine(object value, string category)
+ {
+ WriteLine(value?.ToString(), category);
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLine(string format, params object[] args)
+ {
+ WriteLine(string.Format(null, format, args));
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLine(string message, string category)
+ {
+ if (category == null)
+ {
+ WriteLine(message);
+ }
+ else
+ {
+ WriteLine(category + ":" + message);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Write(object value)
+ {
+ Write(value?.ToString());
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Write(string message, string category)
+ {
+ if (category == null)
+ {
+ Write(message);
+ }
+ else
+ {
+ Write(category + ":" + message);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void Write(object value, string category)
+ {
+ Write(value?.ToString(), category);
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteIf(bool condition, string message)
+ {
+ if (condition)
+ {
+ Write(message);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteIf(bool condition, object value)
+ {
+ if (condition)
+ {
+ Write(value);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteIf(bool condition, string message, string category)
+ {
+ if (condition)
+ {
+ Write(message, category);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteIf(bool condition, object value, string category)
+ {
+ if (condition)
+ {
+ Write(value, category);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLineIf(bool condition, object value)
+ {
+ if (condition)
+ {
+ WriteLine(value);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLineIf(bool condition, object value, string category)
+ {
+ if (condition)
+ {
+ WriteLine(value, category);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLineIf(bool condition, string message)
+ {
+ if (condition)
+ {
+ WriteLine(message);
+ }
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void WriteLineIf(bool condition, string message, string category)
+ {
+ if (condition)
+ {
+ WriteLine(message, category);
+ }
+ }
+
+ private static bool s_needIndent;
+
+ private static string s_indentString;
+
+ private static string GetIndentString()
+ {
+ int indentCount = IndentSize * IndentLevel;
+ if (s_indentString?.Length == indentCount)
+ {
+ return s_indentString;
+ }
+ return s_indentString = new string(' ', indentCount);
+ }
+
+ private sealed class DebugAssertException : Exception
+ {
+ internal DebugAssertException(string stackTrace) :
+ base(Environment.NewLine + stackTrace)
+ {
+ }
+
+ internal DebugAssertException(string message, string stackTrace) :
+ base(message + Environment.NewLine + Environment.NewLine + stackTrace)
+ {
+ }
+
+ internal DebugAssertException(string message, string detailMessage, string stackTrace) :
+ base(message + Environment.NewLine + detailMessage + Environment.NewLine + Environment.NewLine + stackTrace)
+ {
+ }
+ }
+
+ // internal and not readonly so that the tests can swap this out.
+ internal static Action<string, string, string, string> s_ShowDialog = ShowDialog;
+
+ internal static Action<string> s_WriteCore = WriteCore;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggableAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggableAttribute.cs
new file mode 100644
index 0000000000..d05f8471b3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggableAttribute.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ // Attribute class used by the compiler to mark modules.
+ // If present, then debugging information for everything in the
+ // assembly was generated by the compiler, and will be preserved
+ // by the Runtime so that the debugger can provide full functionality
+ // in the case of JIT attach. If not present, then the compiler may
+ // or may not have included debugging information, and the Runtime
+ // won't preserve the debugging info, which will make debugging after
+ // a JIT attach difficult.
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module, AllowMultiple = false)]
+ public sealed class DebuggableAttribute : Attribute
+ {
+ [Flags]
+ public enum DebuggingModes
+ {
+ None = 0x0,
+ Default = 0x1,
+ DisableOptimizations = 0x100,
+ IgnoreSymbolStoreSequencePoints = 0x2,
+ EnableEditAndContinue = 0x4
+ }
+
+ public DebuggableAttribute(bool isJITTrackingEnabled, bool isJITOptimizerDisabled)
+ {
+ DebuggingFlags = 0;
+
+ if (isJITTrackingEnabled)
+ {
+ DebuggingFlags |= DebuggingModes.Default;
+ }
+
+ if (isJITOptimizerDisabled)
+ {
+ DebuggingFlags |= DebuggingModes.DisableOptimizations;
+ }
+ }
+
+ public DebuggableAttribute(DebuggingModes modes)
+ {
+ DebuggingFlags = modes;
+ }
+
+ public bool IsJITTrackingEnabled => (DebuggingFlags & DebuggingModes.Default) != 0;
+
+ public bool IsJITOptimizerDisabled => (DebuggingFlags & DebuggingModes.DisableOptimizations) != 0;
+
+ public DebuggingModes DebuggingFlags { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerBrowsableAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerBrowsableAttribute.cs
new file mode 100644
index 0000000000..b9d62225a9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerBrowsableAttribute.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ // DebuggerBrowsableState states are defined as follows:
+ // Never never show this element
+ // Expanded expansion of the class is done, so that all visible internal members are shown
+ // Collapsed expansion of the class is not performed. Internal visible members are hidden
+ // RootHidden The target element itself should not be shown, but should instead be
+ // automatically expanded to have its members displayed.
+ // Default value is collapsed
+
+ // Please also change the code which validates DebuggerBrowsableState variable (in this file)
+ // if you change this enum.
+ public enum DebuggerBrowsableState
+ {
+ Never = 0,
+ //Expanded is not supported in this release
+ //Expanded = 1,
+ Collapsed = 2,
+ RootHidden = 3
+ }
+
+
+ // the one currently supported with the csee.dat
+ // (mcee.dat, autoexp.dat) file.
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public sealed class DebuggerBrowsableAttribute : Attribute
+ {
+ public DebuggerBrowsableAttribute(DebuggerBrowsableState state)
+ {
+ if (state < DebuggerBrowsableState.Never || state > DebuggerBrowsableState.RootHidden)
+ throw new ArgumentOutOfRangeException(nameof(state));
+
+ State = state;
+ }
+ public DebuggerBrowsableState State { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerDisplayAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerDisplayAttribute.cs
new file mode 100644
index 0000000000..7aae4b9397
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerDisplayAttribute.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ // This attribute is used to control what is displayed for the given class or field
+ // in the data windows in the debugger. The single argument to this attribute is
+ // the string that will be displayed in the value column for instances of the type.
+ // This string can include text between { and } which can be either a field,
+ // property or method (as will be documented in mscorlib). In the C# case,
+ // a general expression will be allowed which only has implicit access to the this pointer
+ // for the current instance of the target type. The expression will be limited,
+ // however: there is no access to aliases, locals, or pointers.
+ // In addition, attributes on properties referenced in the expression are not processed.
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Delegate | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class DebuggerDisplayAttribute : Attribute
+ {
+ private Type _target;
+
+ public DebuggerDisplayAttribute(string value)
+ {
+ Value = value ?? "";
+ Name = "";
+ Type = "";
+ }
+
+ public string Value { get; }
+
+ public string Name { get; set; }
+
+ public string Type { get; set; }
+
+ public Type Target
+ {
+ get => _target;
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ TargetTypeName = value.AssemblyQualifiedName;
+ _target = value;
+ }
+ }
+
+ public string TargetTypeName { get; set; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerHiddenAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerHiddenAttribute.cs
new file mode 100644
index 0000000000..ace452e911
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerHiddenAttribute.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, Inherited = false)]
+ public sealed class DebuggerHiddenAttribute : Attribute
+ {
+ public DebuggerHiddenAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerNonUserCodeAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerNonUserCodeAttribute.cs
new file mode 100644
index 0000000000..1b61cb7262
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerNonUserCodeAttribute.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)]
+ public sealed class DebuggerNonUserCodeAttribute : Attribute
+ {
+ public DebuggerNonUserCodeAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerStepThroughAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerStepThroughAttribute.cs
new file mode 100644
index 0000000000..82a164771e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerStepThroughAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+#if PROJECTN
+ // Used by the IL2IL toolchain to mark generated code to control debugger stepping policy
+ [System.Runtime.CompilerServices.DependencyReductionRoot]
+#endif
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)]
+ public sealed class DebuggerStepThroughAttribute : Attribute
+ {
+ public DebuggerStepThroughAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerStepperBoundaryAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerStepperBoundaryAttribute.cs
new file mode 100644
index 0000000000..647f2fdb00
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerStepperBoundaryAttribute.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ /// <summary>Indicates the code following the attribute is to be executed in run, not step, mode.</summary>
+ [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)]
+ public sealed class DebuggerStepperBoundaryAttribute : Attribute
+ {
+ public DebuggerStepperBoundaryAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerTypeProxyAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerTypeProxyAttribute.cs
new file mode 100644
index 0000000000..445834e056
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerTypeProxyAttribute.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class DebuggerTypeProxyAttribute : Attribute
+ {
+ private Type _target;
+
+ public DebuggerTypeProxyAttribute(Type type)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ ProxyTypeName = type.AssemblyQualifiedName;
+ }
+
+ public DebuggerTypeProxyAttribute(string typeName)
+ {
+ ProxyTypeName = typeName;
+ }
+
+ public string ProxyTypeName { get; }
+
+ public Type Target
+ {
+ get => _target;
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ TargetTypeName = value.AssemblyQualifiedName;
+ _target = value;
+ }
+ }
+
+ public string TargetTypeName { get; set; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerVisualizerAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerVisualizerAttribute.cs
new file mode 100644
index 0000000000..032eca8a15
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/DebuggerVisualizerAttribute.cs
@@ -0,0 +1,97 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ /// <summary>
+ /// Signifies that the attributed type has a visualizer which is pointed
+ /// to by the parameter type name strings.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class DebuggerVisualizerAttribute : Attribute
+ {
+ private Type _target;
+
+ public DebuggerVisualizerAttribute(string visualizerTypeName)
+ {
+ VisualizerTypeName = visualizerTypeName;
+ }
+
+ public DebuggerVisualizerAttribute(string visualizerTypeName, string visualizerObjectSourceTypeName)
+ {
+ VisualizerTypeName = visualizerTypeName;
+ VisualizerObjectSourceTypeName = visualizerObjectSourceTypeName;
+ }
+
+ public DebuggerVisualizerAttribute(string visualizerTypeName, Type visualizerObjectSource)
+ {
+ if (visualizerObjectSource == null)
+ {
+ throw new ArgumentNullException(nameof(visualizerObjectSource));
+ }
+
+ VisualizerTypeName = visualizerTypeName;
+ VisualizerObjectSourceTypeName = visualizerObjectSource.AssemblyQualifiedName;
+ }
+
+ public DebuggerVisualizerAttribute(Type visualizer)
+ {
+ if (visualizer == null)
+ {
+ throw new ArgumentNullException(nameof(visualizer));
+ }
+
+ VisualizerTypeName = visualizer.AssemblyQualifiedName;
+ }
+
+ public DebuggerVisualizerAttribute(Type visualizer, Type visualizerObjectSource)
+ {
+ if (visualizer == null)
+ {
+ throw new ArgumentNullException(nameof(visualizer));
+ }
+ if (visualizerObjectSource == null)
+ {
+ throw new ArgumentNullException(nameof(visualizerObjectSource));
+ }
+
+ VisualizerTypeName = visualizer.AssemblyQualifiedName;
+ VisualizerObjectSourceTypeName = visualizerObjectSource.AssemblyQualifiedName;
+ }
+
+ public DebuggerVisualizerAttribute(Type visualizer, string visualizerObjectSourceTypeName)
+ {
+ if (visualizer == null)
+ {
+ throw new ArgumentNullException(nameof(visualizer));
+ }
+
+ VisualizerTypeName = visualizer.AssemblyQualifiedName;
+ VisualizerObjectSourceTypeName = visualizerObjectSourceTypeName;
+ }
+
+ public string VisualizerObjectSourceTypeName { get; }
+
+ public string VisualizerTypeName { get; }
+
+ public string Description { get; set; }
+
+ public Type Target
+ {
+ get => _target;
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ TargetTypeName = value.AssemblyQualifiedName;
+ _target = value;
+ }
+ }
+
+ public string TargetTypeName { get; set; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/StackTraceHiddenAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/StackTraceHiddenAttribute.cs
new file mode 100644
index 0000000000..474274ac08
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/StackTraceHiddenAttribute.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)]
+ internal sealed class StackTraceHiddenAttribute : Attribute
+ {
+ public StackTraceHiddenAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs
new file mode 100644
index 0000000000..d9f9f081cb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs
@@ -0,0 +1,661 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+using System.Threading.Tasks;
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Tracks activities. This is meant to be a singleton (accessed by the ActivityTracer.Instance static property)
+ ///
+ /// Logically this is simply holds the m_current variable that holds the async local that holds the current ActivityInfo
+ /// An ActivityInfo is represents a activity (which knows its creator and thus knows its path).
+ ///
+ /// Most of the magic is in the async local (it gets copied to new tasks)
+ ///
+ /// On every start event call OnStart
+ ///
+ /// Guid activityID;
+ /// Guid relatedActivityID;
+ /// if (OnStart(activityName, out activityID, out relatedActivityID, ForceStop, options))
+ /// // Log Start event with activityID and relatedActivityID
+ ///
+ /// On every stop event call OnStop
+ ///
+ /// Guid activityID;
+ /// if (OnStop(activityName, ref activityID ForceStop))
+ /// // Stop event with activityID
+ ///
+ /// On any normal event log the event with activityTracker.CurrentActivityId
+ /// </summary>
+ internal class ActivityTracker
+ {
+
+ /// <summary>
+ /// Called on work item begins. The activity name = providerName + activityName without 'Start' suffix.
+ /// It updates CurrentActivityId to track.
+ ///
+ /// It returns true if the Start should be logged, otherwise (if it is illegal recursion) it return false.
+ ///
+ /// The start event should use as its activity ID the CurrentActivityId AFTER calling this routine and its
+ /// RelatedActivityID the CurrentActivityId BEFORE calling this routine (the creator).
+ ///
+ /// If activity tracing is not on, then activityId and relatedActivityId are not set
+ /// </summary>
+ public void OnStart(string providerName, string activityName, int task, ref Guid activityId, ref Guid relatedActivityId, EventActivityOptions options)
+ {
+ if (m_current == null) // We are not enabled
+ {
+ // We used to rely on the TPL provider turning us on, but that has the disadvantage that you don't get Start-Stop tracking
+ // until you use Tasks for the first time (which you may never do). Thus we change it to pull rather tan push for whether
+ // we are enabled.
+ if (m_checkedForEnable)
+ return;
+ m_checkedForEnable = true;
+ if (TplEtwProvider.Log.IsEnabled(EventLevel.Informational, TplEtwProvider.Keywords.TasksFlowActivityIds))
+ Enable();
+ if (m_current == null)
+ return;
+ }
+
+
+ Debug.Assert((options & EventActivityOptions.Disable) == 0);
+
+ var currentActivity = m_current.Value;
+ var fullActivityName = NormalizeActivityName(providerName, activityName, task);
+
+ var etwLog = TplEtwProvider.Log;
+ if (etwLog.Debug)
+ {
+ etwLog.DebugFacilityMessage("OnStartEnter", fullActivityName);
+ etwLog.DebugFacilityMessage("OnStartEnterActivityState", ActivityInfo.LiveActivities(currentActivity));
+ }
+
+ if (currentActivity != null)
+ {
+ // Stop activity tracking if we reached the maximum allowed depth
+ if (currentActivity.m_level >= MAX_ACTIVITY_DEPTH)
+ {
+ activityId = Guid.Empty;
+ relatedActivityId = Guid.Empty;
+ if (etwLog.Debug)
+ etwLog.DebugFacilityMessage("OnStartRET", "Fail");
+ return;
+ }
+ // Check for recursion, and force-stop any activities if the activity already started.
+ if ((options & EventActivityOptions.Recursive) == 0)
+ {
+ ActivityInfo existingActivity = FindActiveActivity(fullActivityName, currentActivity);
+ if (existingActivity != null)
+ {
+ OnStop(providerName, activityName, task, ref activityId);
+ currentActivity = m_current.Value;
+ }
+ }
+ }
+
+ // Get a unique ID for this activity.
+ long id;
+ if (currentActivity == null)
+ id = Interlocked.Increment(ref m_nextId);
+ else
+ id = Interlocked.Increment(ref currentActivity.m_lastChildID);
+
+ // The previous ID is my 'causer' and becomes my related activity ID
+ relatedActivityId = EventSource.CurrentThreadActivityId;
+
+ // Add to the list of started but not stopped activities.
+ ActivityInfo newActivity = new ActivityInfo(fullActivityName, id, currentActivity, relatedActivityId, options);
+ m_current.Value = newActivity;
+
+ // Remember the current ID so we can log it
+ activityId = newActivity.ActivityId;
+
+ if (etwLog.Debug)
+ {
+ etwLog.DebugFacilityMessage("OnStartRetActivityState", ActivityInfo.LiveActivities(newActivity));
+ etwLog.DebugFacilityMessage1("OnStartRet", activityId.ToString(), relatedActivityId.ToString());
+ }
+ }
+
+ /// <summary>
+ /// Called when a work item stops. The activity name = providerName + activityName without 'Stop' suffix.
+ /// It updates m_current variable to track this fact. The Stop event associated with stop should log the ActivityID associated with the event.
+ ///
+ /// If activity tracing is not on, then activityId and relatedActivityId are not set
+ /// </summary>
+ public void OnStop(string providerName, string activityName, int task, ref Guid activityId)
+ {
+ if (m_current == null) // We are not enabled
+ return;
+
+ var fullActivityName = NormalizeActivityName(providerName, activityName, task);
+
+ var etwLog = TplEtwProvider.Log;
+ if (etwLog.Debug)
+ {
+ etwLog.DebugFacilityMessage("OnStopEnter", fullActivityName);
+ etwLog.DebugFacilityMessage("OnStopEnterActivityState", ActivityInfo.LiveActivities(m_current.Value));
+ }
+
+ for (; ; ) // This is a retry loop.
+ {
+ ActivityInfo currentActivity = m_current.Value;
+ ActivityInfo newCurrentActivity = null; // if we have seen any live activities (orphans), at he first one we have seen.
+
+ // Search to find the activity to stop in one pass. This insures that we don't let one mistake
+ // (stopping something that was not started) cause all active starts to be stopped
+ // By first finding the target start to stop we are more robust.
+ ActivityInfo activityToStop = FindActiveActivity(fullActivityName, currentActivity);
+
+ // ignore stops where we can't find a start because we may have popped them previously.
+ if (activityToStop == null)
+ {
+ activityId = Guid.Empty;
+ // TODO add some logging about this. Basically could not find matching start.
+ if (etwLog.Debug)
+ etwLog.DebugFacilityMessage("OnStopRET", "Fail");
+ return;
+ }
+
+ activityId = activityToStop.ActivityId;
+
+ // See if there are any orphans that need to be stopped.
+ ActivityInfo orphan = currentActivity;
+ while (orphan != activityToStop && orphan != null)
+ {
+ if (orphan.m_stopped != 0) // Skip dead activities.
+ {
+ orphan = orphan.m_creator;
+ continue;
+ }
+ if (orphan.CanBeOrphan())
+ {
+ // We can't pop anything after we see a valid orphan, remember this for later when we update m_current.
+ if (newCurrentActivity == null)
+ newCurrentActivity = orphan;
+ }
+ else
+ {
+ orphan.m_stopped = 1;
+ Debug.Assert(orphan.m_stopped != 0);
+ }
+ orphan = orphan.m_creator;
+ }
+
+ // try to Stop the activity atomically. Other threads may be trying to do this as well.
+ if (Interlocked.CompareExchange(ref activityToStop.m_stopped, 1, 0) == 0)
+ {
+ // I succeeded stopping this activity. Now we update our m_current pointer
+
+ // If I haven't yet determined the new current activity, it is my creator.
+ if (newCurrentActivity == null)
+ newCurrentActivity = activityToStop.m_creator;
+
+ m_current.Value = newCurrentActivity;
+
+ if (etwLog.Debug)
+ {
+ etwLog.DebugFacilityMessage("OnStopRetActivityState", ActivityInfo.LiveActivities(newCurrentActivity));
+ etwLog.DebugFacilityMessage("OnStopRet", activityId.ToString());
+ }
+ return;
+ }
+ // We failed to stop it. We must have hit a race to stop it. Just start over and try again.
+ }
+ }
+
+ /// <summary>
+ /// Turns on activity tracking. It is sticky, once on it stays on (race issues otherwise)
+ /// </summary>
+ public void Enable()
+ {
+ if (m_current == null)
+ {
+ // Catch the not Implemented
+ try
+ {
+ m_current = new AsyncLocal<ActivityInfo>(ActivityChanging);
+ }
+ catch (NotImplementedException) {
+#if (!ES_BUILD_PCL && ! ES_BUILD_PN)
+ // send message to debugger without delay
+ System.Diagnostics.Debugger.Log(0, null, "Activity Enabled() called but AsyncLocals Not Supported (pre V4.6). Ignoring Enable");
+#endif
+ }
+ }
+ }
+
+ /// <summary>
+ /// An activity tracker is a singleton, this is how you get the one and only instance.
+ /// </summary>
+ public static ActivityTracker Instance { get { return s_activityTrackerInstance; } }
+
+
+ #region private
+
+ /// <summary>
+ /// Searched for a active (nonstopped) activity with the given name. Returns null if not found.
+ /// </summary>
+ private ActivityInfo FindActiveActivity(string name, ActivityInfo startLocation)
+ {
+ var activity = startLocation;
+ while (activity != null)
+ {
+ if (name == activity.m_name && activity.m_stopped == 0)
+ return activity;
+ activity = activity.m_creator;
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Strip out "Start" or "End" suffix from activity name and add providerName prefix.
+ /// If 'task' it does not end in Start or Stop and Task is non-zero use that as the name of the activity
+ /// </summary>
+ private string NormalizeActivityName(string providerName, string activityName, int task)
+ {
+ if (activityName.EndsWith(EventSource.s_ActivityStartSuffix, StringComparison.Ordinal))
+ activityName = activityName.Substring(0, activityName.Length - EventSource.s_ActivityStartSuffix.Length);
+ else if (activityName.EndsWith(EventSource.s_ActivityStopSuffix, StringComparison.Ordinal))
+ activityName = activityName.Substring(0, activityName.Length - EventSource.s_ActivityStopSuffix.Length);
+ else if (task != 0)
+ activityName = "task" + task.ToString();
+
+ // We use provider name to distinguish between activities from different providers.
+ return providerName + activityName;
+ }
+
+ // *******************************************************************************
+ /// <summary>
+ /// An ActivityInfo represents a particular activity. It is almost read-only. The only
+ /// fields that change after creation are
+ /// m_lastChildID - used to generate unique IDs for the children activities and for the most part can be ignored.
+ /// m_stopped - indicates that this activity is dead
+ /// This read-only-ness is important because an activity's m_creator chain forms the
+ /// 'Path of creation' for the activity (which is also its unique ID) but is also used as
+ /// the 'list of live parents' which indicate of those ancestors, which are alive (if they
+ /// are not marked dead they are alive).
+ /// </summary>
+ private class ActivityInfo
+ {
+ public ActivityInfo(string name, long uniqueId, ActivityInfo creator, Guid activityIDToRestore, EventActivityOptions options)
+ {
+ m_name = name;
+ m_eventOptions = options;
+ m_creator = creator;
+ m_uniqueId = uniqueId;
+ m_level = creator != null ? creator.m_level + 1 : 0;
+ m_activityIdToRestore = activityIDToRestore;
+
+ // Create a nice GUID that encodes the chain of activities that started this one.
+ CreateActivityPathGuid(out m_guid, out m_activityPathGuidOffset);
+ }
+
+ public Guid ActivityId
+ {
+ get
+ {
+ return m_guid;
+ }
+ }
+
+ public static string Path(ActivityInfo activityInfo)
+ {
+ if (activityInfo == null)
+ return ("");
+ return Path(activityInfo.m_creator) + "/" + activityInfo.m_uniqueId.ToString();
+ }
+
+ public override string ToString()
+ {
+ return m_name + "(" + Path(this) + (m_stopped != 0 ? ",DEAD)" : ")");
+ }
+
+ public static string LiveActivities(ActivityInfo list)
+ {
+ if (list == null)
+ return "";
+ return list.ToString() + ";" + LiveActivities(list.m_creator);
+ }
+
+ public bool CanBeOrphan()
+ {
+ if ((m_eventOptions & EventActivityOptions.Detachable) != 0)
+ return true;
+ return false;
+ }
+
+ #region private
+
+ #region CreateActivityPathGuid
+ /// <summary>
+ /// Logically every activity Path (see Path()) that describes the activities that caused this
+ /// (rooted in an activity that predates activity tracking.
+ ///
+ /// We wish to encode this path in the Guid to the extent that we can. Many of the paths have
+ /// many small numbers in them and we take advantage of this in the encoding to output as long
+ /// a path in the GUID as possible.
+ ///
+ /// Because of the possibility of GUID collision, we only use 96 of the 128 bits of the GUID
+ /// for encoding the path. The last 32 bits are a simple checksum (and random number) that
+ /// identifies this as using the convention defined here.
+ ///
+ /// It returns both the GUID which has the path as well as the offset that points just beyond
+ /// the end of the activity (so it can be appended to). Note that if the end is in a nibble
+ /// (it uses nibbles instead of bytes as the unit of encoding, then it will point at the unfinished
+ /// byte (since the top nibble can't be zero you can determine if this is true by seeing if
+ /// this byte is nonZero. This offset is needed to efficiently create the ID for child activities.
+ /// </summary>
+ private unsafe void CreateActivityPathGuid(out Guid idRet, out int activityPathGuidOffset)
+ {
+ fixed (Guid* outPtr = &idRet)
+ {
+ int activityPathGuidOffsetStart = 0;
+ if (m_creator != null)
+ {
+ activityPathGuidOffsetStart = m_creator.m_activityPathGuidOffset;
+ idRet = m_creator.m_guid;
+ }
+ else
+ {
+ // TODO FIXME - differentiate between AD inside PCL
+ int appDomainID = 0;
+#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN)
+ appDomainID = System.Threading.Thread.GetDomainID();
+#endif
+ // We start with the appdomain number to make this unique among appdomains.
+ activityPathGuidOffsetStart = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint)appDomainID);
+ }
+
+ activityPathGuidOffset = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint)m_uniqueId);
+
+
+ // If the path does not fit, Make a GUID by incrementing rather than as a path, keeping as much of the path as possible
+ if (12 < activityPathGuidOffset)
+ CreateOverflowGuid(outPtr);
+ }
+ }
+
+ /// <summary>
+ /// If we can't fit the activity Path into the GUID we come here. What we do is simply
+ /// generate a 4 byte number (s_nextOverflowId). Then look for an ancestor that has
+ /// sufficient space for this ID. By doing this, we preserve the fact that this activity
+ /// is a child (of unknown depth) from that ancestor.
+ /// </summary>
+ private unsafe void CreateOverflowGuid(Guid* outPtr)
+ {
+ // Search backwards for an ancestor that has sufficient space to put the ID.
+ for (ActivityInfo ancestor = m_creator; ancestor != null; ancestor = ancestor.m_creator)
+ {
+ if (ancestor.m_activityPathGuidOffset <= 10) // we need at least 2 bytes.
+ {
+ uint id = unchecked((uint)Interlocked.Increment(ref ancestor.m_lastChildID)); // Get a unique ID
+ // Try to put the ID into the GUID
+ *outPtr = ancestor.m_guid;
+ int endId = AddIdToGuid(outPtr, ancestor.m_activityPathGuidOffset, id, true);
+
+ // Does it fit?
+ if (endId <= 12)
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// The encoding for a list of numbers used to make Activity GUIDs. Basically
+ /// we operate on nibbles (which are nice because they show up as hex digits). The
+ /// list is ended with a end nibble (0) and depending on the nibble value (Below)
+ /// the value is either encoded into nibble itself or it can spill over into the
+ /// bytes that follow.
+ /// </summary>
+ enum NumberListCodes : byte
+ {
+ End = 0x0, // ends the list. No valid value has this prefix.
+ LastImmediateValue = 0xA,
+
+ PrefixCode = 0xB, // all the 'long' encodings go here. If the next nibble is MultiByte1-4
+ // than this is a 'overflow' id. Unlike the hierarchical IDs these are
+ // allocated densely but don't tell you anything about nesting. we use
+ // these when we run out of space in the GUID to store the path.
+
+ MultiByte1 = 0xC, // 1 byte follows. If this Nibble is in the high bits, it the high bits of the number are stored in the low nibble.
+ // commented out because the code does not explicitly reference the names (but they are logically defined).
+ // MultiByte2 = 0xD, // 2 bytes follow (we don't bother with the nibble optimization)
+ // MultiByte3 = 0xE, // 3 bytes follow (we don't bother with the nibble optimization)
+ // MultiByte4 = 0xF, // 4 bytes follow (we don't bother with the nibble optimization)
+ }
+
+ /// Add the activity id 'id' to the output Guid 'outPtr' starting at the offset 'whereToAddId'
+ /// Thus if this number is 6 that is where 'id' will be added. This will return 13 (12
+ /// is the maximum number of bytes that fit in a GUID) if the path did not fit.
+ /// If 'overflow' is true, then the number is encoded as an 'overflow number (which has a
+ /// special (longer prefix) that indicates that this ID is allocated differently
+ private static unsafe int AddIdToGuid(Guid* outPtr, int whereToAddId, uint id, bool overflow = false)
+ {
+ byte* ptr = (byte*)outPtr;
+ byte* endPtr = ptr + 12;
+ ptr += whereToAddId;
+ if (endPtr <= ptr)
+ return 13; // 12 means we might exactly fit, 13 means we definately did not fit
+
+ if (0 < id && id <= (uint)NumberListCodes.LastImmediateValue && !overflow)
+ WriteNibble(ref ptr, endPtr, id);
+ else
+ {
+ uint len = 4;
+ if (id <= 0xFF)
+ len = 1;
+ else if (id <= 0xFFFF)
+ len = 2;
+ else if (id <= 0xFFFFFF)
+ len = 3;
+
+ if (overflow)
+ {
+ if (endPtr <= ptr + 2) // I need at least 2 bytes
+ return 13;
+
+ // Write out the prefix code nibble and the length nibble
+ WriteNibble(ref ptr, endPtr, (uint)NumberListCodes.PrefixCode);
+ }
+ // The rest is the same for overflow and non-overflow case
+ WriteNibble(ref ptr, endPtr, (uint)NumberListCodes.MultiByte1 + (len - 1));
+
+ // Do we have an odd nibble? If so flush it or use it for the 12 byte case.
+ if (ptr < endPtr && *ptr != 0)
+ {
+ // If the value < 4096 we can use the nibble we are otherwise just outputting as padding.
+ if (id < 4096)
+ {
+ // Indicate this is a 1 byte multicode with 4 high order bits in the lower nibble.
+ *ptr = (byte)(((uint)NumberListCodes.MultiByte1 << 4) + (id >> 8));
+ id &= 0xFF; // Now we only want the low order bits.
+ }
+ ptr++;
+ }
+
+ // Write out the bytes.
+ while (0 < len)
+ {
+ if (endPtr <= ptr)
+ {
+ ptr++; // Indicate that we have overflowed
+ break;
+ }
+ *ptr++ = (byte)id;
+ id = (id >> 8);
+ --len;
+ }
+ }
+
+ // Compute the checksum
+ uint* sumPtr = (uint*)outPtr;
+ // We set the last DWORD the sum of the first 3 DWORDS in the GUID. This
+ // This last number is a random number (it identifies us as us) the process ID to make it unique per process.
+ sumPtr[3] = (sumPtr[0] + sumPtr[1] + sumPtr[2] + 0x599D99AD) ^ EventSource.s_currentPid;
+
+ return (int)(ptr - ((byte*)outPtr));
+ }
+
+ /// <summary>
+ /// Write a single Nible 'value' (must be 0-15) to the byte buffer represented by *ptr.
+ /// Will not go past 'endPtr'. Also it assumes that we never write 0 so we can detect
+ /// whether a nibble has already been written to ptr because it will be nonzero.
+ /// Thus if it is non-zero it adds to the current byte, otherwise it advances and writes
+ /// the new byte (in the high bits) of the next byte.
+ /// </summary>
+ private static unsafe void WriteNibble(ref byte* ptr, byte* endPtr, uint value)
+ {
+ Debug.Assert(value < 16);
+ Debug.Assert(ptr < endPtr);
+
+ if (*ptr != 0)
+ *ptr++ |= (byte)value;
+ else
+ *ptr = (byte)(value << 4);
+ }
+
+ #endregion // CreateGuidForActivityPath
+
+ readonly internal string m_name; // The name used in the 'start' and 'stop' APIs to help match up
+ readonly long m_uniqueId; // a small number that makes this activity unique among its siblings
+ internal readonly Guid m_guid; // Activity Guid, it is basically an encoding of the Path() (see CreateActivityPathGuid)
+ internal readonly int m_activityPathGuidOffset; // Keeps track of where in m_guid the causality path stops (used to generated child GUIDs)
+ internal readonly int m_level; // current depth of the Path() of the activity (used to keep recursion under control)
+ readonly internal EventActivityOptions m_eventOptions; // Options passed to start.
+ internal long m_lastChildID; // used to create a unique ID for my children activities
+ internal int m_stopped; // This work item has stopped
+ readonly internal ActivityInfo m_creator; // My parent (creator). Forms the Path() for the activity.
+ readonly internal Guid m_activityIdToRestore; // The Guid to restore after a stop.
+ #endregion
+ }
+
+ // This callback is used to initialize the m_current AsyncLocal Variable.
+ // Its job is to keep the ETW Activity ID (part of thread local storage) in sync
+ // with m_current.ActivityID
+ void ActivityChanging(AsyncLocalValueChangedArgs<ActivityInfo> args)
+ {
+ ActivityInfo cur = args.CurrentValue;
+ ActivityInfo prev = args.PreviousValue;
+
+ // Are we popping off a value? (we have a prev, and it creator is cur)
+ // Then check if we should use the GUID at the time of the start event
+ if (prev != null && prev.m_creator == cur)
+ {
+ // If the saved activity ID is not the same as the creator activity
+ // that takes precedence (it means someone explicitly did a SetActivityID)
+ // Set it to that and get out
+ if (cur == null || prev.m_activityIdToRestore != cur.ActivityId)
+ {
+ EventSource.SetCurrentThreadActivityId(prev.m_activityIdToRestore);
+ return;
+ }
+ }
+
+ // OK we did not have an explicit SetActivityID set. Then we should be
+ // setting the activity to current ActivityInfo. However that activity
+ // might be dead, in which case we should skip it, so we never set
+ // the ID to dead things.
+ while (cur != null)
+ {
+ // We found a live activity (typically the first time), set it to that.
+ if (cur.m_stopped == 0)
+ {
+ EventSource.SetCurrentThreadActivityId(cur.ActivityId);
+ return;
+ }
+ cur = cur.m_creator;
+ }
+ // we can get here if there is no information on our activity stack (everything is dead)
+ // currently we do nothing, as that seems better than setting to Guid.Emtpy.
+ }
+
+ /// <summary>
+ /// Async local variables have the property that the are automatically copied whenever a task is created and used
+ /// while that task is running. Thus m_current 'flows' to any task that is caused by the current thread that
+ /// last set it.
+ ///
+ /// This variable points a a linked list that represents all Activities that have started but have not stopped.
+ /// </summary>
+ AsyncLocal<ActivityInfo> m_current;
+ bool m_checkedForEnable;
+
+ // Singleton
+ private static ActivityTracker s_activityTrackerInstance = new ActivityTracker();
+
+ // Used to create unique IDs at the top level. Not used for nested Ids (each activity has its own id generator)
+ static long m_nextId = 0;
+ private const ushort MAX_ACTIVITY_DEPTH = 100; // Limit maximum depth of activities to be tracked at 100.
+ // This will avoid leaking memory in case of activities that are never stopped.
+
+ #endregion
+ }
+
+#if ES_BUILD_STANDALONE || ES_BUILD_PN
+ /******************************** SUPPORT *****************************/
+ /// <summary>
+ /// This is supplied by the framework. It is has the semantics that the value is copied to any new Tasks that is created
+ /// by the current task. Thus all causally related code gets this value. Note that reads and writes to this VARIABLE
+ /// (not what it points it) to this does not need to be protected by locks because it is inherently thread local (you always
+ /// only get your thread local copy which means that you never have races.
+ /// </summary>
+ ///
+#if ES_BUILD_STANDALONE
+ [EventSource(Name = "Microsoft.Tasks.Nuget")]
+#else
+ [EventSource(Name = "System.Diagnostics.Tracing.TplEtwProvider")]
+#endif
+ internal class TplEtwProvider : EventSource
+ {
+ public class Keywords
+ {
+ public const EventKeywords TasksFlowActivityIds = (EventKeywords)0x80;
+ public const EventKeywords Debug = (EventKeywords)0x20000;
+ }
+
+ public static TplEtwProvider Log = new TplEtwProvider();
+ public bool Debug { get { return IsEnabled(EventLevel.Verbose, Keywords.Debug); } }
+
+ public void DebugFacilityMessage(string Facility, string Message) { WriteEvent(1, Facility, Message); }
+ public void DebugFacilityMessage1(string Facility, string Message, string Arg) { WriteEvent(2, Facility, Message, Arg); }
+ public void SetActivityId(Guid Id) { WriteEvent(3, Id); }
+ }
+#endif
+
+#if ES_BUILD_AGAINST_DOTNET_V35 || ES_BUILD_PCL || NO_ASYNC_LOCAL
+ // In these cases we don't have any Async local support. Do nothing.
+ internal sealed class AsyncLocalValueChangedArgs<T>
+ {
+ public T PreviousValue { get { return default(T); } }
+ public T CurrentValue { get { return default(T); } }
+
+ }
+
+ internal sealed class AsyncLocal<T>
+ {
+ public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>> valueChangedHandler) {
+ throw new NotImplementedException("AsyncLocal only available on V4.6 and above");
+ }
+ public T Value
+ {
+ get { return default(T); }
+ set { }
+ }
+ }
+#endif
+
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventActivityOptions.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventActivityOptions.cs
new file mode 100644
index 0000000000..782afbf869
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventActivityOptions.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// EventActivityOptions flags allow to specify different activity related characteristics.
+ /// </summary>
+ [Flags]
+ public enum EventActivityOptions
+ {
+ /// <summary>
+ /// No special options are added to the event.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Disable Implicit Activity Tracking
+ /// </summary>
+ Disable = 0x2,
+
+ /// <summary>
+ /// Allow activity event to call itself (directly or indirectly)
+ /// </summary>
+ Recursive = 0x4,
+
+ /// <summary>
+ /// Allows event activity to live beyond its parent.
+ /// </summary>
+ Detachable = 0x8
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventDescriptor.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventDescriptor.cs
new file mode 100644
index 0000000000..0fed7b5c00
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventDescriptor.cs
@@ -0,0 +1,217 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+#if ES_BUILD_STANDALONE
+using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment;
+#endif
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ [StructLayout(LayoutKind.Explicit, Size = 16)]
+#if !CORECLR && !ES_BUILD_PN
+ [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
+#endif // !CORECLR && !ES_BUILD_PN
+
+ /*
+ EventDescriptor was public in the separate System.Diagnostics.Tracing assembly(pre NS2.0),
+ now the move to CoreLib marked them as private.
+ While they are technically private (it's a contract used between the library and the ILC toolchain),
+ we need them to be rooted and exported from shared library for the system to work.
+ For now I'm simply marking them as public again.A cleaner solution might be to use.rd.xml to
+ root them and modify shared library definition to force export them.
+ */
+#if ES_BUILD_PN
+ public
+#else
+ internal
+#endif
+ struct EventDescriptor
+ {
+ # region private
+ [FieldOffset(0)]
+ private int m_traceloggingId;
+ [FieldOffset(0)]
+ private ushort m_id;
+ [FieldOffset(2)]
+ private byte m_version;
+ [FieldOffset(3)]
+ private byte m_channel;
+ [FieldOffset(4)]
+ private byte m_level;
+ [FieldOffset(5)]
+ private byte m_opcode;
+ [FieldOffset(6)]
+ private ushort m_task;
+ [FieldOffset(8)]
+ private long m_keywords;
+ #endregion
+
+ public EventDescriptor(
+ int traceloggingId,
+ byte level,
+ byte opcode,
+ long keywords
+ )
+ {
+ this.m_id = 0;
+ this.m_version = 0;
+ this.m_channel = 0;
+ this.m_traceloggingId = traceloggingId;
+ this.m_level = level;
+ this.m_opcode = opcode;
+ this.m_task = 0;
+ this.m_keywords = keywords;
+ }
+
+ public EventDescriptor(
+ int id,
+ byte version,
+ byte channel,
+ byte level,
+ byte opcode,
+ int task,
+ long keywords
+ )
+ {
+ if (id < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(id), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (id > ushort.MaxValue)
+ {
+ throw new ArgumentOutOfRangeException(nameof(id), SR.Format(SR.ArgumentOutOfRange_NeedValidId, 1, ushort.MaxValue));
+ }
+
+ m_traceloggingId = 0;
+ m_id = (ushort)id;
+ m_version = version;
+ m_channel = channel;
+ m_level = level;
+ m_opcode = opcode;
+ m_keywords = keywords;
+
+ if (task < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(task), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (task > ushort.MaxValue)
+ {
+ throw new ArgumentOutOfRangeException(nameof(task), SR.Format(SR.ArgumentOutOfRange_NeedValidId, 1, ushort.MaxValue));
+ }
+
+ m_task = (ushort)task;
+ }
+
+ public int EventId
+ {
+ get
+ {
+ return m_id;
+ }
+ }
+ public byte Version
+ {
+ get
+ {
+ return m_version;
+ }
+ }
+ public byte Channel
+ {
+ get
+ {
+ return m_channel;
+ }
+ }
+ public byte Level
+ {
+ get
+ {
+ return m_level;
+ }
+ }
+ public byte Opcode
+ {
+ get
+ {
+ return m_opcode;
+ }
+ }
+ public int Task
+ {
+ get
+ {
+ return m_task;
+ }
+ }
+ public long Keywords
+ {
+ get
+ {
+ return m_keywords;
+ }
+ }
+
+ internal int TraceLoggingId
+ {
+ get
+ {
+ return m_traceloggingId;
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is EventDescriptor))
+ return false;
+
+ return Equals((EventDescriptor) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return m_id ^ m_version ^ m_channel ^ m_level ^ m_opcode ^ m_task ^ (int)m_keywords;
+ }
+
+ public bool Equals(EventDescriptor other)
+ {
+ if ((m_id != other.m_id) ||
+ (m_version != other.m_version) ||
+ (m_channel != other.m_channel) ||
+ (m_level != other.m_level) ||
+ (m_opcode != other.m_opcode) ||
+ (m_task != other.m_task) ||
+ (m_keywords != other.m_keywords))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public static bool operator ==(EventDescriptor event1, EventDescriptor event2)
+ {
+ return event1.Equals(event2);
+ }
+
+ public static bool operator !=(EventDescriptor event1, EventDescriptor event2)
+ {
+ return !event1.Equals(event2);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventProvider.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventProvider.cs
new file mode 100644
index 0000000000..c1e4298da8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventProvider.cs
@@ -0,0 +1,1323 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using Microsoft.Win32;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Security;
+#if !CORECLR && !ES_BUILD_PN
+using System.Security.Permissions;
+#endif // !CORECLR && !ES_BUILD_PN
+using System.Threading;
+using System;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_AGAINST_DOTNET_V35
+using Microsoft.Internal; // for Tuple (can't define alias for open generic types so we "use" the whole namespace)
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ // New in CLR4.0
+ internal enum ControllerCommand
+ {
+ // Strictly Positive numbers are for provider-specific commands, negative number are for 'shared' commands. 256
+ // The first 256 negative numbers are reserved for the framework.
+ Update = 0, // Not used by EventPrividerBase.
+ SendManifest = -1,
+ Enable = -2,
+ Disable = -3,
+ };
+
+ /// <summary>
+ /// Only here because System.Diagnostics.EventProvider needs one more extensibility hook (when it gets a
+ /// controller callback)
+ /// </summary>
+#if !CORECLR && !ES_BUILD_PN
+ [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
+#endif // !CORECLR && !ES_BUILD_PN
+ internal partial class EventProvider : IDisposable
+ {
+ // This is the windows EVENT_DATA_DESCRIPTOR structure. We expose it because this is what
+ // subclasses of EventProvider use when creating efficient (but unsafe) version of
+ // EventWrite. We do make it a nested type because we really don't expect anyone to use
+ // it except subclasses (and then only rarely).
+ [StructLayout(LayoutKind.Sequential)]
+ public struct EventData
+ {
+ internal unsafe ulong Ptr;
+ internal uint Size;
+ internal uint Reserved;
+ }
+
+ /// <summary>
+ /// A struct characterizing ETW sessions (identified by the etwSessionId) as
+ /// activity-tracing-aware or legacy. A session that's activity-tracing-aware
+ /// has specified one non-zero bit in the reserved range 44-47 in the
+ /// 'allKeywords' value it passed in for a specific EventProvider.
+ /// </summary>
+ public struct SessionInfo
+ {
+ internal int sessionIdBit; // the index of the bit used for tracing in the "reserved" field of AllKeywords
+ internal int etwSessionId; // the machine-wide ETW session ID
+
+ internal SessionInfo(int sessionIdBit_, int etwSessionId_)
+ { sessionIdBit = sessionIdBit_; etwSessionId = etwSessionId_; }
+ }
+
+ private static bool m_setInformationMissing;
+
+ internal IEventProvider m_eventProvider; // The interface that implements the specific logging mechanism functions.
+ UnsafeNativeMethods.ManifestEtw.EtwEnableCallback m_etwCallback; // Trace Callback function
+ private long m_regHandle; // Trace Registration Handle
+ private byte m_level; // Tracing Level
+ private long m_anyKeywordMask; // Trace Enable Flags
+ private long m_allKeywordMask; // Match all keyword
+ private List<SessionInfo> m_liveSessions; // current live sessions (Tuple<sessionIdBit, etwSessionId>)
+ private bool m_enabled; // Enabled flag from Trace callback
+ private string m_providerName; // Control name
+ private Guid m_providerId; // Control Guid
+ internal bool m_disposed; // when true provider has unregistered
+
+ [ThreadStatic]
+ private static WriteEventErrorCode s_returnCode; // The last return code
+
+ private const int s_basicTypeAllocationBufferSize = 16;
+ private const int s_etwMaxNumberArguments = 128;
+ private const int s_etwAPIMaxRefObjCount = 8;
+ private const int s_maxEventDataDescriptors = 128;
+ private const int s_traceEventMaximumSize = 65482;
+ private const int s_traceEventMaximumStringSize = 32724;
+
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
+ public enum WriteEventErrorCode : int
+ {
+ //check mapping to runtime codes
+ NoError = 0,
+ NoFreeBuffers = 1,
+ EventTooBig = 2,
+ NullInput = 3,
+ TooManyArgs = 4,
+ Other = 5,
+ };
+
+ // Because callbacks happen on registration, and we need the callbacks for those setup
+ // we can't call Register in the constructor.
+ //
+ // Note that EventProvider should ONLY be used by EventSource. In particular because
+ // it registers a callback from native code you MUST dispose it BEFORE shutdown, otherwise
+ // you may get native callbacks during shutdown when we have destroyed the delegate.
+ // EventSource has special logic to do this, no one else should be calling EventProvider.
+ internal EventProvider()
+ {
+#if PLATFORM_WINDOWS
+ m_eventProvider = new EtwEventProvider();
+#elif FEATURE_PERFTRACING
+ m_eventProvider = new EventPipeEventProvider();
+#else
+ m_eventProvider = new NoOpEventProvider();
+#endif
+ }
+
+ /// <summary>
+ /// This method registers the controlGuid of this class with ETW. We need to be running on
+ /// Vista or above. If not a PlatformNotSupported exception will be thrown. If for some
+ /// reason the ETW Register call failed a NotSupported exception will be thrown.
+ /// </summary>
+ // <SecurityKernel Critical="True" Ring="0">
+ // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventRegister(System.Guid&,Microsoft.Win32.UnsafeNativeMethods.ManifestEtw+EtwEnableCallback,System.Void*,System.Int64&):System.UInt32" />
+ // <SatisfiesLinkDemand Name="Win32Exception..ctor(System.Int32)" />
+ // <ReferencesCritical Name="Method: EtwEnableCallBack(Guid&, Int32, Byte, Int64, Int64, Void*, Void*):Void" Ring="1" />
+ // </SecurityKernel>
+ internal unsafe void Register(EventSource eventSource)
+ {
+ uint status;
+ m_etwCallback = new UnsafeNativeMethods.ManifestEtw.EtwEnableCallback(EtwEnableCallBack);
+
+ status = EventRegister(eventSource, m_etwCallback);
+ if (status != 0)
+ {
+#if PLATFORM_WINDOWS && !ES_BUILD_STANDALONE
+ throw new ArgumentException(Interop.Kernel32.GetMessage(unchecked((int)status)));
+#else
+ throw new ArgumentException(Convert.ToString(unchecked((int)status)));
+#endif
+ }
+ }
+
+ //
+ // implement Dispose Pattern to early deregister from ETW insted of waiting for
+ // the finalizer to call deregistration.
+ // Once the user is done with the provider it needs to call Close() or Dispose()
+ // If neither are called the finalizer will unregister the provider anyway
+ //
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ // <SecurityKernel Critical="True" TreatAsSafe="Does not expose critical resource" Ring="1">
+ // <ReferencesCritical Name="Method: Deregister():Void" Ring="1" />
+ // </SecurityKernel>
+ protected virtual void Dispose(bool disposing)
+ {
+ //
+ // explicit cleanup is done by calling Dispose with true from
+ // Dispose() or Close(). The disposing arguement is ignored because there
+ // are no unmanaged resources.
+ // The finalizer calls Dispose with false.
+ //
+
+ //
+ // check if the object has been already disposed
+ //
+ if (m_disposed) return;
+
+ // Disable the provider.
+ m_enabled = false;
+
+ // Do most of the work under a lock to avoid shutdown race.
+
+ long registrationHandle = 0;
+ lock (EventListener.EventListenersLock)
+ {
+ // Double check
+ if (m_disposed)
+ return;
+
+ registrationHandle = m_regHandle;
+ m_regHandle = 0;
+ m_disposed = true;
+ }
+
+ // We do the Unregistration outside the EventListenerLock because there is a lock
+ // inside the ETW routines. This lock is taken before ETW issues commands
+ // Thus the ETW lock gets taken first and then our EventListenersLock gets taken
+ // in SendCommand(), and also here. If we called EventUnregister after taking
+ // the EventListenersLock then the take-lock order is reversed and we can have
+ // deadlocks in race conditions (dispose racing with an ETW command).
+ //
+ // We solve by Unregistering after releasing the EventListenerLock.
+ if (registrationHandle != 0)
+ EventUnregister(registrationHandle);
+
+ }
+
+ /// <summary>
+ /// This method deregisters the controlGuid of this class with ETW.
+ ///
+ /// </summary>
+ public virtual void Close()
+ {
+ Dispose();
+ }
+
+ ~EventProvider()
+ {
+ Dispose(false);
+ }
+
+ // <SecurityKernel Critical="True" Ring="0">
+ // <UsesUnsafeCode Name="Parameter filterData of type: Void*" />
+ // <UsesUnsafeCode Name="Parameter callbackContext of type: Void*" />
+ // </SecurityKernel>
+ unsafe void EtwEnableCallBack(
+ [In] ref System.Guid sourceId,
+ [In] int controlCode,
+ [In] byte setLevel,
+ [In] long anyKeyword,
+ [In] long allKeyword,
+ [In] UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData,
+ [In] void* callbackContext
+ )
+ {
+ // This is an optional callback API. We will therefore ignore any failures that happen as a
+ // result of turning on this provider as to not crash the app.
+ // EventSource has code to validate whether initialization it expected to occur actually occurred
+ try
+ {
+ ControllerCommand command = ControllerCommand.Update;
+ IDictionary<string, string> args = null;
+ bool skipFinalOnControllerCommand = false;
+ if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_ENABLE_PROVIDER)
+ {
+ m_enabled = true;
+ m_level = setLevel;
+ m_anyKeywordMask = anyKeyword;
+ m_allKeywordMask = allKeyword;
+
+ List<Tuple<SessionInfo, bool>> sessionsChanged = GetSessions();
+ foreach (var session in sessionsChanged)
+ {
+ int sessionChanged = session.Item1.sessionIdBit;
+ int etwSessionId = session.Item1.etwSessionId;
+ bool bEnabling = session.Item2;
+
+ skipFinalOnControllerCommand = true;
+ args = null; // reinitialize args for every session...
+
+ // if we get more than one session changed we have no way
+ // of knowing which one "filterData" belongs to
+ if (sessionsChanged.Count > 1)
+ filterData = null;
+
+ // read filter data only when a session is being *added*
+ byte[] data;
+ int keyIndex;
+ if (bEnabling &&
+ GetDataFromController(etwSessionId, filterData, out command, out data, out keyIndex))
+ {
+ args = new Dictionary<string, string>(4);
+ while (keyIndex < data.Length)
+ {
+ int keyEnd = FindNull(data, keyIndex);
+ int valueIdx = keyEnd + 1;
+ int valueEnd = FindNull(data, valueIdx);
+ if (valueEnd < data.Length)
+ {
+ string key = System.Text.Encoding.UTF8.GetString(data, keyIndex, keyEnd - keyIndex);
+ string value = System.Text.Encoding.UTF8.GetString(data, valueIdx, valueEnd - valueIdx);
+ args[key] = value;
+ }
+ keyIndex = valueEnd + 1;
+ }
+ }
+
+ // execute OnControllerCommand once for every session that has changed.
+ OnControllerCommand(command, args, (bEnabling ? sessionChanged : -sessionChanged), etwSessionId);
+ }
+ }
+ else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_DISABLE_PROVIDER)
+ {
+ m_enabled = false;
+ m_level = 0;
+ m_anyKeywordMask = 0;
+ m_allKeywordMask = 0;
+ m_liveSessions = null;
+ }
+ else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_CAPTURE_STATE)
+ {
+ command = ControllerCommand.SendManifest;
+ }
+ else
+ return; // per spec you ignore commands you don't recognize.
+
+ if (!skipFinalOnControllerCommand)
+ OnControllerCommand(command, args, 0, 0);
+ }
+ catch (Exception)
+ {
+ // We want to ignore any failures that happen as a result of turning on this provider as to
+ // not crash the app.
+ }
+ }
+
+ // New in CLR4.0
+ protected virtual void OnControllerCommand(ControllerCommand command, IDictionary<string, string> arguments, int sessionId, int etwSessionId) { }
+ protected EventLevel Level { get { return (EventLevel)m_level; } set { m_level = (byte)value; } }
+ protected EventKeywords MatchAnyKeyword { get { return (EventKeywords)m_anyKeywordMask; } set { m_anyKeywordMask = unchecked((long)value); } }
+ protected EventKeywords MatchAllKeyword { get { return (EventKeywords)m_allKeywordMask; } set { m_allKeywordMask = unchecked((long)value); } }
+
+ private static int FindNull(byte[] buffer, int idx)
+ {
+ while (idx < buffer.Length && buffer[idx] != 0)
+ idx++;
+ return idx;
+ }
+
+ /// <summary>
+ /// Determines the ETW sessions that have been added and/or removed to the set of
+ /// sessions interested in the current provider. It does so by (1) enumerating over all
+ /// ETW sessions that enabled 'this.m_Guid' for the current process ID, and (2)
+ /// comparing the current list with a list it cached on the previous invocation.
+ ///
+ /// The return value is a list of tuples, where the SessionInfo specifies the
+ /// ETW session that was added or remove, and the bool specifies whether the
+ /// session was added or whether it was removed from the set.
+ /// </summary>
+ private List<Tuple<SessionInfo, bool>> GetSessions()
+ {
+ List<SessionInfo> liveSessionList = null;
+
+ GetSessionInfo(
+ (int etwSessionId, long matchAllKeywords, ref List<SessionInfo> sessionList) =>
+ GetSessionInfoCallback(etwSessionId, matchAllKeywords, ref sessionList),
+ ref liveSessionList);
+
+ List<Tuple<SessionInfo, bool>> changedSessionList = new List<Tuple<SessionInfo, bool>>();
+
+ // first look for sessions that have gone away (or have changed)
+ // (present in the m_liveSessions but not in the new liveSessionList)
+ if (m_liveSessions != null)
+ {
+ foreach (SessionInfo s in m_liveSessions)
+ {
+ int idx;
+ if ((idx = IndexOfSessionInList(liveSessionList, s.etwSessionId)) < 0 ||
+ (liveSessionList[idx].sessionIdBit != s.sessionIdBit))
+ changedSessionList.Add(Tuple.Create(s, false));
+
+ }
+ }
+ // next look for sessions that were created since the last callback (or have changed)
+ // (present in the new liveSessionList but not in m_liveSessions)
+ if (liveSessionList != null)
+ {
+ foreach (SessionInfo s in liveSessionList)
+ {
+ int idx;
+ if ((idx = IndexOfSessionInList(m_liveSessions, s.etwSessionId)) < 0 ||
+ (m_liveSessions[idx].sessionIdBit != s.sessionIdBit))
+ changedSessionList.Add(Tuple.Create(s, true));
+ }
+ }
+
+ m_liveSessions = liveSessionList;
+ return changedSessionList;
+ }
+
+
+ /// <summary>
+ /// This method is the callback used by GetSessions() when it calls into GetSessionInfo().
+ /// It updates a List{SessionInfo} based on the etwSessionId and matchAllKeywords that
+ /// GetSessionInfo() passes in.
+ /// </summary>
+ private static void GetSessionInfoCallback(int etwSessionId, long matchAllKeywords,
+ ref List<SessionInfo> sessionList)
+ {
+ uint sessionIdBitMask = (uint)SessionMask.FromEventKeywords(unchecked((ulong)matchAllKeywords));
+ // an ETW controller that specifies more than the mandated bit for our EventSource
+ // will be ignored...
+ if (bitcount(sessionIdBitMask) > 1)
+ return;
+
+ if (sessionList == null)
+ sessionList = new List<SessionInfo>(8);
+
+ if (bitcount(sessionIdBitMask) == 1)
+ {
+ // activity-tracing-aware etw session
+ sessionList.Add(new SessionInfo(bitindex(sessionIdBitMask) + 1, etwSessionId));
+ }
+ else
+ {
+ // legacy etw session
+ sessionList.Add(new SessionInfo(bitcount((uint)SessionMask.All) + 1, etwSessionId));
+ }
+ }
+
+ private delegate void SessionInfoCallback(int etwSessionId, long matchAllKeywords, ref List<SessionInfo> sessionList);
+
+ /// <summary>
+ /// This method enumerates over all active ETW sessions that have enabled 'this.m_Guid'
+ /// for the current process ID, calling 'action' for each session, and passing it the
+ /// ETW session and the 'AllKeywords' the session enabled for the current provider.
+ /// </summary>
+ private unsafe void GetSessionInfo(SessionInfoCallback action, ref List<SessionInfo> sessionList)
+ {
+ // We wish the EventSource package to be legal for Windows Store applications.
+ // Currently EnumerateTraceGuidsEx is not an allowed API, so we avoid its use here
+ // and use the information in the registry instead. This means that ETW controllers
+ // that do not publish their intent to the registry (basically all controllers EXCEPT
+ // TraceEventSesion) will not work properly
+
+ // However the framework version of EventSource DOES have ES_SESSION_INFO defined and thus
+ // does not have this issue.
+#if (PLATFORM_WINDOWS && (ES_SESSION_INFO || !ES_BUILD_STANDALONE))
+ int buffSize = 256; // An initial guess that probably works most of the time.
+ byte* buffer;
+ for (; ; )
+ {
+ var space = stackalloc byte[buffSize];
+ buffer = space;
+ var hr = 0;
+
+ fixed (Guid* provider = &m_providerId)
+ {
+ hr = UnsafeNativeMethods.ManifestEtw.EnumerateTraceGuidsEx(UnsafeNativeMethods.ManifestEtw.TRACE_QUERY_INFO_CLASS.TraceGuidQueryInfo,
+ provider, sizeof(Guid), buffer, buffSize, ref buffSize);
+ }
+ if (hr == 0)
+ break;
+ if (hr != 122 /* ERROR_INSUFFICIENT_BUFFER */)
+ return;
+ }
+
+ var providerInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_GUID_INFO*)buffer;
+ var providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&providerInfos[1];
+ int processId = unchecked((int)Win32Native.GetCurrentProcessId());
+ // iterate over the instances of the EventProvider in all processes
+ for (int i = 0; i < providerInfos->InstanceCount; i++)
+ {
+ if (providerInstance->Pid == processId)
+ {
+ var enabledInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_ENABLE_INFO*)&providerInstance[1];
+ // iterate over the list of active ETW sessions "listening" to the current provider
+ for (int j = 0; j < providerInstance->EnableCount; j++)
+ action(enabledInfos[j].LoggerId, enabledInfos[j].MatchAllKeyword, ref sessionList);
+ }
+ if (providerInstance->NextOffset == 0)
+ break;
+ Debug.Assert(0 <= providerInstance->NextOffset && providerInstance->NextOffset < buffSize);
+ var structBase = (byte*)providerInstance;
+ providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset];
+ }
+#else
+#if !ES_BUILD_PCL && PLATFORM_WINDOWS // TODO command arguments don't work on PCL builds...
+ // This code is only used in the Nuget Package Version of EventSource. because
+ // the code above is using APIs baned from UWP apps.
+ //
+ // TODO: In addition to only working when TraceEventSession enables the provider, this code
+ // also has a problem because TraceEvent does not clean up if the registry is stale
+ // It is unclear if it is worth keeping, but for now we leave it as it does work
+ // at least some of the time.
+
+ // Determine our session from what is in the registry.
+ string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerName + "}";
+ if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
+ regKey = @"Software" + @"\Wow6432Node" + regKey;
+ else
+ regKey = @"Software" + regKey;
+
+ var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKey);
+ if (key != null)
+ {
+ foreach (string valueName in key.GetValueNames())
+ {
+ if (valueName.StartsWith("ControllerData_Session_", StringComparison.Ordinal))
+ {
+ string strId = valueName.Substring(23); // strip of the ControllerData_Session_
+ int etwSessionId;
+ if (int.TryParse(strId, out etwSessionId))
+ {
+ // we need to assert this permission for partial trust scenarios
+ (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
+ var data = key.GetValue(valueName) as byte[];
+ if (data != null)
+ {
+ var dataAsString = System.Text.Encoding.UTF8.GetString(data);
+ int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword", StringComparison.Ordinal);
+ if (0 <= keywordIdx)
+ {
+ int startIdx = keywordIdx + 18;
+ int endIdx = dataAsString.IndexOf('\0', startIdx);
+ string keywordBitString = dataAsString.Substring(startIdx, endIdx-startIdx);
+ int keywordBit;
+ if (0 < endIdx && int.TryParse(keywordBitString, out keywordBit))
+ action(etwSessionId, 1L << keywordBit, ref sessionList);
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+#endif
+ }
+
+ /// <summary>
+ /// Returns the index of the SesisonInfo from 'sessions' that has the specified 'etwSessionId'
+ /// or -1 if the value is not present.
+ /// </summary>
+ private static int IndexOfSessionInList(List<SessionInfo> sessions, int etwSessionId)
+ {
+ if (sessions == null)
+ return -1;
+ // for non-coreclr code we could use List<T>.FindIndex(Predicate<T>), but we need this to compile
+ // on coreclr as well
+ for (int i = 0; i < sessions.Count; ++i)
+ if (sessions[i].etwSessionId == etwSessionId)
+ return i;
+
+ return -1;
+ }
+
+ /// <summary>
+ /// Gets any data to be passed from the controller to the provider. It starts with what is passed
+ /// into the callback, but unfortunately this data is only present for when the provider is active
+ /// at the time the controller issues the command. To allow for providers to activate after the
+ /// controller issued a command, we also check the registry and use that to get the data. The function
+ /// returns an array of bytes representing the data, the index into that byte array where the data
+ /// starts, and the command being issued associated with that data.
+ /// </summary>
+ private unsafe bool GetDataFromController(int etwSessionId,
+ UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, out ControllerCommand command, out byte[] data, out int dataStart)
+ {
+ data = null;
+ dataStart = 0;
+ if (filterData == null)
+ {
+#if (!ES_BUILD_PCL && !ES_BUILD_PN && PLATFORM_WINDOWS)
+ string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
+ if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
+ regKey = @"HKEY_LOCAL_MACHINE\Software" + @"\Wow6432Node" + regKey;
+ else
+ regKey = @"HKEY_LOCAL_MACHINE\Software" + regKey;
+
+ string valueName = "ControllerData_Session_" + etwSessionId.ToString(CultureInfo.InvariantCulture);
+
+ // we need to assert this permission for partial trust scenarios
+#if !CORECLR
+ (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
+#endif
+ data = Microsoft.Win32.Registry.GetValue(regKey, valueName, null) as byte[];
+ if (data != null)
+ {
+ // We only used the persisted data from the registry for updates.
+ command = ControllerCommand.Update;
+ return true;
+ }
+#endif
+ }
+ else
+ {
+ if (filterData->Ptr != 0 && 0 < filterData->Size && filterData->Size <= 1024)
+ {
+ data = new byte[filterData->Size];
+ Marshal.Copy((IntPtr)filterData->Ptr, data, 0, data.Length);
+ }
+ command = (ControllerCommand)filterData->Type;
+ return true;
+ }
+
+ command = ControllerCommand.Update;
+ return false;
+ }
+
+ /// <summary>
+ /// IsEnabled, method used to test if provider is enabled
+ /// </summary>
+ public bool IsEnabled()
+ {
+ return m_enabled;
+ }
+
+ /// <summary>
+ /// IsEnabled, method used to test if event is enabled
+ /// </summary>
+ /// <param name="level">
+ /// Level to test
+ /// </param>
+ /// <param name="keywords">
+ /// Keyword to test
+ /// </param>
+ public bool IsEnabled(byte level, long keywords)
+ {
+ //
+ // If not enabled at all, return false.
+ //
+ if (!m_enabled)
+ {
+ return false;
+ }
+
+ // This also covers the case of Level == 0.
+ if ((level <= m_level) ||
+ (m_level == 0))
+ {
+
+ //
+ // Check if Keyword is enabled
+ //
+
+ if ((keywords == 0) ||
+ (((keywords & m_anyKeywordMask) != 0) &&
+ ((keywords & m_allKeywordMask) == m_allKeywordMask)))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ public static WriteEventErrorCode GetLastWriteEventError()
+ {
+ return s_returnCode;
+ }
+
+ //
+ // Helper function to set the last error on the thread
+ //
+ private static void SetLastError(int error)
+ {
+ switch (error)
+ {
+ case UnsafeNativeMethods.ManifestEtw.ERROR_ARITHMETIC_OVERFLOW:
+ case UnsafeNativeMethods.ManifestEtw.ERROR_MORE_DATA:
+ s_returnCode = WriteEventErrorCode.EventTooBig;
+ break;
+ case UnsafeNativeMethods.ManifestEtw.ERROR_NOT_ENOUGH_MEMORY:
+ s_returnCode = WriteEventErrorCode.NoFreeBuffers;
+ break;
+ }
+ }
+
+ // <SecurityKernel Critical="True" Ring="0">
+ // <UsesUnsafeCode Name="Local intptrPtr of type: IntPtr*" />
+ // <UsesUnsafeCode Name="Local intptrPtr of type: Int32*" />
+ // <UsesUnsafeCode Name="Local longptr of type: Int64*" />
+ // <UsesUnsafeCode Name="Local uintptr of type: UInt32*" />
+ // <UsesUnsafeCode Name="Local ulongptr of type: UInt64*" />
+ // <UsesUnsafeCode Name="Local charptr of type: Char*" />
+ // <UsesUnsafeCode Name="Local byteptr of type: Byte*" />
+ // <UsesUnsafeCode Name="Local shortptr of type: Int16*" />
+ // <UsesUnsafeCode Name="Local sbyteptr of type: SByte*" />
+ // <UsesUnsafeCode Name="Local ushortptr of type: UInt16*" />
+ // <UsesUnsafeCode Name="Local floatptr of type: Single*" />
+ // <UsesUnsafeCode Name="Local doubleptr of type: Double*" />
+ // <UsesUnsafeCode Name="Local boolptr of type: Boolean*" />
+ // <UsesUnsafeCode Name="Local guidptr of type: Guid*" />
+ // <UsesUnsafeCode Name="Local decimalptr of type: Decimal*" />
+ // <UsesUnsafeCode Name="Local booleanptr of type: Boolean*" />
+ // <UsesUnsafeCode Name="Parameter dataDescriptor of type: EventData*" />
+ // <UsesUnsafeCode Name="Parameter dataBuffer of type: Byte*" />
+ // </SecurityKernel>
+ private static unsafe object EncodeObject(ref object data, ref EventData* dataDescriptor, ref byte* dataBuffer, ref uint totalEventSize)
+ /*++
+
+ Routine Description:
+
+ This routine is used by WriteEvent to unbox the object type and
+ to fill the passed in ETW data descriptor.
+
+ Arguments:
+
+ data - argument to be decoded
+
+ dataDescriptor - pointer to the descriptor to be filled (updated to point to the next empty entry)
+
+ dataBuffer - storage buffer for storing user data, needed because cant get the address of the object
+ (updated to point to the next empty entry)
+
+ Return Value:
+
+ null if the object is a basic type other than string or byte[]. String otherwise
+
+ --*/
+ {
+ Again:
+ dataDescriptor->Reserved = 0;
+
+ string sRet = data as string;
+ byte[] blobRet = null;
+
+ if (sRet != null)
+ {
+ dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
+ }
+ else if ((blobRet = data as byte[]) != null)
+ {
+ // first store array length
+ *(int*)dataBuffer = blobRet.Length;
+ dataDescriptor->Ptr = (ulong)dataBuffer;
+ dataDescriptor->Size = 4;
+ totalEventSize += dataDescriptor->Size;
+
+ // then the array parameters
+ dataDescriptor++;
+ dataBuffer += s_basicTypeAllocationBufferSize;
+ dataDescriptor->Size = (uint)blobRet.Length;
+ }
+ else if (data is IntPtr)
+ {
+ dataDescriptor->Size = (uint)sizeof(IntPtr);
+ IntPtr* intptrPtr = (IntPtr*)dataBuffer;
+ *intptrPtr = (IntPtr)data;
+ dataDescriptor->Ptr = (ulong)intptrPtr;
+ }
+ else if (data is int)
+ {
+ dataDescriptor->Size = (uint)sizeof(int);
+ int* intptr = (int*)dataBuffer;
+ *intptr = (int)data;
+ dataDescriptor->Ptr = (ulong)intptr;
+ }
+ else if (data is long)
+ {
+ dataDescriptor->Size = (uint)sizeof(long);
+ long* longptr = (long*)dataBuffer;
+ *longptr = (long)data;
+ dataDescriptor->Ptr = (ulong)longptr;
+ }
+ else if (data is uint)
+ {
+ dataDescriptor->Size = (uint)sizeof(uint);
+ uint* uintptr = (uint*)dataBuffer;
+ *uintptr = (uint)data;
+ dataDescriptor->Ptr = (ulong)uintptr;
+ }
+ else if (data is UInt64)
+ {
+ dataDescriptor->Size = (uint)sizeof(ulong);
+ UInt64* ulongptr = (ulong*)dataBuffer;
+ *ulongptr = (ulong)data;
+ dataDescriptor->Ptr = (ulong)ulongptr;
+ }
+ else if (data is char)
+ {
+ dataDescriptor->Size = (uint)sizeof(char);
+ char* charptr = (char*)dataBuffer;
+ *charptr = (char)data;
+ dataDescriptor->Ptr = (ulong)charptr;
+ }
+ else if (data is byte)
+ {
+ dataDescriptor->Size = (uint)sizeof(byte);
+ byte* byteptr = (byte*)dataBuffer;
+ *byteptr = (byte)data;
+ dataDescriptor->Ptr = (ulong)byteptr;
+ }
+ else if (data is short)
+ {
+ dataDescriptor->Size = (uint)sizeof(short);
+ short* shortptr = (short*)dataBuffer;
+ *shortptr = (short)data;
+ dataDescriptor->Ptr = (ulong)shortptr;
+ }
+ else if (data is sbyte)
+ {
+ dataDescriptor->Size = (uint)sizeof(sbyte);
+ sbyte* sbyteptr = (sbyte*)dataBuffer;
+ *sbyteptr = (sbyte)data;
+ dataDescriptor->Ptr = (ulong)sbyteptr;
+ }
+ else if (data is ushort)
+ {
+ dataDescriptor->Size = (uint)sizeof(ushort);
+ ushort* ushortptr = (ushort*)dataBuffer;
+ *ushortptr = (ushort)data;
+ dataDescriptor->Ptr = (ulong)ushortptr;
+ }
+ else if (data is float)
+ {
+ dataDescriptor->Size = (uint)sizeof(float);
+ float* floatptr = (float*)dataBuffer;
+ *floatptr = (float)data;
+ dataDescriptor->Ptr = (ulong)floatptr;
+ }
+ else if (data is double)
+ {
+ dataDescriptor->Size = (uint)sizeof(double);
+ double* doubleptr = (double*)dataBuffer;
+ *doubleptr = (double)data;
+ dataDescriptor->Ptr = (ulong)doubleptr;
+ }
+ else if (data is bool)
+ {
+ // WIN32 Bool is 4 bytes
+ dataDescriptor->Size = 4;
+ int* intptr = (int*)dataBuffer;
+ if (((bool)data))
+ {
+ *intptr = 1;
+ }
+ else
+ {
+ *intptr = 0;
+ }
+ dataDescriptor->Ptr = (ulong)intptr;
+ }
+ else if (data is Guid)
+ {
+ dataDescriptor->Size = (uint)sizeof(Guid);
+ Guid* guidptr = (Guid*)dataBuffer;
+ *guidptr = (Guid)data;
+ dataDescriptor->Ptr = (ulong)guidptr;
+ }
+ else if (data is decimal)
+ {
+ dataDescriptor->Size = (uint)sizeof(decimal);
+ decimal* decimalptr = (decimal*)dataBuffer;
+ *decimalptr = (decimal)data;
+ dataDescriptor->Ptr = (ulong)decimalptr;
+ }
+ else if (data is DateTime)
+ {
+ const long UTCMinTicks = 504911232000000000;
+ long dateTimeTicks = 0;
+ // We cannot translate dates sooner than 1/1/1601 in UTC.
+ // To avoid getting an ArgumentOutOfRangeException we compare with 1/1/1601 DateTime ticks
+ if (((DateTime)data).Ticks > UTCMinTicks)
+ dateTimeTicks = ((DateTime)data).ToFileTimeUtc();
+ dataDescriptor->Size = (uint)sizeof(long);
+ long* longptr = (long*)dataBuffer;
+ *longptr = dateTimeTicks;
+ dataDescriptor->Ptr = (ulong)longptr;
+ }
+ else
+ {
+ if (data is System.Enum)
+ {
+ Type underlyingType = Enum.GetUnderlyingType(data.GetType());
+ if (underlyingType == typeof(int))
+ {
+#if !ES_BUILD_PCL
+ data = ((IConvertible)data).ToInt32(null);
+#else
+ data = (int)data;
+#endif
+ goto Again;
+ }
+ else if (underlyingType == typeof(long))
+ {
+#if !ES_BUILD_PCL
+ data = ((IConvertible)data).ToInt64(null);
+#else
+ data = (long)data;
+#endif
+ goto Again;
+ }
+ }
+
+ // To our eyes, everything else is a just a string
+ if (data == null)
+ sRet = "";
+ else
+ sRet = data.ToString();
+ dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
+ }
+
+ totalEventSize += dataDescriptor->Size;
+
+ // advance buffers
+ dataDescriptor++;
+ dataBuffer += s_basicTypeAllocationBufferSize;
+
+ return (object)sRet ?? (object)blobRet;
+ }
+
+ /// <summary>
+ /// WriteEvent, method to write a parameters with event schema properties
+ /// </summary>
+ /// <param name="eventDescriptor">
+ /// Event Descriptor for this event.
+ /// </param>
+ /// <param name="activityID">
+ /// A pointer to the activity ID GUID to log
+ /// </param>
+ /// <param name="childActivityID">
+ /// childActivityID is marked as 'related' to the current activity ID.
+ /// </param>
+ /// <param name="eventPayload">
+ /// Payload for the ETW event.
+ /// </param>
+ // <SecurityKernel Critical="True" Ring="0">
+ // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventWrite(System.Int64,EventDescriptor&,System.UInt32,System.Void*):System.UInt32" />
+ // <UsesUnsafeCode Name="Local dataBuffer of type: Byte*" />
+ // <UsesUnsafeCode Name="Local pdata of type: Char*" />
+ // <UsesUnsafeCode Name="Local userData of type: EventData*" />
+ // <UsesUnsafeCode Name="Local userDataPtr of type: EventData*" />
+ // <UsesUnsafeCode Name="Local currentBuffer of type: Byte*" />
+ // <UsesUnsafeCode Name="Local v0 of type: Char*" />
+ // <UsesUnsafeCode Name="Local v1 of type: Char*" />
+ // <UsesUnsafeCode Name="Local v2 of type: Char*" />
+ // <UsesUnsafeCode Name="Local v3 of type: Char*" />
+ // <UsesUnsafeCode Name="Local v4 of type: Char*" />
+ // <UsesUnsafeCode Name="Local v5 of type: Char*" />
+ // <UsesUnsafeCode Name="Local v6 of type: Char*" />
+ // <UsesUnsafeCode Name="Local v7 of type: Char*" />
+ // <ReferencesCritical Name="Method: EncodeObject(Object&, EventData*, Byte*):String" Ring="1" />
+ // </SecurityKernel>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Performance-critical code")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
+ internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityID, Guid* childActivityID, params object[] eventPayload)
+ {
+ int status = 0;
+
+ if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords))
+ {
+ int argCount = 0;
+ unsafe
+ {
+ argCount = eventPayload.Length;
+
+ if (argCount > s_etwMaxNumberArguments)
+ {
+ s_returnCode = WriteEventErrorCode.TooManyArgs;
+ return false;
+ }
+
+ uint totalEventSize = 0;
+ int index;
+ int refObjIndex = 0;
+ List<int> refObjPosition = new List<int>(s_etwAPIMaxRefObjCount);
+ List<object> dataRefObj = new List<object>(s_etwAPIMaxRefObjCount);
+ EventData* userData = stackalloc EventData[2 * argCount];
+ for (int i = 0; i < 2 * argCount; i++)
+ userData[i] = default(EventData);
+ EventData* userDataPtr = (EventData*)userData;
+ byte* dataBuffer = stackalloc byte[s_basicTypeAllocationBufferSize * 2 * argCount]; // Assume 16 chars for non-string argument
+ byte* currentBuffer = dataBuffer;
+
+ //
+ // The loop below goes through all the arguments and fills in the data
+ // descriptors. For strings save the location in the dataString array.
+ // Calculates the total size of the event by adding the data descriptor
+ // size value set in EncodeObject method.
+ //
+ bool hasNonStringRefArgs = false;
+ for (index = 0; index < eventPayload.Length; index++)
+ {
+ if (eventPayload[index] != null)
+ {
+ object supportedRefObj;
+ supportedRefObj = EncodeObject(ref eventPayload[index], ref userDataPtr, ref currentBuffer, ref totalEventSize);
+
+ if (supportedRefObj != null)
+ {
+ // EncodeObject advanced userDataPtr to the next empty slot
+ int idx = (int)(userDataPtr - userData - 1);
+ if (!(supportedRefObj is string))
+ {
+ if (eventPayload.Length + idx + 1 - index > s_etwMaxNumberArguments)
+ {
+ s_returnCode = WriteEventErrorCode.TooManyArgs;
+ return false;
+ }
+ hasNonStringRefArgs = true;
+ }
+ dataRefObj.Add(supportedRefObj);
+ refObjPosition.Add(idx);
+ refObjIndex++;
+ }
+ }
+ else
+ {
+ s_returnCode = WriteEventErrorCode.NullInput;
+ return false;
+ }
+ }
+
+ // update argCount based on actual number of arguments written to 'userData'
+ argCount = (int)(userDataPtr - userData);
+
+ if (totalEventSize > s_traceEventMaximumSize)
+ {
+ s_returnCode = WriteEventErrorCode.EventTooBig;
+ return false;
+ }
+
+ // the optimized path (using "fixed" instead of allocating pinned GCHandles
+ if (!hasNonStringRefArgs && (refObjIndex < s_etwAPIMaxRefObjCount))
+ {
+ // Fast path: at most 8 string arguments
+
+ // ensure we have at least s_etwAPIMaxStringCount in dataString, so that
+ // the "fixed" statement below works
+ while (refObjIndex < s_etwAPIMaxRefObjCount)
+ {
+ dataRefObj.Add(null);
+ ++refObjIndex;
+ }
+
+ //
+ // now fix any string arguments and set the pointer on the data descriptor
+ //
+ fixed (char* v0 = (string)dataRefObj[0], v1 = (string)dataRefObj[1], v2 = (string)dataRefObj[2], v3 = (string)dataRefObj[3],
+ v4 = (string)dataRefObj[4], v5 = (string)dataRefObj[5], v6 = (string)dataRefObj[6], v7 = (string)dataRefObj[7])
+ {
+ userDataPtr = (EventData*)userData;
+ if (dataRefObj[0] != null)
+ {
+ userDataPtr[refObjPosition[0]].Ptr = (ulong)v0;
+ }
+ if (dataRefObj[1] != null)
+ {
+ userDataPtr[refObjPosition[1]].Ptr = (ulong)v1;
+ }
+ if (dataRefObj[2] != null)
+ {
+ userDataPtr[refObjPosition[2]].Ptr = (ulong)v2;
+ }
+ if (dataRefObj[3] != null)
+ {
+ userDataPtr[refObjPosition[3]].Ptr = (ulong)v3;
+ }
+ if (dataRefObj[4] != null)
+ {
+ userDataPtr[refObjPosition[4]].Ptr = (ulong)v4;
+ }
+ if (dataRefObj[5] != null)
+ {
+ userDataPtr[refObjPosition[5]].Ptr = (ulong)v5;
+ }
+ if (dataRefObj[6] != null)
+ {
+ userDataPtr[refObjPosition[6]].Ptr = (ulong)v6;
+ }
+ if (dataRefObj[7] != null)
+ {
+ userDataPtr[refObjPosition[7]].Ptr = (ulong)v7;
+ }
+
+ status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, eventHandle, activityID, childActivityID, argCount, userData);
+ }
+ }
+ else
+ {
+ // Slow path: use pinned handles
+ userDataPtr = (EventData*)userData;
+
+ GCHandle[] rgGCHandle = new GCHandle[refObjIndex];
+ for (int i = 0; i < refObjIndex; ++i)
+ {
+ // below we still use "fixed" to avoid taking dependency on the offset of the first field
+ // in the object (the way we would need to if we used GCHandle.AddrOfPinnedObject)
+ rgGCHandle[i] = GCHandle.Alloc(dataRefObj[i], GCHandleType.Pinned);
+ if (dataRefObj[i] is string)
+ {
+ fixed (char* p = (string)dataRefObj[i])
+ userDataPtr[refObjPosition[i]].Ptr = (ulong)p;
+ }
+ else
+ {
+ fixed (byte* p = (byte[])dataRefObj[i])
+ userDataPtr[refObjPosition[i]].Ptr = (ulong)p;
+ }
+ }
+
+ status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, eventHandle, activityID, childActivityID, argCount, userData);
+
+ for (int i = 0; i < refObjIndex; ++i)
+ {
+ rgGCHandle[i].Free();
+ }
+ }
+ }
+ }
+
+ if (status != 0)
+ {
+ SetLastError((int)status);
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// WriteEvent, method to be used by generated code on a derived class
+ /// </summary>
+ /// <param name="eventDescriptor">
+ /// Event Descriptor for this event.
+ /// </param>
+ /// <param name="activityID">
+ /// A pointer to the activity ID to log
+ /// </param>
+ /// <param name="childActivityID">
+ /// If this event is generating a child activity (WriteEventTransfer related activity) this is child activity
+ /// This can be null for events that do not generate a child activity.
+ /// </param>
+ /// <param name="dataCount">
+ /// number of event descriptors
+ /// </param>
+ /// <param name="data">
+ /// pointer do the event data
+ /// </param>
+ // <SecurityKernel Critical="True" Ring="0">
+ // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventWrite(System.Int64,EventDescriptor&,System.UInt32,System.Void*):System.UInt32" />
+ // </SecurityKernel>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
+ internal protected unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityID, Guid* childActivityID, int dataCount, IntPtr data)
+ {
+ if (childActivityID != null)
+ {
+ // activity transfers are supported only for events that specify the Send or Receive opcode
+ Debug.Assert((EventOpcode)eventDescriptor.Opcode == EventOpcode.Send ||
+ (EventOpcode)eventDescriptor.Opcode == EventOpcode.Receive ||
+ (EventOpcode)eventDescriptor.Opcode == EventOpcode.Start ||
+ (EventOpcode)eventDescriptor.Opcode == EventOpcode.Stop);
+ }
+
+ int status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, eventHandle, activityID, childActivityID, dataCount, (EventData*)data);
+
+ if (status != 0)
+ {
+ SetLastError(status);
+ return false;
+ }
+ return true;
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
+ internal unsafe bool WriteEventRaw(
+ ref EventDescriptor eventDescriptor,
+ IntPtr eventHandle,
+ Guid* activityID,
+ Guid* relatedActivityID,
+ int dataCount,
+ IntPtr data)
+ {
+ int status;
+
+ status = m_eventProvider.EventWriteTransferWrapper(
+ m_regHandle,
+ ref eventDescriptor,
+ eventHandle,
+ activityID,
+ relatedActivityID,
+ dataCount,
+ (EventData*)data);
+
+ if (status != 0)
+ {
+ SetLastError(status);
+ return false;
+ }
+ return true;
+ }
+
+
+ // These are look-alikes to the Manifest based ETW OS APIs that have been shimmed to work
+ // either with Manifest ETW or Classic ETW (if Manifest based ETW is not available).
+ private unsafe uint EventRegister(EventSource eventSource, UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback)
+ {
+ m_providerName = eventSource.Name;
+ m_providerId = eventSource.Guid;
+ m_etwCallback = enableCallback;
+ return m_eventProvider.EventRegister(eventSource, enableCallback, null, ref m_regHandle);
+ }
+
+ private uint EventUnregister(long registrationHandle)
+ {
+ return m_eventProvider.EventUnregister(registrationHandle);
+ }
+
+ static int[] nibblebits = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
+ private static int bitcount(uint n)
+ {
+ int count = 0;
+ for (; n != 0; n = n >> 4)
+ count += nibblebits[n & 0x0f];
+ return count;
+ }
+ private static int bitindex(uint n)
+ {
+ Debug.Assert(bitcount(n) == 1);
+ int idx = 0;
+ while ((n & (1 << idx)) == 0)
+ idx++;
+ return idx;
+ }
+ }
+
+#if PLATFORM_WINDOWS
+
+ // A wrapper around the ETW-specific API calls.
+ internal sealed class EtwEventProvider : IEventProvider
+ {
+ // Register an event provider.
+ unsafe uint IEventProvider.EventRegister(
+ EventSource eventSource,
+ UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback,
+ void* callbackContext,
+ ref long registrationHandle)
+ {
+ Guid providerId = eventSource.Guid;
+ return UnsafeNativeMethods.ManifestEtw.EventRegister(
+ ref providerId,
+ enableCallback,
+ callbackContext,
+ ref registrationHandle);
+ }
+
+ // Unregister an event provider.
+ uint IEventProvider.EventUnregister(long registrationHandle)
+ {
+ return UnsafeNativeMethods.ManifestEtw.EventUnregister(registrationHandle);
+ }
+
+ // Write an event.
+ unsafe int IEventProvider.EventWriteTransferWrapper(
+ long registrationHandle,
+ ref EventDescriptor eventDescriptor,
+ IntPtr eventHandle,
+ Guid* activityId,
+ Guid* relatedActivityId,
+ int userDataCount,
+ EventProvider.EventData* userData)
+ {
+ return UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(
+ registrationHandle,
+ ref eventDescriptor,
+ activityId,
+ relatedActivityId,
+ userDataCount,
+ userData);
+ }
+
+ // Get or set the per-thread activity ID.
+ int IEventProvider.EventActivityIdControl(UnsafeNativeMethods.ManifestEtw.ActivityControl ControlCode, ref Guid ActivityId)
+ {
+ return UnsafeNativeMethods.ManifestEtw.EventActivityIdControl(
+ ControlCode,
+ ref ActivityId);
+ }
+
+ // Define an EventPipeEvent handle.
+ unsafe IntPtr IEventProvider.DefineEventHandle(uint eventID, string eventName, Int64 keywords, uint eventVersion, uint level, byte *pMetadata, uint metadataLength)
+ {
+ throw new System.NotSupportedException();
+ }
+ }
+
+#elif !FEATURE_PERFTRACING
+
+ internal sealed class NoOpEventProvider : IEventProvider
+ {
+ unsafe uint IEventProvider.EventRegister(
+ EventSource eventSource,
+ UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback,
+ void* callbackContext,
+ ref long registrationHandle)
+ {
+ return 0;
+ }
+
+ uint IEventProvider.EventUnregister(long registrationHandle)
+ {
+ return 0;
+ }
+
+ unsafe int IEventProvider.EventWriteTransferWrapper(
+ long registrationHandle,
+ ref EventDescriptor eventDescriptor,
+ IntPtr eventHandle,
+ Guid* activityId,
+ Guid* relatedActivityId,
+ int userDataCount,
+ EventProvider.EventData* userData)
+ {
+ return 0;
+ }
+
+ int IEventProvider.EventActivityIdControl(UnsafeNativeMethods.ManifestEtw.ActivityControl ControlCode, ref Guid ActivityId)
+ {
+ return 0;
+ }
+
+ // Define an EventPipeEvent handle.
+ unsafe IntPtr IEventProvider.DefineEventHandle(uint eventID, string eventName, Int64 keywords, uint eventVersion, uint level, byte *pMetadata, uint metadataLength)
+ {
+ throw new System.NotSupportedException();
+ }
+ }
+
+#endif
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
new file mode 100644
index 0000000000..1836a1f27d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
@@ -0,0 +1,5859 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in.
+// It is available from http://www.codeplex.com/hyperAddin
+#if ES_BUILD_STANDALONE
+#define FEATURE_MANAGED_ETW_CHANNELS
+// #define FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+#endif
+
+/* DESIGN NOTES DESIGN NOTES DESIGN NOTES DESIGN NOTES */
+// DESIGN NOTES
+// Over the years EventSource has become more complex and so it is important to understand
+// the basic structure of the code to insure that it does not grow more complex.
+//
+// Basic Model
+//
+// PRINCIPLE: EventSource - ETW decoupling
+//
+// Conceptually and EventSouce is something takes event logging data from the source methods
+// To the EventListener that can subscribe them. Note that CONCEPTUALLY EVENTSOURCES DON'T
+// KNOW ABOUT ETW!. The MODEL of the system is that there is a special EventListener Which
+// we will call the EtwEventListener, that forwards commands from ETW to EventSources and
+// listeners to the EventSources and forwards on those events to ETW. THus the model should
+// be that you DON'T NEED ETW.
+//
+// Now in actual practice, EventSouce have rather intimate knowledge of ETW and send events
+// to it directly, but this can be VIEWED AS AN OPTIMIZATION.
+//
+// Basic Event Data Flow:
+//
+// There are two ways for event Data to enter the system
+// 1) WriteEvent* and friends. This is called the 'contract' based approach because
+// you write a method per event which forms a contract that is know at compile time.
+// In this scheme each event is given an EVENTID (small integer). which is its identity
+// 2) Write<T> methods. This is called the 'dynamic' approach because new events
+// can be created on the fly. Event identity is determined by the event NAME, and these
+// are not quite as efficient at runtime since you have at least a hash table lookup
+// on every event write.
+//
+// EventSource-EventListener transfer fully support both ways of writing events (either contract
+// based (WriteEvent*) or dynamic (Write<T>). Both way fully support the same set of data
+// types. It is suggested, however, that you use the contract based approach when the event scheme
+// is known at compile time (that is whenever possible). It is more efficient, but more importantly
+// it makes the contract very explicit, and centralizes all policy about logging. These are good
+// things. The Write<T> API is really meant for more ad-hoc
+//
+// Allowed Data.
+//
+// Note that EventSource-EventListeners have a conceptual serialization-deserialization that happens
+// during the transfer. In particular object identity is not preserved, some objects are morphed,
+// and not all data types are supported. In particular you can pass
+//
+// A Valid type to log to an EventSource include
+// * Primitive data types
+// * IEnumerable<T> of valid types T (this include arrays) (* New for V4.6)
+// * Explicitly Opted in class or struct with public property Getters over Valid types. (* New for V4.6)
+//
+// This set of types is roughly a generalization of JSON support (Basically primitives, bags, and arrays).
+//
+// Explicitly allowed structs include (* New for V4.6)
+// * Marked with the EventData attribute
+// * implicitly defined (e.g the C# new {x = 3, y = 5} syntax)
+// * KeyValuePair<K,V> (thus dictionaries can be passed since they are an IEnumerable of KeyValuePair)
+//
+// When classes are returned in an EventListener, what is returned is something that implements
+// IDictionary<string, T>. Thus when objects are passed to an EventSource they are transformed
+// into a key-value bag (the IDictionary<string, T>) for consumption in the listener. These
+// are obvious NOT the original objects.
+//
+// ETWserialization formats:
+//
+// As mentioned conceptually EventSource's send data to EventListeners and there is a conceptual
+// copy/morph of that data as described above. In addition the .NET framework supports a conceptual
+// ETWListener that will send the data to then ETW stream. If you use this feature, the data needs
+// to be serialized in a way that ETW supports. ETW supports the following serialization formats
+//
+// 1) Manifest Based serialization.
+// 2) SelfDescribing serialization (TraceLogging style in the TraceLogging directory)
+//
+// A key factor is that the Write<T> method, which support on the fly definition of events, can't
+// support the manifest based serialization because the manifest needs the schema of all events
+// to be known before any events are emitted. This implies the following
+//
+// If you use Write<T> and the output goes to ETW it will use the SelfDescribing format.
+// If you use the EventSource(string) constructor for an eventSource (in which you don't
+// create a subclass), the default is also to use Self-Describing serialization. In addition
+// you can use the EventSoruce(EventSourceSettings) constructor to also explicitly specify
+// Self-Describing serialization format. These effect the WriteEvent* APIs going to ETW.
+//
+// Note that none of this ETW serialization logic affects EventListeners. Only the ETW listener.
+//
+// *************************************************************************************
+// *** INTERNALS: Event Propagation
+//
+// Data enters the system either though
+//
+// 1) A user defined method in the user defined subclass of EventSource which calls
+// A) A typesafe type specific overload of WriteEvent(ID, ...) e.g. WriteEvent(ID, string, string)
+// * which calls into the unsafe WriteEventCore(ID COUNT EventData*) WriteEventWithRelatedActivityIdCore()
+// B) The typesafe overload WriteEvent(ID, object[]) which calls the private helper WriteEventVarargs(ID, Guid* object[])
+// C) Directly into the unsafe WriteEventCore(ID, COUNT EventData*) or WriteEventWithRelatedActivityIdCore()
+//
+// All event data eventually flows to one of
+// * WriteEventWithRelatedActivityIdCore(ID, Guid*, COUNT, EventData*)
+// * WriteEventVarargs(ID, Guid*, object[])
+//
+// 2) A call to one of the overloads of Write<T>. All these overloads end up in
+// * WriteImpl<T>(EventName, Options, Data, Guid*, Guid*)
+//
+// On output there are the following routines
+// Writing to all listeners that are NOT ETW, we have the following routines
+// * WriteToAllListeners(ID, Guid*, Guid*, COUNT, EventData*)
+// * WriteToAllListeners(ID, Guid*, Guid*, object[])
+// * WriteToAllListeners(NAME, Guid*, Guid*, EventPayload)
+//
+// EventPayload is the internal type that implements the IDictionary<string, object> interface
+// The EventListeners will pass back for serialized classes for nested object, but
+// WriteToAllListeners(NAME, Guid*, Guid*, EventPayload) unpacks this uses the fields as if they
+// were parameters to a method.
+//
+// The first two are used for the WriteEvent* case, and the later is used for the Write<T> case.
+//
+// Writing to ETW, Manifest Based
+// EventProvider.WriteEvent(EventDescriptor, Guid*, COUNT, EventData*)
+// EventProvider.WriteEvent(EventDescriptor, Guid*, object[])
+// Writing to ETW, Self-Describing format
+// WriteMultiMerge(NAME, Options, Types, EventData*)
+// WriteMultiMerge(NAME, Options, Types, object[])
+// WriteImpl<T> has logic that knows how to serialize (like WriteMultiMerge) but also knows
+// will write it to
+//
+// All ETW writes eventually call
+// EventWriteTransfer (native PINVOKE wrapper)
+// EventWriteTransferWrapper (fixes compat problem if you pass null as the related activityID)
+// EventProvider.WriteEventRaw - sets last error
+// EventSource.WriteEventRaw - Does EventSource exception handling logic
+// WriteMultiMerge
+// WriteImpl<T>
+// EventProvider.WriteEvent(EventDescriptor, Guid*, COUNT, EventData*)
+// EventProvider.WriteEvent(EventDescriptor, Guid*, object[])
+//
+// Serialization: We have a bit of a hodge-podge of serializers right now. Only the one for ETW knows
+// how to deal with nested classes or arrays. I will call this serializer the 'TypeInfo' serializer
+// since it is the TraceLoggingTypeInfo structure that knows how to do this. Effectively for a type you
+// can call one of these
+// WriteMetadata - transforms the type T into serialization meta data blob for that type
+// WriteObjectData - transforms an object of T into serialization meta data blob for that type
+// GetData - transforms an object of T into its deserialized form suitable for passing to EventListener.
+// The first two are used to serialize something for ETW. The second one is used to transform the object
+// for use by the EventListener. We also have a 'DecodeObject' method that will take a EventData* and
+// deserialize to pass to an EventListener, but it only works on primitive types (types supported in version V4.5).
+//
+// It is an important observation that while EventSource does support users directly calling with EventData*
+// blobs, we ONLY support that for the primitive types (V4.5 level support). Thus while there is a EventData*
+// path through the system it is only for some types. The object[] path is the more general (but less efficient) path.
+//
+// TODO There is cleanup needed There should be no divergence until WriteEventRaw.
+//
+// TODO: We should have a single choke point (right now we always have this parallel EventData* and object[] path. This
+// was historical (at one point we tried to pass object directly from EventSoruce to EventListener. That was always
+// fragile and a compatibility headache, but we have finally been forced into the idea that there is always a transformation.
+// This allows us to use the EventData* form to be the canonical data format in the low level APIs. This also gives us the
+// opportunity to expose this format to EventListeners in the future.
+//
+using System;
+using System.Runtime.CompilerServices;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Reflection;
+using System.Resources;
+using System.Security;
+#if !CORECLR && !ES_BUILD_PN
+using System.Security.Permissions;
+#endif // !CORECLR && !ES_BUILD_PN
+
+using System.Text;
+using System.Threading;
+using Microsoft.Win32;
+
+#if ES_BUILD_STANDALONE
+using EventDescriptor = Microsoft.Diagnostics.Tracing.EventDescriptor;
+#else
+using System.Threading.Tasks;
+#endif
+
+using Microsoft.Reflection;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if CORECLR || ES_BUILD_PN
+using Internal.Runtime.Augments;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// This class is meant to be inherited by a user-defined event source in order to define a managed
+ /// ETW provider. Please See DESIGN NOTES above for the internal architecture.
+ /// The minimal definition of an EventSource simply specifies a number of ETW event methods that
+ /// call one of the EventSource.WriteEvent overloads, <see cref="EventSource.WriteEventCore"/>,
+ /// or <see cref="EventSource.WriteEventWithRelatedActivityIdCore"/> to log them. This functionality
+ /// is sufficient for many users.
+ /// <para>
+ /// To achieve more control over the ETW provider manifest exposed by the event source type, the
+ /// [<see cref="EventAttribute"/>] attributes can be specified for the ETW event methods.
+ /// </para><para>
+ /// For very advanced EventSources, it is possible to intercept the commands being given to the
+ /// eventSource and change what filtering is done (see EventListener.EnableEvents and
+ /// <see cref="EventListener.DisableEvents"/>) or cause actions to be performed by the eventSource,
+ /// e.g. dumping a data structure (see EventSource.SendCommand and
+ /// <see cref="EventSource.OnEventCommand"/>).
+ /// </para><para>
+ /// The eventSources can be turned on with Windows ETW controllers (e.g. logman), immediately.
+ /// It is also possible to control and intercept the data dispatcher programmatically. See
+ /// <see cref="EventListener"/> for more.
+ /// </para>
+ /// </summary>
+ /// <remarks>
+ /// This is a minimal definition for a custom event source:
+ /// <code>
+ /// [EventSource(Name="Samples-Demos-Minimal")]
+ /// sealed class MinimalEventSource : EventSource
+ /// {
+ /// public static MinimalEventSource Log = new MinimalEventSource();
+ /// public void Load(long ImageBase, string Name) { WriteEvent(1, ImageBase, Name); }
+ /// public void Unload(long ImageBase) { WriteEvent(2, ImageBase); }
+ /// private MinimalEventSource() {}
+ /// }
+ /// </code>
+ /// </remarks>
+ public partial class EventSource : IDisposable
+ {
+
+#if FEATURE_EVENTSOURCE_XPLAT
+ private static readonly EventListener persistent_Xplat_Listener = XplatEventLogger.InitializePersistentListener();
+#endif //FEATURE_EVENTSOURCE_XPLAT
+
+ /// <summary>
+ /// The human-friendly name of the eventSource. It defaults to the simple name of the class
+ /// </summary>
+ public string Name { get { return m_name; } }
+ /// <summary>
+ /// Every eventSource is assigned a GUID to uniquely identify it to the system.
+ /// </summary>
+ public Guid Guid { get { return m_guid; } }
+
+ /// <summary>
+ /// Returns true if the eventSource has been enabled at all. This is the preferred test
+ /// to be performed before a relatively expensive EventSource operation.
+ /// </summary>
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ public bool IsEnabled()
+ {
+ return m_eventSourceEnabled;
+ }
+
+ /// <summary>
+ /// Returns true if events with greater than or equal 'level' and have one of 'keywords' set are enabled.
+ ///
+ /// Note that the result of this function is only an approximation on whether a particular
+ /// event is active or not. It is only meant to be used as way of avoiding expensive
+ /// computation for logging when logging is not on, therefore it sometimes returns false
+ /// positives (but is always accurate when returning false). EventSources are free to
+ /// have additional filtering.
+ /// </summary>
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ public bool IsEnabled(EventLevel level, EventKeywords keywords)
+ {
+ return IsEnabled(level, keywords, EventChannel.None);
+ }
+
+ /// <summary>
+ /// Returns true if events with greater than or equal 'level' and have one of 'keywords' set are enabled, or
+ /// if 'keywords' specifies a channel bit for a channel that is enabled.
+ ///
+ /// Note that the result of this function only an approximation on whether a particular
+ /// event is active or not. It is only meant to be used as way of avoiding expensive
+ /// computation for logging when logging is not on, therefore it sometimes returns false
+ /// positives (but is always accurate when returning false). EventSources are free to
+ /// have additional filtering.
+ /// </summary>
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ public bool IsEnabled(EventLevel level, EventKeywords keywords, EventChannel channel)
+ {
+ if (!m_eventSourceEnabled)
+ return false;
+
+ if (!IsEnabledCommon(m_eventSourceEnabled, m_level, m_matchAnyKeyword, level, keywords, channel))
+ return false;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Returns the settings for the event source instance
+ /// </summary>
+ public EventSourceSettings Settings
+ {
+ get { return m_config; }
+ }
+
+ // Manifest support
+ /// <summary>
+ /// Returns the GUID that uniquely identifies the eventSource defined by 'eventSourceType'.
+ /// This API allows you to compute this without actually creating an instance of the EventSource.
+ /// It only needs to reflect over the type.
+ /// </summary>
+ public static Guid GetGuid(Type eventSourceType)
+ {
+ if (eventSourceType == null)
+ throw new ArgumentNullException(nameof(eventSourceType));
+
+ EventSourceAttribute attrib = (EventSourceAttribute)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute));
+ string name = eventSourceType.Name;
+ if (attrib != null)
+ {
+ if (attrib.Guid != null)
+ {
+ Guid g = Guid.Empty;
+#if !ES_BUILD_AGAINST_DOTNET_V35
+ if (Guid.TryParse(attrib.Guid, out g))
+ return g;
+#else
+ try { return new Guid(attrib.Guid); }
+ catch (Exception) { }
+#endif
+ }
+
+ if (attrib.Name != null)
+ name = attrib.Name;
+ }
+
+ if (name == null)
+ {
+ throw new ArgumentException(SR.Argument_InvalidTypeName, nameof(eventSourceType));
+ }
+ return GenerateGuidFromName(name.ToUpperInvariant()); // Make it case insensitive.
+ }
+ /// <summary>
+ /// Returns the official ETW Provider name for the eventSource defined by 'eventSourceType'.
+ /// This API allows you to compute this without actually creating an instance of the EventSource.
+ /// It only needs to reflect over the type.
+ /// </summary>
+ public static string GetName(Type eventSourceType)
+ {
+ return GetName(eventSourceType, EventManifestOptions.None);
+ }
+
+ /// <summary>
+ /// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is
+ /// documented at in EventManifest Schema http://msdn.microsoft.com/en-us/library/aa384043(VS.85).aspx.
+ /// This is the preferred way of generating a manifest to be embedded in the ETW stream as it is fast and
+ /// the fact that it only includes localized entries for the current UI culture is an acceptable tradeoff.
+ /// </summary>
+ /// <param name="eventSourceType">The type of the event source class for which the manifest is generated</param>
+ /// <param name="assemblyPathToIncludeInManifest">The manifest XML fragment contains the string name of the DLL name in
+ /// which it is embedded. This parameter specifies what name will be used</param>
+ /// <returns>The XML data string</returns>
+ public static string GenerateManifest(Type eventSourceType, string assemblyPathToIncludeInManifest)
+ {
+ return GenerateManifest(eventSourceType, assemblyPathToIncludeInManifest, EventManifestOptions.None);
+ }
+ /// <summary>
+ /// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is
+ /// documented at in EventManifest Schema http://msdn.microsoft.com/en-us/library/aa384043(VS.85).aspx.
+ /// Pass EventManifestOptions.AllCultures when generating a manifest to be registered on the machine. This
+ /// ensures that the entries in the event log will be "optimally" localized.
+ /// </summary>
+ /// <param name="eventSourceType">The type of the event source class for which the manifest is generated</param>
+ /// <param name="assemblyPathToIncludeInManifest">The manifest XML fragment contains the string name of the DLL name in
+ /// which it is embedded. This parameter specifies what name will be used</param>
+ /// <param name="flags">The flags to customize manifest generation. If flags has bit OnlyIfNeededForRegistration specified
+ /// this returns null when the eventSourceType does not require explicit registration</param>
+ /// <returns>The XML data string or null</returns>
+ public static string GenerateManifest(Type eventSourceType, string assemblyPathToIncludeInManifest, EventManifestOptions flags)
+ {
+ if (eventSourceType == null)
+ throw new ArgumentNullException(nameof(eventSourceType));
+
+ byte[] manifestBytes = EventSource.CreateManifestAndDescriptors(eventSourceType, assemblyPathToIncludeInManifest, null, flags);
+ return (manifestBytes == null) ? null : Encoding.UTF8.GetString(manifestBytes, 0, manifestBytes.Length);
+ }
+
+ // EventListener support
+ /// <summary>
+ /// returns a list (IEnumerable) of all sources in the appdomain). EventListeners typically need this.
+ /// </summary>
+ /// <returns></returns>
+ public static IEnumerable<EventSource> GetSources()
+ {
+ var ret = new List<EventSource>();
+ lock (EventListener.EventListenersLock)
+ {
+ foreach (WeakReference eventSourceRef in EventListener.s_EventSources)
+ {
+ EventSource eventSource = eventSourceRef.Target as EventSource;
+ if (eventSource != null && !eventSource.IsDisposed)
+ ret.Add(eventSource);
+ }
+ }
+ return ret;
+ }
+
+ /// <summary>
+ /// Send a command to a particular EventSource identified by 'eventSource'.
+ /// Calling this routine simply forwards the command to the EventSource.OnEventCommand
+ /// callback. What the EventSource does with the command and its arguments are from
+ /// that point EventSource-specific.
+ /// </summary>
+ /// <param name="eventSource">The instance of EventSource to send the command to</param>
+ /// <param name="command">A positive user-defined EventCommand, or EventCommand.SendManifest</param>
+ /// <param name="commandArguments">A set of (name-argument, value-argument) pairs associated with the command</param>
+ public static void SendCommand(EventSource eventSource, EventCommand command, IDictionary<string, string> commandArguments)
+ {
+ if (eventSource == null)
+ throw new ArgumentNullException(nameof(eventSource));
+
+ // User-defined EventCommands should not conflict with the reserved commands.
+ if ((int)command <= (int)EventCommand.Update && (int)command != (int)EventCommand.SendManifest)
+ {
+ throw new ArgumentException(SR.EventSource_InvalidCommand, nameof(command));
+ }
+
+ eventSource.SendCommand(null, 0, 0, command, true, EventLevel.LogAlways, EventKeywords.None, commandArguments);
+ }
+
+#if !ES_BUILD_STANDALONE
+ /// <summary>
+ /// This property allows EventSource code to appropriately handle as "different"
+ /// activities started on different threads that have not had an activity created on them.
+ /// </summary>
+ internal static Guid InternalCurrentThreadActivityId
+ {
+ get
+ {
+ Guid retval = CurrentThreadActivityId;
+ if (retval == Guid.Empty)
+ {
+ retval = FallbackActivityId;
+ }
+ return retval;
+ }
+ }
+
+ internal static Guid FallbackActivityId
+ {
+ get
+ {
+#pragma warning disable 612, 618
+ int threadID = AppDomain.GetCurrentThreadId();
+
+ // Managed thread IDs are more aggressively re-used than native thread IDs,
+ // so we'll use the latter...
+ return new Guid(unchecked((uint)threadID),
+ unchecked((ushort)s_currentPid), unchecked((ushort)(s_currentPid >> 16)),
+ 0x94, 0x1b, 0x87, 0xd5, 0xa6, 0x5c, 0x36, 0x64);
+#pragma warning restore 612, 618
+ }
+ }
+#endif // !ES_BUILD_STANDALONE
+
+ // Error APIs. (We don't throw by default, but you can probe for status)
+ /// <summary>
+ /// Because
+ ///
+ /// 1) Logging is often optional and thus should not generate fatal errors (exceptions)
+ /// 2) EventSources are often initialized in class constructors (which propagate exceptions poorly)
+ ///
+ /// The event source constructor does not throw exceptions. Instead we remember any exception that
+ /// was generated (it is also logged to Trace.WriteLine).
+ /// </summary>
+ public Exception ConstructionException { get { return m_constructionException; } }
+
+ /// <summary>
+ /// EventSources can have arbitrary string key-value pairs associated with them called Traits.
+ /// These traits are not interpreted by the EventSource but may be interpreted by EventListeners
+ /// (e.g. like the built in ETW listener). These traits are specified at EventSource
+ /// construction time and can be retrieved by using this GetTrait API.
+ /// </summary>
+ /// <param name="key">The key to look up in the set of key-value pairs passed to the EventSource constructor</param>
+ /// <returns>The value string associated with key. Will return null if there is no such key.</returns>
+ public string GetTrait(string key)
+ {
+ if (m_traits != null)
+ {
+ for (int i = 0; i < m_traits.Length - 1; i += 2)
+ {
+ if (m_traits[i] == key)
+ return m_traits[i + 1];
+ }
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Displays the name and GUID for the eventSource for debugging purposes.
+ /// </summary>
+ public override string ToString()
+ {
+ return SR.Format(SR.EventSource_ToString, Name, Guid);
+ }
+
+ /// <summary>
+ /// Fires when a Command (e.g. Enable) comes from a an EventListener.
+ /// </summary>
+ public event EventHandler<EventCommandEventArgs> EventCommandExecuted
+ {
+ add
+ {
+ m_eventCommandExecuted += value;
+
+ // If we have an EventHandler<EventCommandEventArgs> attached to the EventSource before the first command arrives
+ // It should get a chance to handle the deferred commands.
+ EventCommandEventArgs deferredCommands = m_deferredCommands;
+ while (deferredCommands != null)
+ {
+ value(this, deferredCommands);
+ deferredCommands = deferredCommands.nextCommand;
+ }
+ }
+ remove
+ {
+ m_eventCommandExecuted -= value;
+ }
+ }
+
+#region protected
+ /// <summary>
+ /// This is the constructor that most users will use to create their eventSource. It takes
+ /// no parameters. The ETW provider name and GUID of the EventSource are determined by the EventSource
+ /// custom attribute (so you can determine these things declaratively). If the GUID for the eventSource
+ /// is not specified in the EventSourceAttribute (recommended), it is Generated by hashing the name.
+ /// If the ETW provider name of the EventSource is not given, the name of the EventSource class is used as
+ /// the ETW provider name.
+ /// </summary>
+ protected EventSource()
+ : this(EventSourceSettings.EtwManifestEventFormat)
+ {
+ }
+
+ /// <summary>
+ /// By default calling the 'WriteEvent' methods do NOT throw on errors (they silently discard the event).
+ /// This is because in most cases users assume logging is not 'precious' and do NOT wish to have logging failures
+ /// crash the program. However for those applications where logging is 'precious' and if it fails the caller
+ /// wishes to react, setting 'throwOnEventWriteErrors' will cause an exception to be thrown if WriteEvent
+ /// fails. Note the fact that EventWrite succeeds does not necessarily mean that the event reached its destination
+ /// only that operation of writing it did not fail. These EventSources will not generate self-describing ETW events.
+ ///
+ /// For compatibility only use the EventSourceSettings.ThrowOnEventWriteErrors flag instead.
+ /// </summary>
+ // [Obsolete("Use the EventSource(EventSourceSettings) overload")]
+ protected EventSource(bool throwOnEventWriteErrors)
+ : this(EventSourceSettings.EtwManifestEventFormat | (throwOnEventWriteErrors ? EventSourceSettings.ThrowOnEventWriteErrors : 0))
+ { }
+
+ /// <summary>
+ /// Construct an EventSource with additional non-default settings (see EventSourceSettings for more)
+ /// </summary>
+ protected EventSource(EventSourceSettings settings) : this(settings, null) { }
+
+ /// <summary>
+ /// Construct an EventSource with additional non-default settings.
+ ///
+ /// Also specify a list of key-value pairs called traits (you must pass an even number of strings).
+ /// The first string is the key and the second is the value. These are not interpreted by EventSource
+ /// itself but may be interpreted the listeners. Can be fetched with GetTrait(string).
+ /// </summary>
+ /// <param name="settings">See EventSourceSettings for more.</param>
+ /// <param name="traits">A collection of key-value strings (must be an even number).</param>
+ protected EventSource(EventSourceSettings settings, params string[] traits)
+ {
+ m_config = ValidateSettings(settings);
+
+ Guid eventSourceGuid;
+ string eventSourceName;
+
+ EventMetadata[] eventDescriptors;
+ byte[] manifest;
+ GetMetadata(out eventSourceGuid, out eventSourceName, out eventDescriptors, out manifest);
+
+ if (eventSourceGuid.Equals(Guid.Empty) || eventSourceName == null)
+ {
+ var myType = this.GetType();
+ eventSourceGuid = GetGuid(myType);
+ eventSourceName = GetName(myType);
+ }
+
+ Initialize(eventSourceGuid, eventSourceName, traits);
+ }
+
+#if FEATURE_PERFTRACING
+ // Generate the serialized blobs that describe events for all strongly typed events (that is events that define strongly
+ // typed event methods. Dynamically defined events (that use Write) hare defined on the fly and are handled elsewhere.
+ private unsafe void DefineEventPipeEvents()
+ {
+ // If the EventSource is set to emit all events as TraceLogging events, skip this initialization.
+ // Events will be defined when they are emitted for the first time.
+ if(SelfDescribingEvents)
+ {
+ return;
+ }
+
+ Debug.Assert(m_eventData != null);
+ Debug.Assert(m_provider != null);
+ int cnt = m_eventData.Length;
+ for (int i = 0; i < cnt; i++)
+ {
+ uint eventID = (uint)m_eventData[i].Descriptor.EventId;
+ if (eventID == 0)
+ continue;
+
+ byte[] metadata = EventPipeMetadataGenerator.Instance.GenerateEventMetadata(m_eventData[i]);
+ uint metadataLength = (metadata != null) ? (uint)metadata.Length : 0;
+
+ string eventName = m_eventData[i].Name;
+ Int64 keywords = m_eventData[i].Descriptor.Keywords;
+ uint eventVersion = m_eventData[i].Descriptor.Version;
+ uint level = m_eventData[i].Descriptor.Level;
+
+ fixed (byte *pMetadata = metadata)
+ {
+ IntPtr eventHandle = m_provider.m_eventProvider.DefineEventHandle(
+ eventID,
+ eventName,
+ keywords,
+ eventVersion,
+ level,
+ pMetadata,
+ metadataLength);
+
+ Debug.Assert(eventHandle != IntPtr.Zero);
+ m_eventData[i].EventHandle = eventHandle;
+ }
+ }
+ }
+#endif
+
+ internal virtual void GetMetadata(out Guid eventSourceGuid, out string eventSourceName, out EventMetadata[] eventData, out byte[] manifestBytes)
+ {
+ //
+ // In ProjectN subclasses need to override this method, and return the data from their EventSourceAttribute and EventAttribute annotations.
+ // On other architectures it is a no-op.
+ //
+ // eventDescriptors needs to contain one EventDescriptor for each event; the event's ID should be the same as its index in this array.
+ // manifestBytes is a UTF-8 encoding of the ETW manifest for the type.
+ //
+ // This will be implemented by an IL rewriter, so we can't make this method abstract or the initial build of the subclass would fail.
+ //
+ eventSourceGuid = Guid.Empty;
+ eventSourceName = null;
+ eventData = null;
+ manifestBytes = null;
+
+ return;
+ }
+
+ /// <summary>
+ /// This method is called when the eventSource is updated by the controller.
+ /// </summary>
+ protected virtual void OnEventCommand(EventCommandEventArgs command) { }
+
+#pragma warning disable 1591
+ // optimized for common signatures (no args)
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId)
+ {
+ WriteEventCore(eventId, 0, null);
+ }
+
+ // optimized for common signatures (ints)
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, int arg1)
+ {
+ if (m_eventSourceEnabled)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[1];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 4;
+ descrs[0].Reserved = 0;
+ WriteEventCore(eventId, 1, descrs);
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, int arg1, int arg2)
+ {
+ if (m_eventSourceEnabled)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 4;
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)(&arg2);
+ descrs[1].Size = 4;
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, int arg1, int arg2, int arg3)
+ {
+ if (m_eventSourceEnabled)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 4;
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)(&arg2);
+ descrs[1].Size = 4;
+ descrs[1].Reserved = 0;
+ descrs[2].DataPointer = (IntPtr)(&arg3);
+ descrs[2].Size = 4;
+ descrs[2].Reserved = 0;
+ WriteEventCore(eventId, 3, descrs);
+ }
+ }
+
+ // optimized for common signatures (longs)
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, long arg1)
+ {
+ if (m_eventSourceEnabled)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[1];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 8;
+ descrs[0].Reserved = 0;
+ WriteEventCore(eventId, 1, descrs);
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, long arg1, long arg2)
+ {
+ if (m_eventSourceEnabled)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 8;
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)(&arg2);
+ descrs[1].Size = 8;
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, long arg1, long arg2, long arg3)
+ {
+ if (m_eventSourceEnabled)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 8;
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)(&arg2);
+ descrs[1].Size = 8;
+ descrs[1].Reserved = 0;
+ descrs[2].DataPointer = (IntPtr)(&arg3);
+ descrs[2].Size = 8;
+ descrs[2].Reserved = 0;
+ WriteEventCore(eventId, 3, descrs);
+ }
+ }
+
+ // optimized for common signatures (strings)
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, string arg1)
+ {
+ if (m_eventSourceEnabled)
+ {
+ if (arg1 == null) arg1 = "";
+ fixed (char* string1Bytes = arg1)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[1];
+ descrs[0].DataPointer = (IntPtr)string1Bytes;
+ descrs[0].Size = ((arg1.Length + 1) * 2);
+ descrs[0].Reserved = 0;
+ WriteEventCore(eventId, 1, descrs);
+ }
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, string arg1, string arg2)
+ {
+ if (m_eventSourceEnabled)
+ {
+ if (arg1 == null) arg1 = "";
+ if (arg2 == null) arg2 = "";
+ fixed (char* string1Bytes = arg1)
+ fixed (char* string2Bytes = arg2)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ descrs[0].DataPointer = (IntPtr)string1Bytes;
+ descrs[0].Size = ((arg1.Length + 1) * 2);
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)string2Bytes;
+ descrs[1].Size = ((arg2.Length + 1) * 2);
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, string arg1, string arg2, string arg3)
+ {
+ if (m_eventSourceEnabled)
+ {
+ if (arg1 == null) arg1 = "";
+ if (arg2 == null) arg2 = "";
+ if (arg3 == null) arg3 = "";
+ fixed (char* string1Bytes = arg1)
+ fixed (char* string2Bytes = arg2)
+ fixed (char* string3Bytes = arg3)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
+ descrs[0].DataPointer = (IntPtr)string1Bytes;
+ descrs[0].Size = ((arg1.Length + 1) * 2);
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)string2Bytes;
+ descrs[1].Size = ((arg2.Length + 1) * 2);
+ descrs[1].Reserved = 0;
+ descrs[2].DataPointer = (IntPtr)string3Bytes;
+ descrs[2].Size = ((arg3.Length + 1) * 2);
+ descrs[2].Reserved = 0;
+ WriteEventCore(eventId, 3, descrs);
+ }
+ }
+ }
+
+ // optimized for common signatures (string and ints)
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, string arg1, int arg2)
+ {
+ if (m_eventSourceEnabled)
+ {
+ if (arg1 == null) arg1 = "";
+ fixed (char* string1Bytes = arg1)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ descrs[0].DataPointer = (IntPtr)string1Bytes;
+ descrs[0].Size = ((arg1.Length + 1) * 2);
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)(&arg2);
+ descrs[1].Size = 4;
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, string arg1, int arg2, int arg3)
+ {
+ if (m_eventSourceEnabled)
+ {
+ if (arg1 == null) arg1 = "";
+ fixed (char* string1Bytes = arg1)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
+ descrs[0].DataPointer = (IntPtr)string1Bytes;
+ descrs[0].Size = ((arg1.Length + 1) * 2);
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)(&arg2);
+ descrs[1].Size = 4;
+ descrs[1].Reserved = 0;
+ descrs[2].DataPointer = (IntPtr)(&arg3);
+ descrs[2].Size = 4;
+ descrs[2].Reserved = 0;
+ WriteEventCore(eventId, 3, descrs);
+ }
+ }
+ }
+
+ // optimized for common signatures (string and longs)
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, string arg1, long arg2)
+ {
+ if (m_eventSourceEnabled)
+ {
+ if (arg1 == null) arg1 = "";
+ fixed (char* string1Bytes = arg1)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ descrs[0].DataPointer = (IntPtr)string1Bytes;
+ descrs[0].Size = ((arg1.Length + 1) * 2);
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)(&arg2);
+ descrs[1].Size = 8;
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ }
+ }
+
+ // optimized for common signatures (long and string)
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, long arg1, string arg2)
+ {
+ if (m_eventSourceEnabled)
+ {
+ if (arg2 == null) arg2 = "";
+ fixed (char* string2Bytes = arg2)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 8;
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)string2Bytes;
+ descrs[1].Size = ((arg2.Length + 1) * 2);
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ }
+ }
+
+ // optimized for common signatures (int and string)
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, int arg1, string arg2)
+ {
+ if (m_eventSourceEnabled)
+ {
+ if (arg2 == null) arg2 = "";
+ fixed (char* string2Bytes = arg2)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 4;
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)string2Bytes;
+ descrs[1].Size = ((arg2.Length + 1) * 2);
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, byte[] arg1)
+ {
+ if (m_eventSourceEnabled)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ if (arg1 == null || arg1.Length == 0)
+ {
+ int blobSize = 0;
+ descrs[0].DataPointer = (IntPtr)(&blobSize);
+ descrs[0].Size = 4;
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)(&blobSize); // valid address instead of empty content
+ descrs[1].Size = 0;
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ else
+ {
+ int blobSize = arg1.Length;
+ fixed (byte* blob = &arg1[0])
+ {
+ descrs[0].DataPointer = (IntPtr)(&blobSize);
+ descrs[0].Size = 4;
+ descrs[0].Reserved = 0;
+ descrs[1].DataPointer = (IntPtr)blob;
+ descrs[1].Size = blobSize;
+ descrs[1].Reserved = 0;
+ WriteEventCore(eventId, 2, descrs);
+ }
+ }
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, long arg1, byte[] arg2)
+ {
+ if (m_eventSourceEnabled)
+ {
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 8;
+ descrs[0].Reserved = 0;
+ if (arg2 == null || arg2.Length == 0)
+ {
+ int blobSize = 0;
+ descrs[1].DataPointer = (IntPtr)(&blobSize);
+ descrs[1].Size = 4;
+ descrs[1].Reserved = 0;
+ descrs[2].DataPointer = (IntPtr)(&blobSize); // valid address instead of empty contents
+ descrs[2].Size = 0;
+ descrs[2].Reserved = 0;
+ WriteEventCore(eventId, 3, descrs);
+ }
+ else
+ {
+ int blobSize = arg2.Length;
+ fixed (byte* blob = &arg2[0])
+ {
+ descrs[1].DataPointer = (IntPtr)(&blobSize);
+ descrs[1].Size = 4;
+ descrs[1].Reserved = 0;
+ descrs[2].DataPointer = (IntPtr)blob;
+ descrs[2].Size = blobSize;
+ descrs[2].Reserved = 0;
+ WriteEventCore(eventId, 3, descrs);
+ }
+ }
+ }
+ }
+
+#pragma warning restore 1591
+
+ /// <summary>
+ /// Used to construct the data structure to be passed to the native ETW APIs - EventWrite and EventWriteTransfer.
+ /// </summary>
+ protected internal struct EventData
+ {
+ /// <summary>
+ /// Address where the one argument lives (if this points to managed memory you must ensure the
+ /// managed object is pinned.
+ /// </summary>
+ public unsafe IntPtr DataPointer { get { return (IntPtr)(void*)m_Ptr; } set { m_Ptr = unchecked((ulong)(void*)value); } }
+
+ /// <summary>
+ /// Size of the argument referenced by DataPointer
+ /// </summary>
+ public int Size { get { return m_Size; } set { m_Size = value; } }
+
+ /// <summary>
+ /// Reserved by ETW. This property is present to ensure that we can zero it
+ /// since System.Private.CoreLib uses are not zero'd.
+ /// </summary>
+ internal int Reserved { get { return m_Reserved; } set { m_Reserved = value; } }
+
+#region private
+ /// <summary>
+ /// Initializes the members of this EventData object to point at a previously-pinned
+ /// tracelogging-compatible metadata blob.
+ /// </summary>
+ /// <param name="pointer">Pinned tracelogging-compatible metadata blob.</param>
+ /// <param name="size">The size of the metadata blob.</param>
+ /// <param name="reserved">Value for reserved: 2 for per-provider metadata, 1 for per-event metadata</param>
+ internal unsafe void SetMetadata(byte* pointer, int size, int reserved)
+ {
+ this.m_Ptr = (ulong)pointer;
+ this.m_Size = size;
+ this.m_Reserved = reserved; // Mark this descriptor as containing tracelogging-compatible metadata.
+ }
+
+ //Important, we pass this structure directly to the Win32 EventWrite API, so this structure must be layed out exactly
+ // the way EventWrite wants it.
+ internal ulong m_Ptr;
+ internal int m_Size;
+#pragma warning disable 0649
+ internal int m_Reserved; // Used to pad the size to match the Win32 API
+#pragma warning restore 0649
+#endregion
+ }
+
+ /// <summary>
+ /// This routine allows you to create efficient WriteEvent helpers, however the code that you use to
+ /// do this, while straightforward, is unsafe.
+ /// </summary>
+ /// <remarks>
+ /// <code>
+ /// protected unsafe void WriteEvent(int eventId, string arg1, long arg2)
+ /// {
+ /// if (IsEnabled())
+ /// {
+ /// if (arg2 == null) arg2 = "";
+ /// fixed (char* string2Bytes = arg2)
+ /// {
+ /// EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ /// descrs[0].DataPointer = (IntPtr)(&amp;arg1);
+ /// descrs[0].Size = 8;
+ /// descrs[0].Reserved = 0;
+ /// descrs[1].DataPointer = (IntPtr)string2Bytes;
+ /// descrs[1].Size = ((arg2.Length + 1) * 2);
+ /// descrs[1].Reserved = 0;
+ /// WriteEventCore(eventId, 2, descrs);
+ /// }
+ /// }
+ /// }
+ /// </code>
+ /// </remarks>
+ [CLSCompliant(false)]
+ protected unsafe void WriteEventCore(int eventId, int eventDataCount, EventSource.EventData* data)
+ {
+ WriteEventWithRelatedActivityIdCore(eventId, null, eventDataCount, data);
+ }
+
+ /// <summary>
+ /// This routine allows you to create efficient WriteEventWithRelatedActivityId helpers, however the code
+ /// that you use to do this, while straightforward, is unsafe. The only difference from
+ /// <see cref="WriteEventCore"/> is that you pass the relatedActivityId from caller through to this API
+ /// </summary>
+ /// <remarks>
+ /// <code>
+ /// protected unsafe void WriteEventWithRelatedActivityId(int eventId, Guid relatedActivityId, string arg1, long arg2)
+ /// {
+ /// if (IsEnabled())
+ /// {
+ /// if (arg2 == null) arg2 = "";
+ /// fixed (char* string2Bytes = arg2)
+ /// {
+ /// EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
+ /// descrs[0].DataPointer = (IntPtr)(&amp;arg1);
+ /// descrs[0].Size = 8;
+ /// descrs[1].DataPointer = (IntPtr)string2Bytes;
+ /// descrs[1].Size = ((arg2.Length + 1) * 2);
+ /// WriteEventWithRelatedActivityIdCore(eventId, relatedActivityId, 2, descrs);
+ /// }
+ /// }
+ /// }
+ /// </code>
+ /// </remarks>
+ [CLSCompliant(false)]
+ protected unsafe void WriteEventWithRelatedActivityIdCore(int eventId, Guid* relatedActivityId, int eventDataCount, EventSource.EventData* data)
+ {
+ if (m_eventSourceEnabled)
+ {
+ try
+ {
+ Debug.Assert(m_eventData != null); // You must have initialized this if you enabled the source.
+ if (relatedActivityId != null)
+ ValidateEventOpcodeForTransfer(ref m_eventData[eventId], m_eventData[eventId].Name);
+
+ EventOpcode opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode;
+ EventActivityOptions activityOptions = m_eventData[eventId].ActivityOptions;
+ Guid* pActivityId = null;
+ Guid activityId = Guid.Empty;
+ Guid relActivityId = Guid.Empty;
+
+ if (opcode != EventOpcode.Info && relatedActivityId == null &&
+ ((activityOptions & EventActivityOptions.Disable) == 0))
+ {
+ if (opcode == EventOpcode.Start)
+ {
+ m_activityTracker.OnStart(m_name, m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Task, ref activityId, ref relActivityId, m_eventData[eventId].ActivityOptions);
+ }
+ else if (opcode == EventOpcode.Stop)
+ {
+ m_activityTracker.OnStop(m_name, m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Task, ref activityId);
+ }
+
+ if (activityId != Guid.Empty)
+ pActivityId = &activityId;
+ if (relActivityId != Guid.Empty)
+ relatedActivityId = &relActivityId;
+ }
+
+#if FEATURE_MANAGED_ETW
+ if (m_eventData[eventId].EnabledForETW)
+ {
+ if (!SelfDescribingEvents)
+ {
+ if (!m_provider.WriteEvent(ref m_eventData[eventId].Descriptor, m_eventData[eventId].EventHandle, pActivityId, relatedActivityId, eventDataCount, (IntPtr)data))
+ ThrowEventSourceException(m_eventData[eventId].Name);
+ }
+ else
+ {
+ TraceLoggingEventTypes tlet = m_eventData[eventId].TraceLoggingEventTypes;
+ if (tlet == null)
+ {
+ tlet = new TraceLoggingEventTypes(m_eventData[eventId].Name,
+ m_eventData[eventId].Tags,
+ m_eventData[eventId].Parameters);
+ Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, tlet, null);
+
+ }
+ EventSourceOptions opt = new EventSourceOptions
+ {
+ Keywords = (EventKeywords)m_eventData[eventId].Descriptor.Keywords,
+ Level = (EventLevel)m_eventData[eventId].Descriptor.Level,
+ Opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode
+ };
+
+ WriteMultiMerge(m_eventData[eventId].Name, ref opt, tlet, pActivityId, relatedActivityId, data);
+ }
+ }
+#endif // FEATURE_MANAGED_ETW
+
+ if (m_Dispatchers != null && m_eventData[eventId].EnabledForAnyListener)
+ WriteToAllListeners(eventId, pActivityId, relatedActivityId, eventDataCount, data);
+ }
+ catch (Exception ex)
+ {
+ if (ex is EventSourceException)
+ throw;
+ else
+ ThrowEventSourceException(m_eventData[eventId].Name, ex);
+ }
+ }
+ }
+
+ // fallback varags helpers.
+ /// <summary>
+ /// This is the varargs helper for writing an event. It does create an array and box all the arguments so it is
+ /// relatively inefficient and should only be used for relatively rare events (e.g. less than 100 / sec). If your
+ /// rates are faster than that you should use <see cref="WriteEventCore"/> to create fast helpers for your particular
+ /// method signature. Even if you use this for rare events, this call should be guarded by an <see cref="IsEnabled()"/>
+ /// check so that the varargs call is not made when the EventSource is not active.
+ /// </summary>
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ protected unsafe void WriteEvent(int eventId, params object[] args)
+ {
+ WriteEventVarargs(eventId, null, args);
+ }
+
+ /// <summary>
+ /// This is the varargs helper for writing an event which also specifies a related activity. It is completely analogous
+ /// to corresponding WriteEvent (they share implementation). It does create an array and box all the arguments so it is
+ /// relatively inefficient and should only be used for relatively rare events (e.g. less than 100 / sec). If your
+ /// rates are faster than that you should use <see cref="WriteEventWithRelatedActivityIdCore"/> to create fast helpers for your
+ /// particular method signature. Even if you use this for rare events, this call should be guarded by an <see cref="IsEnabled()"/>
+ /// check so that the varargs call is not made when the EventSource is not active.
+ /// </summary>
+ protected unsafe void WriteEventWithRelatedActivityId(int eventId, Guid relatedActivityId, params object[] args)
+ {
+ WriteEventVarargs(eventId, &relatedActivityId, args);
+ }
+
+#endregion
+
+#region IDisposable Members
+ /// <summary>
+ /// Disposes of an EventSource.
+ /// </summary>
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ /// <summary>
+ /// Disposes of an EventSource.
+ /// </summary>
+ /// <remarks>
+ /// Called from Dispose() with disposing=true, and from the finalizer (~EventSource) with disposing=false.
+ /// Guidelines:
+ /// 1. We may be called more than once: do nothing after the first call.
+ /// 2. Avoid throwing exceptions if disposing is false, i.e. if we're being finalized.
+ /// </remarks>
+ /// <param name="disposing">True if called from Dispose(), false if called from the finalizer.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+#if FEATURE_MANAGED_ETW
+#if !FEATURE_PERFTRACING
+ // Send the manifest one more time to ensure circular buffers have a chance to get to this information
+ // even in scenarios with a high volume of ETW events.
+ if (m_eventSourceEnabled)
+ {
+ try
+ {
+ SendManifest(m_rawManifest);
+ }
+ catch (Exception)
+ { } // If it fails, simply give up.
+ m_eventSourceEnabled = false;
+ }
+#endif
+ if (m_provider != null)
+ {
+ m_provider.Dispose();
+ m_provider = null;
+ }
+#endif
+ }
+ m_eventSourceEnabled = false;
+ m_eventSourceDisposed = true;
+ }
+ /// <summary>
+ /// Finalizer for EventSource
+ /// </summary>
+ ~EventSource()
+ {
+ this.Dispose(false);
+ }
+#endregion
+
+#region private
+
+ private unsafe void WriteEventRaw(
+ string eventName,
+ ref EventDescriptor eventDescriptor,
+ IntPtr eventHandle,
+ Guid* activityID,
+ Guid* relatedActivityID,
+ int dataCount,
+ IntPtr data)
+ {
+#if FEATURE_MANAGED_ETW
+ if (m_provider == null)
+ {
+ ThrowEventSourceException(eventName);
+ }
+ else
+ {
+ if (!m_provider.WriteEventRaw(ref eventDescriptor, eventHandle, activityID, relatedActivityID, dataCount, data))
+ ThrowEventSourceException(eventName);
+ }
+#endif // FEATURE_MANAGED_ETW
+ }
+
+ // FrameworkEventSource is on the startup path for the framework, so we have this internal overload that it can use
+ // to prevent the working set hit from looking at the custom attributes on the type to get the Guid.
+ internal EventSource(Guid eventSourceGuid, string eventSourceName)
+ : this(eventSourceGuid, eventSourceName, EventSourceSettings.EtwManifestEventFormat)
+ { }
+
+ // Used by the internal FrameworkEventSource constructor and the TraceLogging-style event source constructor
+ internal EventSource(Guid eventSourceGuid, string eventSourceName, EventSourceSettings settings, string[] traits = null)
+ {
+ m_config = ValidateSettings(settings);
+ Initialize(eventSourceGuid, eventSourceName, traits);
+ }
+
+ /// <summary>
+ /// This method is responsible for the common initialization path from our constructors. It must
+ /// not leak any exceptions (otherwise, since most EventSource classes define a static member,
+ /// "Log", such an exception would become a cached exception for the initialization of the static
+ /// member, and any future access to the "Log" would throw the cached exception).
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "guid")]
+ private unsafe void Initialize(Guid eventSourceGuid, string eventSourceName, string[] traits)
+ {
+ try
+ {
+ m_traits = traits;
+ if (m_traits != null && m_traits.Length % 2 != 0)
+ {
+ throw new ArgumentException(SR.EventSource_TraitEven, nameof(traits));
+ }
+
+ if (eventSourceGuid == Guid.Empty)
+ {
+ throw new ArgumentException(SR.EventSource_NeedGuid);
+ }
+
+ if (eventSourceName == null)
+ {
+ throw new ArgumentException(SR.EventSource_NeedName);
+ }
+
+ m_name = eventSourceName;
+ m_guid = eventSourceGuid;
+
+ //Enable Implicit Activity tracker
+ m_activityTracker = ActivityTracker.Instance;
+
+#if FEATURE_MANAGED_ETW
+ // Create and register our provider traits. We do this early because it is needed to log errors
+ // In the self-describing event case.
+ this.InitializeProviderMetadata();
+
+ // Register the provider with ETW
+ var provider = new OverideEventProvider(this);
+ provider.Register(this);
+#endif
+ // Add the eventSource to the global (weak) list.
+ // This also sets m_id, which is the index in the list.
+ EventListener.AddEventSource(this);
+
+#if FEATURE_MANAGED_ETW
+ // OK if we get this far without an exception, then we can at least write out error messages.
+ // Set m_provider, which allows this.
+ m_provider = provider;
+
+#if PLATFORM_WINDOWS
+#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN)
+ // API available on OS >= Win 8 and patched Win 7.
+ // Disable only for FrameworkEventSource to avoid recursion inside exception handling.
+ if (this.Name != "System.Diagnostics.Eventing.FrameworkEventSource" || Environment.IsWindows8OrAbove)
+#endif
+ {
+ int setInformationResult;
+ System.Runtime.InteropServices.GCHandle metadataHandle =
+ System.Runtime.InteropServices.GCHandle.Alloc(this.providerMetadata, System.Runtime.InteropServices.GCHandleType.Pinned);
+ IntPtr providerMetadata = metadataHandle.AddrOfPinnedObject();
+
+ setInformationResult = m_provider.SetInformation(
+ UnsafeNativeMethods.ManifestEtw.EVENT_INFO_CLASS.SetTraits,
+ providerMetadata,
+ (uint)this.providerMetadata.Length);
+
+ metadataHandle.Free();
+ }
+#endif // PLATFORM_WINDOWS
+#endif // FEATURE_MANAGED_ETW
+ Debug.Assert(!m_eventSourceEnabled); // We can't be enabled until we are completely initted.
+ // We are logically completely initialized at this point.
+ m_completelyInited = true;
+ }
+ catch (Exception e)
+ {
+ if (m_constructionException == null)
+ m_constructionException = e;
+ ReportOutOfBandMessage("ERROR: Exception during construction of EventSource " + Name + ": " + e.Message, true);
+ }
+
+ // Once m_completelyInited is set, you can have concurrency, so all work is under the lock.
+ lock (EventListener.EventListenersLock)
+ {
+ // If there are any deferred commands, we can do them now.
+ // This is the most likely place for exceptions to happen.
+ // Note that we are NOT resetting m_deferredCommands to NULL here,
+ // We are giving for EventHandler<EventCommandEventArgs> that will be attached later
+ EventCommandEventArgs deferredCommands = m_deferredCommands;
+ while (deferredCommands != null)
+ {
+ DoCommand(deferredCommands); // This can never throw, it catches them and reports the errors.
+ deferredCommands = deferredCommands.nextCommand;
+ }
+ }
+ }
+
+ private static string GetName(Type eventSourceType, EventManifestOptions flags)
+ {
+ if (eventSourceType == null)
+ throw new ArgumentNullException(nameof(eventSourceType));
+
+ EventSourceAttribute attrib = (EventSourceAttribute)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags);
+ if (attrib != null && attrib.Name != null)
+ return attrib.Name;
+
+ return eventSourceType.Name;
+ }
+
+ /// <summary>
+ /// Implements the SHA1 hashing algorithm. Note that this
+ /// implementation is for hashing public information. Do not
+ /// use this code to hash private data, as this implementation does
+ /// not take any steps to avoid information disclosure.
+ /// </summary>
+ private struct Sha1ForNonSecretPurposes
+ {
+ private long length; // Total message length in bits
+ private uint[] w; // Workspace
+ private int pos; // Length of current chunk in bytes
+
+ /// <summary>
+ /// Call Start() to initialize the hash object.
+ /// </summary>
+ public void Start()
+ {
+ if (this.w == null)
+ {
+ this.w = new uint[85];
+ }
+
+ this.length = 0;
+ this.pos = 0;
+ this.w[80] = 0x67452301;
+ this.w[81] = 0xEFCDAB89;
+ this.w[82] = 0x98BADCFE;
+ this.w[83] = 0x10325476;
+ this.w[84] = 0xC3D2E1F0;
+ }
+
+ /// <summary>
+ /// Adds an input byte to the hash.
+ /// </summary>
+ /// <param name="input">Data to include in the hash.</param>
+ public void Append(byte input)
+ {
+ this.w[this.pos / 4] = (this.w[this.pos / 4] << 8) | input;
+ if (64 == ++this.pos)
+ {
+ this.Drain();
+ }
+ }
+
+ /// <summary>
+ /// Adds input bytes to the hash.
+ /// </summary>
+ /// <param name="input">
+ /// Data to include in the hash. Must not be null.
+ /// </param>
+ public void Append(byte[] input)
+ {
+ foreach (var b in input)
+ {
+ this.Append(b);
+ }
+ }
+
+ /// <summary>
+ /// Retrieves the hash value.
+ /// Note that after calling this function, the hash object should
+ /// be considered uninitialized. Subsequent calls to Append or
+ /// Finish will produce useless results. Call Start() to
+ /// reinitialize.
+ /// </summary>
+ /// <param name="output">
+ /// Buffer to receive the hash value. Must not be null.
+ /// Up to 20 bytes of hash will be written to the output buffer.
+ /// If the buffer is smaller than 20 bytes, the remaining hash
+ /// bytes will be lost. If the buffer is larger than 20 bytes, the
+ /// rest of the buffer is left unmodified.
+ /// </param>
+ public void Finish(byte[] output)
+ {
+ long l = this.length + 8 * this.pos;
+ this.Append(0x80);
+ while (this.pos != 56)
+ {
+ this.Append(0x00);
+ }
+
+ unchecked
+ {
+ this.Append((byte)(l >> 56));
+ this.Append((byte)(l >> 48));
+ this.Append((byte)(l >> 40));
+ this.Append((byte)(l >> 32));
+ this.Append((byte)(l >> 24));
+ this.Append((byte)(l >> 16));
+ this.Append((byte)(l >> 8));
+ this.Append((byte)l);
+
+ int end = output.Length < 20 ? output.Length : 20;
+ for (int i = 0; i != end; i++)
+ {
+ uint temp = this.w[80 + i / 4];
+ output[i] = (byte)(temp >> 24);
+ this.w[80 + i / 4] = temp << 8;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Called when this.pos reaches 64.
+ /// </summary>
+ private void Drain()
+ {
+ for (int i = 16; i != 80; i++)
+ {
+ this.w[i] = Rol1((this.w[i - 3] ^ this.w[i - 8] ^ this.w[i - 14] ^ this.w[i - 16]));
+ }
+
+ unchecked
+ {
+ uint a = this.w[80];
+ uint b = this.w[81];
+ uint c = this.w[82];
+ uint d = this.w[83];
+ uint e = this.w[84];
+
+ for (int i = 0; i != 20; i++)
+ {
+ const uint k = 0x5A827999;
+ uint f = (b & c) | ((~b) & d);
+ uint temp = Rol5(a) + f + e + k + this.w[i]; e = d; d = c; c = Rol30(b); b = a; a = temp;
+ }
+
+ for (int i = 20; i != 40; i++)
+ {
+ uint f = b ^ c ^ d;
+ const uint k = 0x6ED9EBA1;
+ uint temp = Rol5(a) + f + e + k + this.w[i]; e = d; d = c; c = Rol30(b); b = a; a = temp;
+ }
+
+ for (int i = 40; i != 60; i++)
+ {
+ uint f = (b & c) | (b & d) | (c & d);
+ const uint k = 0x8F1BBCDC;
+ uint temp = Rol5(a) + f + e + k + this.w[i]; e = d; d = c; c = Rol30(b); b = a; a = temp;
+ }
+
+ for (int i = 60; i != 80; i++)
+ {
+ uint f = b ^ c ^ d;
+ const uint k = 0xCA62C1D6;
+ uint temp = Rol5(a) + f + e + k + this.w[i]; e = d; d = c; c = Rol30(b); b = a; a = temp;
+ }
+
+ this.w[80] += a;
+ this.w[81] += b;
+ this.w[82] += c;
+ this.w[83] += d;
+ this.w[84] += e;
+ }
+
+ this.length += 512; // 64 bytes == 512 bits
+ this.pos = 0;
+ }
+
+ private static uint Rol1(uint input)
+ {
+ return (input << 1) | (input >> 31);
+ }
+
+ private static uint Rol5(uint input)
+ {
+ return (input << 5) | (input >> 27);
+ }
+
+ private static uint Rol30(uint input)
+ {
+ return (input << 30) | (input >> 2);
+ }
+ }
+
+ private static Guid GenerateGuidFromName(string name)
+ {
+ byte[] bytes = Encoding.BigEndianUnicode.GetBytes(name);
+ var hash = new Sha1ForNonSecretPurposes();
+ hash.Start();
+ hash.Append(namespaceBytes);
+ hash.Append(bytes);
+ Array.Resize(ref bytes, 16);
+ hash.Finish(bytes);
+
+ bytes[7] = unchecked((byte)((bytes[7] & 0x0F) | 0x50)); // Set high 4 bits of octet 7 to 5, as per RFC 4122
+ return new Guid(bytes);
+ }
+
+ private unsafe object DecodeObject(int eventId, int parameterId, ref EventSource.EventData* data)
+ {
+ // TODO FIX : We use reflection which in turn uses EventSource, right now we carefully avoid
+ // the recursion, but can we do this in a robust way?
+
+ IntPtr dataPointer = data->DataPointer;
+ // advance to next EventData in array
+ ++data;
+
+ Type dataType = GetDataType(m_eventData[eventId], parameterId);
+
+ Again:
+ if (dataType == typeof(IntPtr))
+ {
+ return *((IntPtr*)dataPointer);
+ }
+ else if (dataType == typeof(int))
+ {
+ return *((int*)dataPointer);
+ }
+ else if (dataType == typeof(uint))
+ {
+ return *((uint*)dataPointer);
+ }
+ else if (dataType == typeof(long))
+ {
+ return *((long*)dataPointer);
+ }
+ else if (dataType == typeof(ulong))
+ {
+ return *((ulong*)dataPointer);
+ }
+ else if (dataType == typeof(byte))
+ {
+ return *((byte*)dataPointer);
+ }
+ else if (dataType == typeof(sbyte))
+ {
+ return *((sbyte*)dataPointer);
+ }
+ else if (dataType == typeof(short))
+ {
+ return *((short*)dataPointer);
+ }
+ else if (dataType == typeof(ushort))
+ {
+ return *((ushort*)dataPointer);
+ }
+ else if (dataType == typeof(float))
+ {
+ return *((float*)dataPointer);
+ }
+ else if (dataType == typeof(double))
+ {
+ return *((double*)dataPointer);
+ }
+ else if (dataType == typeof(decimal))
+ {
+ return *((decimal*)dataPointer);
+ }
+ else if (dataType == typeof(bool))
+ {
+ // The manifest defines a bool as a 32bit type (WIN32 BOOL), not 1 bit as CLR Does.
+ if (*((int*)dataPointer) == 1)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (dataType == typeof(Guid))
+ {
+ return *((Guid*)dataPointer);
+ }
+ else if (dataType == typeof(char))
+ {
+ return *((char*)dataPointer);
+ }
+ else if (dataType == typeof(DateTime))
+ {
+ long dateTimeTicks = *((long*)dataPointer);
+ return DateTime.FromFileTimeUtc(dateTimeTicks);
+ }
+ else if (dataType == typeof(byte[]))
+ {
+ // byte[] are written to EventData* as an int followed by a blob
+ int cbSize = *((int*)dataPointer);
+ byte[] blob = new byte[cbSize];
+ dataPointer = data->DataPointer;
+ data++;
+ for (int i = 0; i < cbSize; ++i)
+ blob[i] = *((byte*)(dataPointer + i));
+ return blob;
+ }
+ else if (dataType == typeof(byte*))
+ {
+ // TODO: how do we want to handle this? For now we ignore it...
+ return null;
+ }
+ else
+ {
+ if (m_EventSourcePreventRecursion && m_EventSourceInDecodeObject)
+ {
+ return null;
+ }
+
+ try
+ {
+ m_EventSourceInDecodeObject = true;
+
+ if (dataType.IsEnum())
+ {
+ dataType = Enum.GetUnderlyingType(dataType);
+ goto Again;
+ }
+
+
+ // Everything else is marshaled as a string.
+ // ETW strings are NULL-terminated, so marshal everything up to the first
+ // null in the string.
+ //return System.Runtime.InteropServices.Marshal.PtrToStringUni(dataPointer);
+ if(dataPointer == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return new string((char *)dataPointer);
+
+ }
+ finally
+ {
+ m_EventSourceInDecodeObject = false;
+ }
+ }
+ }
+
+ // Finds the Dispatcher (which holds the filtering state), for a given dispatcher for the current
+ // eventSource).
+ private EventDispatcher GetDispatcher(EventListener listener)
+ {
+ EventDispatcher dispatcher = m_Dispatchers;
+ while (dispatcher != null)
+ {
+ if (dispatcher.m_Listener == listener)
+ return dispatcher;
+ dispatcher = dispatcher.m_Next;
+ }
+ return dispatcher;
+ }
+
+ private unsafe void WriteEventVarargs(int eventId, Guid* childActivityID, object[] args)
+ {
+ if (m_eventSourceEnabled)
+ {
+ try
+ {
+ Debug.Assert(m_eventData != null); // You must have initialized this if you enabled the source.
+ if (childActivityID != null)
+ {
+ ValidateEventOpcodeForTransfer(ref m_eventData[eventId], m_eventData[eventId].Name);
+
+ // If you use WriteEventWithRelatedActivityID you MUST declare the first argument to be a GUID
+ // with the name 'relatedActivityID, and NOT pass this argument to the WriteEvent method.
+ // During manifest creation we modify the ParameterInfo[] that we store to strip out any
+ // first parameter that is of type Guid and named "relatedActivityId." Thus, if you call
+ // WriteEventWithRelatedActivityID from a method that doesn't name its first parameter correctly
+ // we can end up in a state where the ParameterInfo[] doesn't have its first parameter stripped,
+ // and this leads to a mismatch between the number of arguments and the number of ParameterInfos,
+ // which would cause a cryptic IndexOutOfRangeException later if we don't catch it here.
+ if (!m_eventData[eventId].HasRelatedActivityID)
+ {
+ throw new ArgumentException(SR.EventSource_NoRelatedActivityId);
+ }
+ }
+
+ LogEventArgsMismatches(m_eventData[eventId].Parameters, args);
+
+ Guid* pActivityId = null;
+ Guid activityId = Guid.Empty;
+ Guid relatedActivityId = Guid.Empty;
+ EventOpcode opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode;
+ EventActivityOptions activityOptions = m_eventData[eventId].ActivityOptions;
+
+ if (childActivityID == null &&
+ ((activityOptions & EventActivityOptions.Disable) == 0))
+ {
+ if (opcode == EventOpcode.Start)
+ {
+ m_activityTracker.OnStart(m_name, m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Task, ref activityId, ref relatedActivityId, m_eventData[eventId].ActivityOptions);
+ }
+ else if (opcode == EventOpcode.Stop)
+ {
+ m_activityTracker.OnStop(m_name, m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Task, ref activityId);
+ }
+
+ if (activityId != Guid.Empty)
+ pActivityId = &activityId;
+ if (relatedActivityId != Guid.Empty)
+ childActivityID = &relatedActivityId;
+ }
+
+#if FEATURE_MANAGED_ETW
+ if (m_eventData[eventId].EnabledForETW)
+ {
+ if (!SelfDescribingEvents)
+ {
+ if (!m_provider.WriteEvent(ref m_eventData[eventId].Descriptor, m_eventData[eventId].EventHandle, pActivityId, childActivityID, args))
+ ThrowEventSourceException(m_eventData[eventId].Name);
+ }
+ else
+ {
+ TraceLoggingEventTypes tlet = m_eventData[eventId].TraceLoggingEventTypes;
+ if (tlet == null)
+ {
+ tlet = new TraceLoggingEventTypes(m_eventData[eventId].Name,
+ EventTags.None,
+ m_eventData[eventId].Parameters);
+ Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, tlet, null);
+
+ }
+ // TODO: activity ID support
+ EventSourceOptions opt = new EventSourceOptions
+ {
+ Keywords = (EventKeywords)m_eventData[eventId].Descriptor.Keywords,
+ Level = (EventLevel)m_eventData[eventId].Descriptor.Level,
+ Opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode
+ };
+
+ WriteMultiMerge(m_eventData[eventId].Name, ref opt, tlet, pActivityId, childActivityID, args);
+ }
+ }
+#endif // FEATURE_MANAGED_ETW
+ if (m_Dispatchers != null && m_eventData[eventId].EnabledForAnyListener)
+ {
+#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN)
+ // Maintain old behavior - object identity is preserved
+ if (AppContextSwitches.PreserveEventListnerObjectIdentity)
+ {
+ WriteToAllListeners(eventId, pActivityId, childActivityID, args);
+ }
+ else
+#endif // !ES_BUILD_STANDALONE
+ {
+ object[] serializedArgs = SerializeEventArgs(eventId, args);
+ WriteToAllListeners(eventId, pActivityId, childActivityID, serializedArgs);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (ex is EventSourceException)
+ throw;
+ else
+ ThrowEventSourceException(m_eventData[eventId].Name, ex);
+ }
+ }
+ }
+
+ private unsafe object[] SerializeEventArgs(int eventId, object[] args)
+ {
+ TraceLoggingEventTypes eventTypes = m_eventData[eventId].TraceLoggingEventTypes;
+ if (eventTypes == null)
+ {
+ eventTypes = new TraceLoggingEventTypes(m_eventData[eventId].Name,
+ EventTags.None,
+ m_eventData[eventId].Parameters);
+ Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, eventTypes, null);
+ }
+ var eventData = new object[eventTypes.typeInfos.Length];
+ for (int i = 0; i < eventTypes.typeInfos.Length; i++)
+ {
+ eventData[i] = eventTypes.typeInfos[i].GetData(args[i]);
+ }
+ return eventData;
+ }
+
+ /// <summary>
+ /// We expect that the arguments to the Event method and the arguments to WriteEvent match. This function
+ /// checks that they in fact match and logs a warning to the debugger if they don't.
+ /// </summary>
+ /// <param name="infos"></param>
+ /// <param name="args"></param>
+ private void LogEventArgsMismatches(ParameterInfo[] infos, object[] args)
+ {
+#if (!ES_BUILD_PCL && !ES_BUILD_PN)
+ // It would be nice to have this on PCL builds, but it would be pointless since there isn't support for
+ // writing to the debugger log on PCL.
+ bool typesMatch = args.Length == infos.Length;
+
+ int i = 0;
+ while (typesMatch && i < args.Length)
+ {
+ Type pType = infos[i].ParameterType;
+
+ // Checking to see if the Parameter types (from the Event method) match the supplied argument types.
+ // Fail if one of two things hold : either the argument type is not equal to the parameter type, or the
+ // argument is null and the parameter type is non-nullable.
+ if ((args[i] != null && (args[i].GetType() != pType))
+ || (args[i] == null && (!(pType.IsGenericType && pType.GetGenericTypeDefinition() == typeof(Nullable<>))))
+ )
+ {
+ typesMatch = false;
+ break;
+ }
+
+ ++i;
+ }
+
+ if (!typesMatch)
+ {
+ System.Diagnostics.Debugger.Log(0, null, SR.EventSource_VarArgsParameterMismatch + "\r\n");
+ }
+#endif //!ES_BUILD_PCL
+ }
+
+ private unsafe void WriteToAllListeners(int eventId, Guid* activityID, Guid* childActivityID, int eventDataCount, EventSource.EventData* data)
+ {
+ // We represent a byte[] as a integer denoting the length and then a blob of bytes in the data pointer. This causes a spurious
+ // warning because eventDataCount is off by one for the byte[] case since a byte[] has 2 items associated it. So we want to check
+ // that the number of parameters is correct against the byte[] case, but also we the args array would be one too long if
+ // we just used the modifiedParamCount here -- so we need both.
+ int paramCount = GetParameterCount(m_eventData[eventId]);
+ int modifiedParamCount = 0;
+ for (int i = 0; i < paramCount; i++)
+ {
+ Type parameterType = GetDataType(m_eventData[eventId], i);
+ if (parameterType == typeof(byte[]))
+ {
+ modifiedParamCount += 2;
+ }
+ else
+ {
+ modifiedParamCount++;
+ }
+ }
+ if (eventDataCount != modifiedParamCount)
+ {
+ ReportOutOfBandMessage(SR.Format(SR.EventSource_EventParametersMismatch, eventId, eventDataCount, paramCount), true);
+ paramCount = Math.Min(paramCount, eventDataCount);
+ }
+
+ object[] args = new object[paramCount];
+
+ EventSource.EventData* dataPtr = data;
+ for (int i = 0; i < paramCount; i++)
+ args[i] = DecodeObject(eventId, i, ref dataPtr);
+ WriteToAllListeners(eventId, activityID, childActivityID, args);
+ }
+
+ // helper for writing to all EventListeners attached the current eventSource.
+ private unsafe void WriteToAllListeners(int eventId, Guid* activityID, Guid* childActivityID, params object[] args)
+ {
+ EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this);
+ eventCallbackArgs.EventId = eventId;
+ if (activityID != null)
+ eventCallbackArgs.ActivityId = *activityID;
+ if (childActivityID != null)
+ eventCallbackArgs.RelatedActivityId = *childActivityID;
+ eventCallbackArgs.EventName = m_eventData[eventId].Name;
+ eventCallbackArgs.Message = m_eventData[eventId].Message;
+ eventCallbackArgs.Payload = new ReadOnlyCollection<object>(args);
+
+ DispatchToAllListeners(eventId, childActivityID, eventCallbackArgs);
+ }
+
+ private unsafe void DispatchToAllListeners(int eventId, Guid* childActivityID, EventWrittenEventArgs eventCallbackArgs)
+ {
+ Exception lastThrownException = null;
+ for (EventDispatcher dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next)
+ {
+ Debug.Assert(dispatcher.m_EventEnabled != null);
+ if (eventId == -1 || dispatcher.m_EventEnabled[eventId])
+ {
+ {
+ try
+ {
+ dispatcher.m_Listener.OnEventWritten(eventCallbackArgs);
+ }
+ catch (Exception e)
+ {
+ ReportOutOfBandMessage("ERROR: Exception during EventSource.OnEventWritten: "
+ + e.Message, false);
+ lastThrownException = e;
+ }
+ }
+ }
+ }
+
+ if (lastThrownException != null)
+ {
+ throw new EventSourceException(lastThrownException);
+ }
+ }
+
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ private unsafe void WriteEventString(EventLevel level, long keywords, string msgString)
+ {
+#if FEATURE_MANAGED_ETW && !FEATURE_PERFTRACING
+ if (m_provider != null)
+ {
+ string eventName = "EventSourceMessage";
+ if (SelfDescribingEvents)
+ {
+ EventSourceOptions opt = new EventSourceOptions
+ {
+ Keywords = (EventKeywords)unchecked(keywords),
+ Level = level
+ };
+ var msg = new { message = msgString };
+ var tlet = new TraceLoggingEventTypes(eventName, EventTags.None, new Type[] { msg.GetType() });
+ WriteMultiMergeInner(eventName, ref opt, tlet, null, null, msg);
+ }
+ else
+ {
+ // We want the name of the provider to show up so if we don't have a manifest we create
+ // on that at least has the provider name (I don't define any events).
+ if (m_rawManifest == null && m_outOfBandMessageCount == 1)
+ {
+ ManifestBuilder manifestBuilder = new ManifestBuilder(Name, Guid, Name, null, EventManifestOptions.None);
+ manifestBuilder.StartEvent(eventName, new EventAttribute(0) { Level = EventLevel.LogAlways, Task = (EventTask)0xFFFE });
+ manifestBuilder.AddEventParameter(typeof(string), "message");
+ manifestBuilder.EndEvent();
+ SendManifest(manifestBuilder.CreateManifest());
+ }
+
+ // We use this low level routine to to bypass the enabled checking, since the eventSource itself is only partially inited.
+ fixed (char* msgStringPtr = msgString)
+ {
+ EventDescriptor descr = new EventDescriptor(0, 0, 0, (byte)level, 0, 0, keywords);
+ EventProvider.EventData data = new EventProvider.EventData();
+ data.Ptr = (ulong)msgStringPtr;
+ data.Size = (uint)(2 * (msgString.Length + 1));
+ data.Reserved = 0;
+ m_provider.WriteEvent(ref descr, IntPtr.Zero, null, null, 1, (IntPtr)((void*)&data));
+ }
+ }
+ }
+#endif // FEATURE_MANAGED_ETW
+ }
+
+ /// <summary>
+ /// Since this is a means of reporting errors (see ReportoutOfBandMessage) any failure encountered
+ /// while writing the message to any one of the listeners will be silently ignored.
+ /// </summary>
+ private void WriteStringToAllListeners(string eventName, string msg)
+ {
+ EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this);
+ eventCallbackArgs.EventId = 0;
+ eventCallbackArgs.Message = msg;
+ eventCallbackArgs.Payload = new ReadOnlyCollection<object>(new List<object>() { msg });
+ eventCallbackArgs.PayloadNames = new ReadOnlyCollection<string>(new List<string> { "message" });
+ eventCallbackArgs.EventName = eventName;
+
+ for (EventDispatcher dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next)
+ {
+ bool dispatcherEnabled = false;
+ if (dispatcher.m_EventEnabled == null)
+ {
+ // if the listeners that weren't correctly initialized, we will send to it
+ // since this is an error message and we want to see it go out.
+ dispatcherEnabled = true;
+ }
+ else
+ {
+ // if there's *any* enabled event on the dispatcher we'll write out the string
+ // otherwise we'll treat the listener as disabled and skip it
+ for (int evtId = 0; evtId < dispatcher.m_EventEnabled.Length; ++evtId)
+ {
+ if (dispatcher.m_EventEnabled[evtId])
+ {
+ dispatcherEnabled = true;
+ break;
+ }
+ }
+ }
+ try
+ {
+ if (dispatcherEnabled)
+ dispatcher.m_Listener.OnEventWritten(eventCallbackArgs);
+ }
+ catch
+ {
+ // ignore any exceptions thrown by listeners' OnEventWritten
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns true if 'eventNum' is enabled if you only consider the level and matchAnyKeyword filters.
+ /// It is possible that eventSources turn off the event based on additional filtering criteria.
+ /// </summary>
+ private bool IsEnabledByDefault(int eventNum, bool enable, EventLevel currentLevel, EventKeywords currentMatchAnyKeyword)
+ {
+ if (!enable)
+ return false;
+
+ EventLevel eventLevel = (EventLevel)m_eventData[eventNum].Descriptor.Level;
+ EventKeywords eventKeywords = unchecked((EventKeywords)((ulong)m_eventData[eventNum].Descriptor.Keywords & (~(SessionMask.All.ToEventKeywords()))));
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ EventChannel channel = unchecked((EventChannel)m_eventData[eventNum].Descriptor.Channel);
+#else
+ EventChannel channel = EventChannel.None;
+#endif
+
+ return IsEnabledCommon(enable, currentLevel, currentMatchAnyKeyword, eventLevel, eventKeywords, channel);
+ }
+
+ private bool IsEnabledCommon(bool enabled, EventLevel currentLevel, EventKeywords currentMatchAnyKeyword,
+ EventLevel eventLevel, EventKeywords eventKeywords, EventChannel eventChannel)
+ {
+ if (!enabled)
+ return false;
+
+ // does is pass the level test?
+ if ((currentLevel != 0) && (currentLevel < eventLevel))
+ return false;
+
+ // if yes, does it pass the keywords test?
+ if (currentMatchAnyKeyword != 0 && eventKeywords != 0)
+ {
+#if FEATURE_MANAGED_ETW_CHANNELS
+ // is there a channel with keywords that match currentMatchAnyKeyword?
+ if (eventChannel != EventChannel.None && this.m_channelData != null && this.m_channelData.Length > (int)eventChannel)
+ {
+ EventKeywords channel_keywords = unchecked((EventKeywords)(m_channelData[(int)eventChannel] | (ulong)eventKeywords));
+ if (channel_keywords != 0 && (channel_keywords & currentMatchAnyKeyword) == 0)
+ return false;
+ }
+ else
+#endif
+ {
+ if ((unchecked((ulong)eventKeywords & (ulong)currentMatchAnyKeyword)) == 0)
+ return false;
+ }
+ }
+ return true;
+
+ }
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
+ private void ThrowEventSourceException(string eventName, Exception innerEx = null)
+ {
+ // If we fail during out of band logging we may end up trying
+ // to throw another EventSourceException, thus hitting a StackOverflowException.
+ // Avoid StackOverflow by making sure we do not recursively call this method.
+ if (m_EventSourceExceptionRecurenceCount > 0)
+ return;
+ try
+ {
+ m_EventSourceExceptionRecurenceCount++;
+
+ string errorPrefix = "EventSourceException";
+ if (eventName != null)
+ {
+ errorPrefix += " while processing event \"" + eventName + "\"";
+ }
+
+ // TODO Create variations of EventSourceException that indicate more information using the error code.
+ switch (EventProvider.GetLastWriteEventError())
+ {
+ case EventProvider.WriteEventErrorCode.EventTooBig:
+ ReportOutOfBandMessage(errorPrefix + ": " + SR.EventSource_EventTooBig, true);
+ if (ThrowOnEventWriteErrors) throw new EventSourceException(SR.EventSource_EventTooBig, innerEx);
+ break;
+ case EventProvider.WriteEventErrorCode.NoFreeBuffers:
+ ReportOutOfBandMessage(errorPrefix + ": " + SR.EventSource_NoFreeBuffers, true);
+ if (ThrowOnEventWriteErrors) throw new EventSourceException(SR.EventSource_NoFreeBuffers, innerEx);
+ break;
+ case EventProvider.WriteEventErrorCode.NullInput:
+ ReportOutOfBandMessage(errorPrefix + ": " + SR.EventSource_NullInput, true);
+ if (ThrowOnEventWriteErrors) throw new EventSourceException(SR.EventSource_NullInput, innerEx);
+ break;
+ case EventProvider.WriteEventErrorCode.TooManyArgs:
+ ReportOutOfBandMessage(errorPrefix + ": " + SR.EventSource_TooManyArgs, true);
+ if (ThrowOnEventWriteErrors) throw new EventSourceException(SR.EventSource_TooManyArgs, innerEx);
+ break;
+ default:
+ if (innerEx != null)
+ ReportOutOfBandMessage(errorPrefix + ": " + innerEx.GetType() + ":" + innerEx.Message, true);
+ else
+ ReportOutOfBandMessage(errorPrefix, true);
+ if (ThrowOnEventWriteErrors) throw new EventSourceException(innerEx);
+ break;
+ }
+ }
+ finally
+ {
+ m_EventSourceExceptionRecurenceCount--;
+ }
+ }
+
+ private void ValidateEventOpcodeForTransfer(ref EventMetadata eventData, string eventName)
+ {
+ if ((EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Send &&
+ (EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Receive &&
+ (EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Start)
+ {
+ ThrowEventSourceException(eventName);
+ }
+ }
+
+ internal static EventOpcode GetOpcodeWithDefault(EventOpcode opcode, string eventName)
+ {
+ if (opcode == EventOpcode.Info && eventName != null)
+ {
+ if (eventName.EndsWith(s_ActivityStartSuffix, StringComparison.Ordinal))
+ {
+ return EventOpcode.Start;
+ }
+ else if (eventName.EndsWith(s_ActivityStopSuffix, StringComparison.Ordinal))
+ {
+ return EventOpcode.Stop;
+ }
+ }
+
+ return opcode;
+ }
+
+#if FEATURE_MANAGED_ETW
+ /// <summary>
+ /// This class lets us hook the 'OnEventCommand' from the eventSource.
+ /// </summary>
+ private class OverideEventProvider : EventProvider
+ {
+ public OverideEventProvider(EventSource eventSource)
+ {
+ this.m_eventSource = eventSource;
+ }
+ protected override void OnControllerCommand(ControllerCommand command, IDictionary<string, string> arguments,
+ int perEventSourceSessionId, int etwSessionId)
+ {
+ // We use null to represent the ETW EventListener.
+ EventListener listener = null;
+ m_eventSource.SendCommand(listener, perEventSourceSessionId, etwSessionId,
+ (EventCommand)command, IsEnabled(), Level, MatchAnyKeyword, arguments);
+ }
+ private EventSource m_eventSource;
+ }
+#endif
+
+ /// <summary>
+ /// Used to hold all the static information about an event. This includes everything in the event
+ /// descriptor as well as some stuff we added specifically for EventSource. see the
+ /// code:m_eventData for where we use this.
+ /// </summary>
+
+ /*
+ EventMetadata was public in the separate System.Diagnostics.Tracing assembly(pre NS2.0),
+ now the move to CoreLib marked them as private.
+ While they are technically private (it's a contract used between the library and the ILC toolchain),
+ we need them to be rooted and exported from shared library for the system to work.
+ For now I'm simply marking them as public again.A cleaner solution might be to use.rd.xml to
+ root them and modify shared library definition to force export them.
+ */
+#if ES_BUILD_PN
+ public
+#else
+ internal
+#endif
+ partial struct EventMetadata
+ {
+ public EventDescriptor Descriptor;
+ public IntPtr EventHandle; // EventPipeEvent handle.
+ public EventTags Tags;
+ public bool EnabledForAnyListener; // true if any dispatcher has this event turned on
+ public bool EnabledForETW; // is this event on for the OS ETW data dispatcher?
+
+ public bool HasRelatedActivityID; // Set if the event method's first parameter is a Guid named 'relatedActivityId'
+#pragma warning disable 0649
+ public byte TriggersActivityTracking; // count of listeners that marked this event as trigger for start of activity logging.
+#pragma warning restore 0649
+ public string Name; // the name of the event
+ public string Message; // If the event has a message associated with it, this is it.
+ public ParameterInfo[] Parameters; // TODO can we remove?
+
+ public TraceLoggingEventTypes TraceLoggingEventTypes;
+ public EventActivityOptions ActivityOptions;
+
+#if ES_BUILD_PN
+ public EventParameterType[] ParameterTypes;
+#endif
+ };
+
+ // This is the internal entry point that code:EventListeners call when wanting to send a command to a
+ // eventSource. The logic is as follows
+ //
+ // * if Command == Update
+ // * perEventSourceSessionId specifies the per-provider ETW session ID that the command applies
+ // to (if listener != null)
+ // perEventSourceSessionId = 0 - reserved for EventListeners
+ // perEventSourceSessionId = 1..SessionMask.MAX - reserved for activity tracing aware ETW sessions
+ // perEventSourceSessionId-1 represents the bit in the reserved field (bits 44..47) in
+ // Keywords that identifies the session
+ // perEventSourceSessionId = SessionMask.MAX+1 - reserved for legacy ETW sessions; these are
+ // discriminated by etwSessionId
+ // * etwSessionId specifies a machine-wide ETW session ID; this allows correlation of
+ // activity tracing across different providers (which might have different sessionIds
+ // for the same ETW session)
+ // * enable, level, matchAnyKeywords are used to set a default for all events for the
+ // eventSource. In particular, if 'enabled' is false, 'level' and
+ // 'matchAnyKeywords' are not used.
+ // * OnEventCommand is invoked, which may cause calls to
+ // code:EventSource.EnableEventForDispatcher which may cause changes in the filtering
+ // depending on the logic in that routine.
+ // * else (command != Update)
+ // * Simply call OnEventCommand. The expectation is that filtering is NOT changed.
+ // * The 'enabled' 'level', matchAnyKeyword' arguments are ignored (must be true, 0, 0).
+ //
+ // dispatcher == null has special meaning. It is the 'ETW' dispatcher.
+ internal void SendCommand(EventListener listener, int perEventSourceSessionId, int etwSessionId,
+ EventCommand command, bool enable,
+ EventLevel level, EventKeywords matchAnyKeyword,
+ IDictionary<string, string> commandArguments)
+ {
+ var commandArgs = new EventCommandEventArgs(command, commandArguments, this, listener, perEventSourceSessionId, etwSessionId, enable, level, matchAnyKeyword);
+ lock (EventListener.EventListenersLock)
+ {
+ if (m_completelyInited)
+ {
+ // After the first command arrive after construction, we are ready to get rid of the deferred commands
+ this.m_deferredCommands = null;
+ // We are fully initialized, do the command
+ DoCommand(commandArgs);
+ }
+ else
+ {
+ // We can't do the command, simply remember it and we do it when we are fully constructed.
+ if (m_deferredCommands == null)
+ m_deferredCommands = commandArgs; // create the first entry
+ else
+ {
+ // We have one or more entries, find the last one and add it to that.
+ EventCommandEventArgs lastCommand = m_deferredCommands;
+ while (lastCommand.nextCommand != null)
+ lastCommand = lastCommand.nextCommand;
+ lastCommand.nextCommand = commandArgs;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// We want the eventSource to be fully initialized when we do commands because that way we can send
+ /// error messages and other logging directly to the event stream. Unfortunately we can get callbacks
+ /// when we are not fully initialized. In that case we store them in 'commandArgs' and do them later.
+ /// This helper actually does all actual command logic.
+ /// </summary>
+ internal void DoCommand(EventCommandEventArgs commandArgs)
+ {
+ // PRECONDITION: We should be holding the EventListener.EventListenersLock
+ // We defer commands until we are completely inited. This allows error messages to be sent.
+ Debug.Assert(m_completelyInited);
+
+#if FEATURE_MANAGED_ETW
+ if (m_provider == null) // If we failed to construct
+ return;
+#endif // FEATURE_MANAGED_ETW
+
+ m_outOfBandMessageCount = 0;
+ bool shouldReport = (commandArgs.perEventSourceSessionId > 0) && (commandArgs.perEventSourceSessionId <= SessionMask.MAX);
+ try
+ {
+ EnsureDescriptorsInitialized();
+ Debug.Assert(m_eventData != null);
+
+ // Find the per-EventSource dispatcher corresponding to registered dispatcher
+ commandArgs.dispatcher = GetDispatcher(commandArgs.listener);
+ if (commandArgs.dispatcher == null && commandArgs.listener != null) // dispatcher == null means ETW dispatcher
+ {
+ throw new ArgumentException(SR.EventSource_ListenerNotFound);
+ }
+
+ if (commandArgs.Arguments == null)
+ commandArgs.Arguments = new Dictionary<string, string>();
+
+ if (commandArgs.Command == EventCommand.Update)
+ {
+ // Set it up using the 'standard' filtering bitfields (use the "global" enable, not session specific one)
+ for (int i = 0; i < m_eventData.Length; i++)
+ EnableEventForDispatcher(commandArgs.dispatcher, i, IsEnabledByDefault(i, commandArgs.enable, commandArgs.level, commandArgs.matchAnyKeyword));
+
+ if (commandArgs.enable)
+ {
+ if (!m_eventSourceEnabled)
+ {
+ // EventSource turned on for the first time, simply copy the bits.
+ m_level = commandArgs.level;
+ m_matchAnyKeyword = commandArgs.matchAnyKeyword;
+ }
+ else
+ {
+ // Already enabled, make it the most verbose of the existing and new filter
+ if (commandArgs.level > m_level)
+ m_level = commandArgs.level;
+ if (commandArgs.matchAnyKeyword == 0)
+ m_matchAnyKeyword = 0;
+ else if (m_matchAnyKeyword != 0)
+ m_matchAnyKeyword = unchecked(m_matchAnyKeyword | commandArgs.matchAnyKeyword);
+ }
+ }
+
+ // interpret perEventSourceSessionId's sign, and adjust perEventSourceSessionId to
+ // represent 0-based positive values
+ bool bSessionEnable = (commandArgs.perEventSourceSessionId >= 0);
+ if (commandArgs.perEventSourceSessionId == 0 && commandArgs.enable == false)
+ bSessionEnable = false;
+
+ if (commandArgs.listener == null)
+ {
+ if (!bSessionEnable)
+ commandArgs.perEventSourceSessionId = -commandArgs.perEventSourceSessionId;
+ // for "global" enable/disable (passed in with listener == null and
+ // perEventSourceSessionId == 0) perEventSourceSessionId becomes -1
+ --commandArgs.perEventSourceSessionId;
+ }
+
+ commandArgs.Command = bSessionEnable ? EventCommand.Enable : EventCommand.Disable;
+
+ // perEventSourceSessionId = -1 when ETW sent a notification, but the set of active sessions
+ // hasn't changed.
+ // sesisonId = SessionMask.MAX when one of the legacy ETW sessions changed
+ // 0 <= perEventSourceSessionId < SessionMask.MAX for activity-tracing aware sessions
+ Debug.Assert(commandArgs.perEventSourceSessionId >= -1 && commandArgs.perEventSourceSessionId <= SessionMask.MAX);
+
+ // Send the manifest if we are enabling an ETW session
+ if (bSessionEnable && commandArgs.dispatcher == null)
+ {
+ // eventSourceDispatcher == null means this is the ETW manifest
+
+#if !FEATURE_PERFTRACING
+ // Note that we unconditionally send the manifest whenever we are enabled, even if
+ // we were already enabled. This is because there may be multiple sessions active
+ // and we can't know that all the sessions have seen the manifest.
+ if (!SelfDescribingEvents)
+ SendManifest(m_rawManifest);
+#endif
+ }
+
+ // Turn on the enable bit before making the OnEventCommand callback This allows you to do useful
+ // things like log messages, or test if keywords are enabled in the callback.
+ if (commandArgs.enable)
+ {
+ Debug.Assert(m_eventData != null);
+ m_eventSourceEnabled = true;
+ }
+
+ this.OnEventCommand(commandArgs);
+ var eventCommandCallback = this.m_eventCommandExecuted;
+ if (eventCommandCallback != null)
+ eventCommandCallback(this, commandArgs);
+
+ if (!commandArgs.enable)
+ {
+ // If we are disabling, maybe we can turn on 'quick checks' to filter
+ // quickly. These are all just optimizations (since later checks will still filter)
+
+ // There is a good chance EnabledForAnyListener are not as accurate as
+ // they could be, go ahead and get a better estimate.
+ for (int i = 0; i < m_eventData.Length; i++)
+ {
+ bool isEnabledForAnyListener = false;
+ for (EventDispatcher dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next)
+ {
+ if (dispatcher.m_EventEnabled[i])
+ {
+ isEnabledForAnyListener = true;
+ break;
+ }
+ }
+ m_eventData[i].EnabledForAnyListener = isEnabledForAnyListener;
+ }
+
+ // If no events are enabled, disable the global enabled bit.
+ if (!AnyEventEnabled())
+ {
+ m_level = 0;
+ m_matchAnyKeyword = 0;
+ m_eventSourceEnabled = false;
+ }
+ }
+ }
+ else
+ {
+#if !FEATURE_PERFTRACING
+ if (commandArgs.Command == EventCommand.SendManifest)
+ {
+ // TODO: should we generate the manifest here if we hadn't already?
+ if (m_rawManifest != null)
+ SendManifest(m_rawManifest);
+ }
+#endif
+
+ // These are not used for non-update commands and thus should always be 'default' values
+ // Debug.Assert(enable == true);
+ // Debug.Assert(level == EventLevel.LogAlways);
+ // Debug.Assert(matchAnyKeyword == EventKeywords.None);
+
+ this.OnEventCommand(commandArgs);
+ var eventCommandCallback = m_eventCommandExecuted;
+ if (eventCommandCallback != null)
+ eventCommandCallback(this, commandArgs);
+ }
+ }
+ catch (Exception e)
+ {
+ // When the ETW session is created after the EventSource has registered with the ETW system
+ // we can send any error messages here.
+ ReportOutOfBandMessage("ERROR: Exception in Command Processing for EventSource " + Name + ": " + e.Message, true);
+ // We never throw when doing a command.
+ }
+ }
+
+ /// <summary>
+ /// If 'value is 'true' then set the eventSource so that 'dispatcher' will receive event with the eventId
+ /// of 'eventId. If value is 'false' disable the event for that dispatcher. If 'eventId' is out of
+ /// range return false, otherwise true.
+ /// </summary>
+ internal bool EnableEventForDispatcher(EventDispatcher dispatcher, int eventId, bool value)
+ {
+ if (dispatcher == null)
+ {
+ if (eventId >= m_eventData.Length)
+ return false;
+#if FEATURE_MANAGED_ETW
+ if (m_provider != null)
+ m_eventData[eventId].EnabledForETW = value;
+#endif
+ }
+ else
+ {
+ if (eventId >= dispatcher.m_EventEnabled.Length)
+ return false;
+ dispatcher.m_EventEnabled[eventId] = value;
+ if (value)
+ m_eventData[eventId].EnabledForAnyListener = true;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Returns true if any event at all is on.
+ /// </summary>
+ private bool AnyEventEnabled()
+ {
+ for (int i = 0; i < m_eventData.Length; i++)
+ if (m_eventData[i].EnabledForETW || m_eventData[i].EnabledForAnyListener)
+ return true;
+ return false;
+ }
+
+ private bool IsDisposed
+ {
+ get { return m_eventSourceDisposed; }
+ }
+
+ private void EnsureDescriptorsInitialized()
+ {
+#if !ES_BUILD_STANDALONE
+ Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock));
+#endif
+ if (m_eventData == null)
+ {
+ Guid eventSourceGuid = Guid.Empty;
+ string eventSourceName = null;
+ EventMetadata[] eventData = null;
+ byte[] manifest = null;
+
+ // Try the GetMetadata provided by the ILTransform in ProjectN. The default sets all to null, and in that case we fall back
+ // to the reflection approach.
+ GetMetadata(out eventSourceGuid, out eventSourceName, out eventData, out manifest);
+
+ if (eventSourceGuid.Equals(Guid.Empty) || eventSourceName == null || eventData == null || manifest == null)
+ {
+ // GetMetadata failed, so we have to set it via reflection.
+ Debug.Assert(m_rawManifest == null);
+
+ m_rawManifest = CreateManifestAndDescriptors(this.GetType(), Name, this);
+ Debug.Assert(m_eventData != null);
+
+ }
+ else
+ {
+ // GetMetadata worked, so set the fields as appropriate.
+ m_name = eventSourceName;
+ m_guid = eventSourceGuid;
+ m_eventData = eventData;
+ m_rawManifest = manifest;
+ }
+ // TODO Enforce singleton pattern
+ foreach (WeakReference eventSourceRef in EventListener.s_EventSources)
+ {
+ EventSource eventSource = eventSourceRef.Target as EventSource;
+ if (eventSource != null && eventSource.Guid == m_guid && !eventSource.IsDisposed)
+ {
+ if (eventSource != this)
+ {
+ throw new ArgumentException(SR.Format(SR.EventSource_EventSourceGuidInUse, m_guid));
+ }
+ }
+ }
+
+ // Make certain all dispatchers also have their arrays initialized
+ EventDispatcher dispatcher = m_Dispatchers;
+ while (dispatcher != null)
+ {
+ if (dispatcher.m_EventEnabled == null)
+ dispatcher.m_EventEnabled = new bool[m_eventData.Length];
+ dispatcher = dispatcher.m_Next;
+ }
+#if FEATURE_PERFTRACING
+ // Initialize the EventPipe event handles.
+ DefineEventPipeEvents();
+#endif
+ }
+ if (s_currentPid == 0)
+ {
+#if ES_BUILD_STANDALONE && !ES_BUILD_PCL && !CORECLR
+ // for non-BCL EventSource we must assert SecurityPermission
+ new SecurityPermission(PermissionState.Unrestricted).Assert();
+#endif
+ s_currentPid = Win32Native.GetCurrentProcessId();
+ }
+ }
+
+ // Send out the ETW manifest XML out to ETW
+ // Today, we only send the manifest to ETW, custom listeners don't get it.
+ private unsafe bool SendManifest(byte[] rawManifest)
+ {
+ bool success = true;
+
+ if (rawManifest == null)
+ return false;
+
+ Debug.Assert(!SelfDescribingEvents);
+
+#if FEATURE_MANAGED_ETW
+ fixed (byte* dataPtr = rawManifest)
+ {
+ // we don't want the manifest to show up in the event log channels so we specify as keywords
+ // everything but the first 8 bits (reserved for the 8 channels)
+ var manifestDescr = new EventDescriptor(0xFFFE, 1, 0, 0, 0xFE, 0xFFFE, 0x00ffFFFFffffFFFF);
+ ManifestEnvelope envelope = new ManifestEnvelope();
+
+ envelope.Format = ManifestEnvelope.ManifestFormats.SimpleXmlFormat;
+ envelope.MajorVersion = 1;
+ envelope.MinorVersion = 0;
+ envelope.Magic = 0x5B; // An unusual number that can be checked for consistency.
+ int dataLeft = rawManifest.Length;
+ envelope.ChunkNumber = 0;
+
+ EventProvider.EventData* dataDescrs = stackalloc EventProvider.EventData[2];
+
+ dataDescrs[0].Ptr = (ulong)&envelope;
+ dataDescrs[0].Size = (uint)sizeof(ManifestEnvelope);
+ dataDescrs[0].Reserved = 0;
+
+ dataDescrs[1].Ptr = (ulong)dataPtr;
+ dataDescrs[1].Reserved = 0;
+
+ int chunkSize = ManifestEnvelope.MaxChunkSize;
+ TRY_AGAIN_WITH_SMALLER_CHUNK_SIZE:
+ envelope.TotalChunks = (ushort)((dataLeft + (chunkSize - 1)) / chunkSize);
+ while (dataLeft > 0)
+ {
+ dataDescrs[1].Size = (uint)Math.Min(dataLeft, chunkSize);
+ if (m_provider != null)
+ {
+ if (!m_provider.WriteEvent(ref manifestDescr, IntPtr.Zero, null, null, 2, (IntPtr)dataDescrs))
+ {
+ // Turns out that if users set the BufferSize to something less than 64K then WriteEvent
+ // can fail. If we get this failure on the first chunk try again with something smaller
+ // The smallest BufferSize is 1K so if we get to 256 (to account for envelope overhead), we can give up making it smaller.
+ if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig)
+ {
+ if (envelope.ChunkNumber == 0 && chunkSize > 256)
+ {
+ chunkSize = chunkSize / 2;
+ goto TRY_AGAIN_WITH_SMALLER_CHUNK_SIZE;
+ }
+ }
+ success = false;
+ if (ThrowOnEventWriteErrors)
+ ThrowEventSourceException("SendManifest");
+ break;
+ }
+ }
+ dataLeft -= chunkSize;
+ dataDescrs[1].Ptr += (uint)chunkSize;
+ envelope.ChunkNumber++;
+
+ // For large manifests we want to not overflow any receiver's buffer. Most manifests will fit within
+ // 5 chunks, so only the largest manifests will hit the pause.
+ if ((envelope.ChunkNumber % 5) == 0)
+ {
+#if ES_BUILD_STANDALONE
+ Thread.Sleep(15);
+#else
+ RuntimeThread.Sleep(15);
+#endif
+ }
+ }
+ }
+#endif // FEATURE_MANAGED_ETW
+ return success;
+ }
+
+#if (ES_BUILD_PCL)
+ internal static Attribute GetCustomAttributeHelper(Type type, Type attributeType, EventManifestOptions flags = EventManifestOptions.None)
+ {
+ return GetCustomAttributeHelper(type.GetTypeInfo(), attributeType, flags);
+ }
+#endif
+
+ // Helper to deal with the fact that the type we are reflecting over might be loaded in the ReflectionOnly context.
+ // When that is the case, we have the build the custom assemblies on a member by hand.
+ internal static Attribute GetCustomAttributeHelper(MemberInfo member, Type attributeType, EventManifestOptions flags = EventManifestOptions.None)
+ {
+#if !ES_BUILD_PN
+ // On ProjectN, ReflectionOnly() always equals false. AllowEventSourceOverride is an option that allows either Microsoft.Diagnostics.Tracing or
+ // System.Diagnostics.Tracing EventSource to be considered valid. This should not mattter anywhere but in Microsoft.Diagnostics.Tracing (nuget package).
+ if (!member.Module.Assembly.ReflectionOnly() && (flags & EventManifestOptions.AllowEventSourceOverride) == 0)
+#endif // !ES_BUILD_PN
+ {
+ // Let the runtime to the work for us, since we can execute code in this context.
+ Attribute firstAttribute = null;
+ foreach (var attribute in member.GetCustomAttributes(attributeType, false))
+ {
+ firstAttribute = (Attribute)attribute;
+ break;
+ }
+ return firstAttribute;
+ }
+
+#if (!ES_BUILD_PCL && !ES_BUILD_PN)
+ // In the reflection only context, we have to do things by hand.
+ string fullTypeNameToFind = attributeType.FullName;
+
+#if EVENT_SOURCE_LEGACY_NAMESPACE_SUPPORT
+ fullTypeNameToFind = fullTypeNameToFind.Replace("System.Diagnostics.Eventing", "System.Diagnostics.Tracing");
+#endif
+
+ foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(member))
+ {
+ if (AttributeTypeNamesMatch(attributeType, data.Constructor.ReflectedType))
+ {
+ Attribute attr = null;
+
+ Debug.Assert(data.ConstructorArguments.Count <= 1);
+
+ if (data.ConstructorArguments.Count == 1)
+ {
+ attr = (Attribute)Activator.CreateInstance(attributeType, new object[] { data.ConstructorArguments[0].Value });
+ }
+ else if (data.ConstructorArguments.Count == 0)
+ {
+ attr = (Attribute)Activator.CreateInstance(attributeType);
+ }
+
+ if (attr != null)
+ {
+ Type t = attr.GetType();
+
+ foreach (CustomAttributeNamedArgument namedArgument in data.NamedArguments)
+ {
+ PropertyInfo p = t.GetProperty(namedArgument.MemberInfo.Name, BindingFlags.Public | BindingFlags.Instance);
+ object value = namedArgument.TypedValue.Value;
+
+ if (p.PropertyType.IsEnum)
+ {
+ value = Enum.Parse(p.PropertyType, value.ToString());
+ }
+
+ p.SetValue(attr, value, null);
+ }
+
+ return attr;
+ }
+ }
+ }
+
+ return null;
+#else // ES_BUILD_PCL && ES_BUILD_PN
+ // Don't use nameof here because the resource doesn't exist on some platforms, which results in a compilation error.
+ throw new ArgumentException("EventSource_PCLPlatformNotSupportedReflection", "EventSource");
+#endif
+ }
+
+ /// <summary>
+ /// Evaluates if two related "EventSource"-domain types should be considered the same
+ /// </summary>
+ /// <param name="attributeType">The attribute type in the load context - it's associated with the running
+ /// EventSource type. This type may be different fromt he base type of the user-defined EventSource.</param>
+ /// <param name="reflectedAttributeType">The attribute type in the reflection context - it's associated with
+ /// the user-defined EventSource, and is in the same assembly as the eventSourceType passed to
+ /// </param>
+ /// <returns>True - if the types should be considered equivalent, False - otherwise</returns>
+ private static bool AttributeTypeNamesMatch(Type attributeType, Type reflectedAttributeType)
+ {
+ return
+ // are these the same type?
+ attributeType == reflectedAttributeType ||
+ // are the full typenames equal?
+ string.Equals(attributeType.FullName, reflectedAttributeType.FullName, StringComparison.Ordinal) ||
+ // are the typenames equal and the namespaces under "Diagnostics.Tracing" (typically
+ // either Microsoft.Diagnostics.Tracing or System.Diagnostics.Tracing)?
+ string.Equals(attributeType.Name, reflectedAttributeType.Name, StringComparison.Ordinal) &&
+ attributeType.Namespace.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal) &&
+ (reflectedAttributeType.Namespace.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal)
+#if EVENT_SOURCE_LEGACY_NAMESPACE_SUPPORT
+ || reflectedAttributeType.Namespace.EndsWith("Diagnostics.Eventing", StringComparison.Ordinal)
+#endif
+);
+ }
+
+ private static Type GetEventSourceBaseType(Type eventSourceType, bool allowEventSourceOverride, bool reflectionOnly)
+ {
+ // return false for "object" and interfaces
+ if (eventSourceType.BaseType() == null)
+ return null;
+
+ // now go up the inheritance chain until hitting a concrete type ("object" at worse)
+ do
+ {
+ eventSourceType = eventSourceType.BaseType();
+ }
+ while (eventSourceType != null && eventSourceType.IsAbstract());
+
+ if (eventSourceType != null)
+ {
+ if (!allowEventSourceOverride)
+ {
+ if (reflectionOnly && eventSourceType.FullName != typeof(EventSource).FullName ||
+ !reflectionOnly && eventSourceType != typeof(EventSource))
+ return null;
+ }
+ else
+ {
+ if (eventSourceType.Name != "EventSource")
+ return null;
+ }
+ }
+ return eventSourceType;
+ }
+
+ // Use reflection to look at the attributes of a class, and generate a manifest for it (as UTF8) and
+ // return the UTF8 bytes. It also sets up the code:EventData structures needed to dispatch events
+ // at run time. 'source' is the event source to place the descriptors. If it is null,
+ // then the descriptors are not creaed, and just the manifest is generated.
+ private static byte[] CreateManifestAndDescriptors(Type eventSourceType, string eventSourceDllName, EventSource source,
+ EventManifestOptions flags = EventManifestOptions.None)
+ {
+ ManifestBuilder manifest = null;
+ bool bNeedsManifest = source != null ? !source.SelfDescribingEvents : true;
+ Exception exception = null; // exception that might get raised during validation b/c we couldn't/didn't recover from a previous error
+ byte[] res = null;
+
+ if (eventSourceType.IsAbstract() && (flags & EventManifestOptions.Strict) == 0)
+ return null;
+
+#if DEBUG && ES_BUILD_STANDALONE && TEST_SUPPORT
+ TestSupport.TestHooks.MaybeThrow(eventSourceType,
+ TestSupport.Category.ManifestError,
+ "EventSource_CreateManifestAndDescriptors",
+ new ArgumentException("EventSource_CreateManifestAndDescriptors"));
+#endif
+
+ try
+ {
+ MethodInfo[] methods = eventSourceType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
+ EventAttribute defaultEventAttribute;
+ int eventId = 1; // The number given to an event that does not have a explicitly given ID.
+ EventMetadata[] eventData = null;
+ Dictionary<string, string> eventsByName = null;
+ if (source != null || (flags & EventManifestOptions.Strict) != 0)
+ {
+ eventData = new EventMetadata[methods.Length + 1];
+ eventData[0].Name = ""; // Event 0 is the 'write messages string' event, and has an empty name.
+ }
+
+ // See if we have localization information.
+ ResourceManager resources = null;
+ EventSourceAttribute eventSourceAttrib = (EventSourceAttribute)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags);
+ if (eventSourceAttrib != null && eventSourceAttrib.LocalizationResources != null)
+ resources = new ResourceManager(eventSourceAttrib.LocalizationResources, eventSourceType.Assembly());
+
+ manifest = new ManifestBuilder(GetName(eventSourceType, flags), GetGuid(eventSourceType), eventSourceDllName,
+ resources, flags);
+
+ // Add an entry unconditionally for event ID 0 which will be for a string message.
+ manifest.StartEvent("EventSourceMessage", new EventAttribute(0) { Level = EventLevel.LogAlways, Task = (EventTask)0xFFFE });
+ manifest.AddEventParameter(typeof(string), "message");
+ manifest.EndEvent();
+
+ // eventSourceType must be sealed and must derive from this EventSource
+ if ((flags & EventManifestOptions.Strict) != 0)
+ {
+ bool typeMatch = GetEventSourceBaseType(eventSourceType, (flags & EventManifestOptions.AllowEventSourceOverride) != 0, eventSourceType.Assembly().ReflectionOnly()) != null;
+
+ if (!typeMatch)
+ {
+ manifest.ManifestError(SR.EventSource_TypeMustDeriveFromEventSource);
+ }
+ if (!eventSourceType.IsAbstract() && !eventSourceType.IsSealed())
+ {
+ manifest.ManifestError(SR.EventSource_TypeMustBeSealedOrAbstract);
+ }
+ }
+
+ // Collect task, opcode, keyword and channel information
+#if FEATURE_MANAGED_ETW_CHANNELS && FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ foreach (var providerEnumKind in new string[] { "Keywords", "Tasks", "Opcodes", "Channels" })
+#else
+ foreach (var providerEnumKind in new string[] { "Keywords", "Tasks", "Opcodes" })
+#endif
+ {
+ Type nestedType = eventSourceType.GetNestedType(providerEnumKind);
+ if (nestedType != null)
+ {
+ if (eventSourceType.IsAbstract())
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_AbstractMustNotDeclareKTOC, nestedType.Name));
+ }
+ else
+ {
+ foreach (FieldInfo staticField in nestedType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
+ {
+ AddProviderEnumKind(manifest, staticField, providerEnumKind);
+ }
+ }
+ }
+ }
+ // ensure we have keywords for the session-filtering reserved bits
+ {
+ manifest.AddKeyword("Session3", (long)0x1000 << 32);
+ manifest.AddKeyword("Session2", (long)0x2000 << 32);
+ manifest.AddKeyword("Session1", (long)0x4000 << 32);
+ manifest.AddKeyword("Session0", (long)0x8000 << 32);
+ }
+
+ if (eventSourceType != typeof(EventSource))
+ {
+ for (int i = 0; i < methods.Length; i++)
+ {
+ MethodInfo method = methods[i];
+ ParameterInfo[] args = method.GetParameters();
+
+ // Get the EventDescriptor (from the Custom attributes)
+ EventAttribute eventAttribute = (EventAttribute)GetCustomAttributeHelper(method, typeof(EventAttribute), flags);
+
+ // Compat: until v4.5.1 we ignored any non-void returning methods as well as virtual methods for
+ // the only reason of limiting the number of methods considered to be events. This broke a common
+ // design of having event sources implement specific interfaces. To fix this in a compatible way
+ // we will now allow both non-void returning and virtual methods to be Event methods, as long
+ // as they are marked with the [Event] attribute
+ if (/* method.IsVirtual || */ method.IsStatic)
+ {
+ continue;
+ }
+
+ if (eventSourceType.IsAbstract())
+ {
+ if (eventAttribute != null)
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_AbstractMustNotDeclareEventMethods, method.Name, eventAttribute.EventId));
+ }
+ continue;
+ }
+ else if (eventAttribute == null)
+ {
+ // Methods that don't return void can't be events, if they're NOT marked with [Event].
+ // (see Compat comment above)
+ if (method.ReturnType != typeof(void))
+ {
+ continue;
+ }
+
+ // Continue to ignore virtual methods if they do NOT have the [Event] attribute
+ // (see Compat comment above)
+ if (method.IsVirtual)
+ {
+ continue;
+ }
+
+ // If we explicitly mark the method as not being an event, then honor that.
+ if (GetCustomAttributeHelper(method, typeof(NonEventAttribute), flags) != null)
+ continue;
+
+ defaultEventAttribute = new EventAttribute(eventId);
+ eventAttribute = defaultEventAttribute;
+ }
+ else if (eventAttribute.EventId <= 0)
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_NeedPositiveId, method.Name), true);
+ continue; // don't validate anything else for this event
+ }
+ if (method.Name.LastIndexOf('.') >= 0)
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_EventMustNotBeExplicitImplementation, method.Name, eventAttribute.EventId));
+ }
+
+ eventId++;
+ string eventName = method.Name;
+
+ if (eventAttribute.Opcode == EventOpcode.Info) // We are still using the default opcode.
+ {
+ // By default pick a task ID derived from the EventID, starting with the highest task number and working back
+ bool noTask = (eventAttribute.Task == EventTask.None);
+ if (noTask)
+ eventAttribute.Task = (EventTask)(0xFFFE - eventAttribute.EventId);
+
+ // Unless we explicitly set the opcode to Info (to override the auto-generate of Start or Stop opcodes,
+ // pick a default opcode based on the event name (either Info or start or stop if the name ends with that suffix).
+ if (!eventAttribute.IsOpcodeSet)
+ eventAttribute.Opcode = GetOpcodeWithDefault(EventOpcode.Info, eventName);
+
+ // Make the stop opcode have the same task as the start opcode.
+ if (noTask)
+ {
+ if (eventAttribute.Opcode == EventOpcode.Start)
+ {
+ string taskName = eventName.Substring(0, eventName.Length - s_ActivityStartSuffix.Length); // Remove the Stop suffix to get the task name
+ if (string.Compare(eventName, 0, taskName, 0, taskName.Length) == 0 &&
+ string.Compare(eventName, taskName.Length, s_ActivityStartSuffix, 0, Math.Max(eventName.Length - taskName.Length, s_ActivityStartSuffix.Length)) == 0)
+ {
+ // Add a task that is just the task name for the start event. This suppress the auto-task generation
+ // That would otherwise happen (and create 'TaskName'Start as task name rather than just 'TaskName'
+ manifest.AddTask(taskName, (int)eventAttribute.Task);
+ }
+ }
+ else if (eventAttribute.Opcode == EventOpcode.Stop)
+ {
+ // Find the start associated with this stop event. We require start to be immediately before the stop
+ int startEventId = eventAttribute.EventId - 1;
+ if (eventData != null && startEventId < eventData.Length)
+ {
+ Debug.Assert(0 <= startEventId); // Since we reserve id 0, we know that id-1 is <= 0
+ EventMetadata startEventMetadata = eventData[startEventId];
+
+ // If you remove the Stop and add a Start does that name match the Start Event's Name?
+ // Ideally we would throw an error
+ string taskName = eventName.Substring(0, eventName.Length - s_ActivityStopSuffix.Length); // Remove the Stop suffix to get the task name
+ if (startEventMetadata.Descriptor.Opcode == (byte)EventOpcode.Start &&
+ string.Compare(startEventMetadata.Name, 0, taskName, 0, taskName.Length) == 0 &&
+ string.Compare(startEventMetadata.Name, taskName.Length, s_ActivityStartSuffix, 0, Math.Max(startEventMetadata.Name.Length - taskName.Length, s_ActivityStartSuffix.Length)) == 0)
+ {
+
+ // Make the stop event match the start event
+ eventAttribute.Task = (EventTask)startEventMetadata.Descriptor.Task;
+ noTask = false;
+ }
+ }
+ if (noTask && (flags & EventManifestOptions.Strict) != 0) // Throw an error if we can compatibly.
+ {
+ throw new ArgumentException(SR.EventSource_StopsFollowStarts);
+ }
+ }
+ }
+ }
+
+ bool hasRelatedActivityID = RemoveFirstArgIfRelatedActivityId(ref args);
+ if (!(source != null && source.SelfDescribingEvents))
+ {
+ manifest.StartEvent(eventName, eventAttribute);
+ for (int fieldIdx = 0; fieldIdx < args.Length; fieldIdx++)
+ {
+ manifest.AddEventParameter(args[fieldIdx].ParameterType, args[fieldIdx].Name);
+ }
+ manifest.EndEvent();
+ }
+
+ if (source != null || (flags & EventManifestOptions.Strict) != 0)
+ {
+ // Do checking for user errors (optional, but not a big deal so we do it).
+ DebugCheckEvent(ref eventsByName, eventData, method, eventAttribute, manifest, flags);
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ // add the channel keyword for Event Viewer channel based filters. This is added for creating the EventDescriptors only
+ // and is not required for the manifest
+ if (eventAttribute.Channel != EventChannel.None)
+ {
+ unchecked
+ {
+ eventAttribute.Keywords |= (EventKeywords)manifest.GetChannelKeyword(eventAttribute.Channel, (ulong)eventAttribute.Keywords);
+ }
+ }
+#endif
+ string eventKey = "event_" + eventName;
+ string msg = manifest.GetLocalizedMessage(eventKey, CultureInfo.CurrentUICulture, etwFormat: false);
+ // overwrite inline message with the localized message
+ if (msg != null) eventAttribute.Message = msg;
+
+ AddEventDescriptor(ref eventData, eventName, eventAttribute, args, hasRelatedActivityID);
+ }
+ }
+ }
+
+ // Tell the TraceLogging stuff where to start allocating its own IDs.
+ NameInfo.ReserveEventIDsBelow(eventId);
+
+ if (source != null)
+ {
+ TrimEventDescriptors(ref eventData);
+ source.m_eventData = eventData; // officially initialize it. We do this at most once (it is racy otherwise).
+#if FEATURE_MANAGED_ETW_CHANNELS
+ source.m_channelData = manifest.GetChannelData();
+#endif
+ }
+
+ // if this is an abstract event source we've already performed all the validation we can
+ if (!eventSourceType.IsAbstract() && (source == null || !source.SelfDescribingEvents))
+ {
+ bNeedsManifest = (flags & EventManifestOptions.OnlyIfNeededForRegistration) == 0
+#if FEATURE_MANAGED_ETW_CHANNELS
+ || manifest.GetChannelData().Length > 0
+#endif
+;
+
+ // if the manifest is not needed and we're not requested to validate the event source return early
+ if (!bNeedsManifest && (flags & EventManifestOptions.Strict) == 0)
+ return null;
+
+ res = manifest.CreateManifest();
+ }
+ }
+ catch (Exception e)
+ {
+ // if this is a runtime manifest generation let the exception propagate
+ if ((flags & EventManifestOptions.Strict) == 0)
+ throw;
+ // else store it to include it in the Argument exception we raise below
+ exception = e;
+ }
+
+ if ((flags & EventManifestOptions.Strict) != 0 && (manifest.Errors.Count > 0 || exception != null))
+ {
+ string msg = String.Empty;
+ if (manifest.Errors.Count > 0)
+ {
+ bool firstError = true;
+ foreach (string error in manifest.Errors)
+ {
+ if (!firstError)
+ msg += Environment.NewLine;
+ firstError = false;
+ msg += error;
+ }
+ }
+ else
+ msg = "Unexpected error: " + exception.Message;
+
+ throw new ArgumentException(msg, exception);
+ }
+
+ return bNeedsManifest ? res : null;
+ }
+
+ private static bool RemoveFirstArgIfRelatedActivityId(ref ParameterInfo[] args)
+ {
+ // If the first parameter is (case insensitive) 'relatedActivityId' then skip it.
+ if (args.Length > 0 && args[0].ParameterType == typeof(Guid) &&
+ string.Compare(args[0].Name, "relatedActivityId", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ var newargs = new ParameterInfo[args.Length - 1];
+ Array.Copy(args, 1, newargs, 0, args.Length - 1);
+ args = newargs;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // adds a enumeration (keyword, opcode, task or channel) represented by 'staticField'
+ // to the manifest.
+ private static void AddProviderEnumKind(ManifestBuilder manifest, FieldInfo staticField, string providerEnumKind)
+ {
+ bool reflectionOnly = staticField.Module.Assembly.ReflectionOnly();
+ Type staticFieldType = staticField.FieldType;
+ if (!reflectionOnly && (staticFieldType == typeof(EventOpcode)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventOpcode)))
+ {
+ if (providerEnumKind != "Opcodes") goto Error;
+ int value = (int)staticField.GetRawConstantValue();
+ manifest.AddOpcode(staticField.Name, value);
+ }
+ else if (!reflectionOnly && (staticFieldType == typeof(EventTask)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventTask)))
+ {
+ if (providerEnumKind != "Tasks") goto Error;
+ int value = (int)staticField.GetRawConstantValue();
+ manifest.AddTask(staticField.Name, value);
+ }
+ else if (!reflectionOnly && (staticFieldType == typeof(EventKeywords)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventKeywords)))
+ {
+ if (providerEnumKind != "Keywords") goto Error;
+ ulong value = unchecked((ulong)(long)staticField.GetRawConstantValue());
+ manifest.AddKeyword(staticField.Name, value);
+ }
+#if FEATURE_MANAGED_ETW_CHANNELS && FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ else if (!reflectionOnly && (staticFieldType == typeof(EventChannel)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventChannel)))
+ {
+ if (providerEnumKind != "Channels") goto Error;
+ var channelAttribute = (EventChannelAttribute)GetCustomAttributeHelper(staticField, typeof(EventChannelAttribute));
+ manifest.AddChannel(staticField.Name, (byte)staticField.GetRawConstantValue(), channelAttribute);
+ }
+#endif
+ return;
+ Error:
+ manifest.ManifestError(SR.Format(SR.EventSource_EnumKindMismatch, staticField.Name, staticField.FieldType.Name, providerEnumKind));
+ }
+
+ // Helper used by code:CreateManifestAndDescriptors to add a code:EventData descriptor for a method
+ // with the code:EventAttribute 'eventAttribute'. resourceManger may be null in which case we populate it
+ // it is populated if we need to look up message resources
+ private static void AddEventDescriptor(ref EventMetadata[] eventData, string eventName,
+ EventAttribute eventAttribute, ParameterInfo[] eventParameters,
+ bool hasRelatedActivityID)
+ {
+ if (eventData == null || eventData.Length <= eventAttribute.EventId)
+ {
+ EventMetadata[] newValues = new EventMetadata[Math.Max(eventData.Length + 16, eventAttribute.EventId + 1)];
+ Array.Copy(eventData, 0, newValues, 0, eventData.Length);
+ eventData = newValues;
+ }
+
+ eventData[eventAttribute.EventId].Descriptor = new EventDescriptor(
+ eventAttribute.EventId,
+ eventAttribute.Version,
+#if FEATURE_MANAGED_ETW_CHANNELS
+ (byte)eventAttribute.Channel,
+#else
+ (byte)0,
+#endif
+ (byte)eventAttribute.Level,
+ (byte)eventAttribute.Opcode,
+ (int)eventAttribute.Task,
+ unchecked((long)((ulong)eventAttribute.Keywords | SessionMask.All.ToEventKeywords())));
+
+ eventData[eventAttribute.EventId].Tags = eventAttribute.Tags;
+ eventData[eventAttribute.EventId].Name = eventName;
+ eventData[eventAttribute.EventId].Parameters = eventParameters;
+ eventData[eventAttribute.EventId].Message = eventAttribute.Message;
+ eventData[eventAttribute.EventId].ActivityOptions = eventAttribute.ActivityOptions;
+ eventData[eventAttribute.EventId].HasRelatedActivityID = hasRelatedActivityID;
+ eventData[eventAttribute.EventId].EventHandle = IntPtr.Zero;
+ }
+
+ // Helper used by code:CreateManifestAndDescriptors that trims the m_eventData array to the correct
+ // size after all event descriptors have been added.
+ private static void TrimEventDescriptors(ref EventMetadata[] eventData)
+ {
+ int idx = eventData.Length;
+ while (0 < idx)
+ {
+ --idx;
+ if (eventData[idx].Descriptor.EventId != 0)
+ break;
+ }
+ if (eventData.Length - idx > 2) // allow one wasted slot.
+ {
+ EventMetadata[] newValues = new EventMetadata[idx + 1];
+ Array.Copy(eventData, 0, newValues, 0, newValues.Length);
+ eventData = newValues;
+ }
+ }
+
+ // Helper used by code:EventListener.AddEventSource and code:EventListener.EventListener
+ // when a listener gets attached to a eventSource
+ internal void AddListener(EventListener listener)
+ {
+ lock (EventListener.EventListenersLock)
+ {
+ bool[] enabledArray = null;
+ if (m_eventData != null)
+ enabledArray = new bool[m_eventData.Length];
+ m_Dispatchers = new EventDispatcher(m_Dispatchers, enabledArray, listener);
+ listener.OnEventSourceCreated(this);
+ }
+ }
+
+ // Helper used by code:CreateManifestAndDescriptors to find user mistakes like reusing an event
+ // index for two distinct events etc. Throws exceptions when it finds something wrong.
+ private static void DebugCheckEvent(ref Dictionary<string, string> eventsByName,
+ EventMetadata[] eventData, MethodInfo method, EventAttribute eventAttribute,
+ ManifestBuilder manifest, EventManifestOptions options)
+ {
+ int evtId = eventAttribute.EventId;
+ string evtName = method.Name;
+ int eventArg = GetHelperCallFirstArg(method);
+ if (eventArg >= 0 && evtId != eventArg)
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_MismatchIdToWriteEvent, evtName, evtId, eventArg), true);
+ }
+
+ if (evtId < eventData.Length && eventData[evtId].Descriptor.EventId != 0)
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_EventIdReused, evtName, evtId, eventData[evtId].Name), true);
+ }
+
+ // We give a task to things if they don't have one.
+ // TODO this is moderately expensive (N*N). We probably should not even bother....
+ Debug.Assert(eventAttribute.Task != EventTask.None || eventAttribute.Opcode != EventOpcode.Info);
+ for (int idx = 0; idx < eventData.Length; ++idx)
+ {
+ // skip unused Event IDs.
+ if (eventData[idx].Name == null)
+ continue;
+
+ if (eventData[idx].Descriptor.Task == (int)eventAttribute.Task && eventData[idx].Descriptor.Opcode == (int)eventAttribute.Opcode)
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_TaskOpcodePairReused,
+ evtName, evtId, eventData[idx].Name, idx));
+ // If we are not strict stop on first error. We have had problems with really large providers taking forever. because of many errors.
+ if ((options & EventManifestOptions.Strict) == 0)
+ break;
+ }
+ }
+
+ // for non-default event opcodes the user must define a task!
+ if (eventAttribute.Opcode != EventOpcode.Info)
+ {
+ bool failure = false;
+ if (eventAttribute.Task == EventTask.None)
+ failure = true;
+ else
+ {
+ // If you have the auto-assigned Task, then you did not explicitly set one.
+ // This is OK for Start events because we have special logic to assign the task to a prefix derived from the event name
+ // But all other cases we want to catch the omission.
+ var autoAssignedTask = (EventTask)(0xFFFE - evtId);
+ if ((eventAttribute.Opcode != EventOpcode.Start && eventAttribute.Opcode != EventOpcode.Stop) && eventAttribute.Task == autoAssignedTask)
+ failure = true;
+ }
+ if (failure)
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_EventMustHaveTaskIfNonDefaultOpcode, evtName, evtId));
+ }
+ }
+
+ // If we ever want to enforce the rule: MethodName = TaskName + OpcodeName here's how:
+ // (the reason we don't is backwards compat and the need for handling this as a non-fatal error
+ // by eventRegister.exe)
+ // taskName & opcodeName could be passed in by the caller which has opTab & taskTab handy
+ // if (!(((int)eventAttribute.Opcode == 0 && evtName == taskName) || (evtName == taskName+opcodeName)))
+ // {
+ // throw new WarningException(SR.EventSource_EventNameDoesNotEqualTaskPlusOpcode);
+ // }
+
+ if (eventsByName == null)
+ eventsByName = new Dictionary<string, string>();
+
+ if (eventsByName.ContainsKey(evtName))
+ {
+ manifest.ManifestError(SR.Format(SR.EventSource_EventNameReused, evtName), true);
+ }
+
+ eventsByName[evtName] = evtName;
+ }
+
+ /// <summary>
+ /// This method looks at the IL and tries to pattern match against the standard
+ /// 'boilerplate' event body
+ /// <code>
+ /// { if (Enabled()) WriteEvent(#, ...) }
+ /// </code>
+ /// If the pattern matches, it returns the literal number passed as the first parameter to
+ /// the WriteEvent. This is used to find common user errors (mismatching this
+ /// number with the EventAttribute ID). It is only used for validation.
+ /// </summary>
+ /// <param name="method">The method to probe.</param>
+ /// <returns>The literal value or -1 if the value could not be determined. </returns>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Switch statement is clearer than alternatives")]
+ private static int GetHelperCallFirstArg(MethodInfo method)
+ {
+#if (!ES_BUILD_PCL && !ES_BUILD_PN)
+ // Currently searches for the following pattern
+ //
+ // ... // CAN ONLY BE THE INSTRUCTIONS BELOW
+ // LDARG0
+ // LDC.I4 XXX
+ // ... // CAN ONLY BE THE INSTRUCTIONS BELOW CAN'T BE A BRANCH OR A CALL
+ // CALL
+ // NOP // 0 or more times
+ // RET
+ //
+ // If we find this pattern we return the XXX. Otherwise we return -1.
+#if !CORECLR
+ (new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)).Assert();
+#endif
+ byte[] instrs = method.GetMethodBody().GetILAsByteArray();
+ int retVal = -1;
+ for (int idx = 0; idx < instrs.Length;)
+ {
+ switch (instrs[idx])
+ {
+ case 0: // NOP
+ case 1: // BREAK
+ case 2: // LDARG_0
+ case 3: // LDARG_1
+ case 4: // LDARG_2
+ case 5: // LDARG_3
+ case 6: // LDLOC_0
+ case 7: // LDLOC_1
+ case 8: // LDLOC_2
+ case 9: // LDLOC_3
+ case 10: // STLOC_0
+ case 11: // STLOC_1
+ case 12: // STLOC_2
+ case 13: // STLOC_3
+ break;
+ case 14: // LDARG_S
+ case 16: // STARG_S
+ idx++;
+ break;
+ case 20: // LDNULL
+ break;
+ case 21: // LDC_I4_M1
+ case 22: // LDC_I4_0
+ case 23: // LDC_I4_1
+ case 24: // LDC_I4_2
+ case 25: // LDC_I4_3
+ case 26: // LDC_I4_4
+ case 27: // LDC_I4_5
+ case 28: // LDC_I4_6
+ case 29: // LDC_I4_7
+ case 30: // LDC_I4_8
+ if (idx > 0 && instrs[idx - 1] == 2) // preceeded by LDARG0
+ retVal = instrs[idx] - 22;
+ break;
+ case 31: // LDC_I4_S
+ if (idx > 0 && instrs[idx - 1] == 2) // preceeded by LDARG0
+ retVal = instrs[idx + 1];
+ idx++;
+ break;
+ case 32: // LDC_I4
+ idx += 4;
+ break;
+ case 37: // DUP
+ break;
+ case 40: // CALL
+ idx += 4;
+
+ if (retVal >= 0)
+ {
+ // Is this call just before return?
+ for (int search = idx + 1; search < instrs.Length; search++)
+ {
+ if (instrs[search] == 42) // RET
+ return retVal;
+ if (instrs[search] != 0) // NOP
+ break;
+ }
+ }
+ retVal = -1;
+ break;
+ case 44: // BRFALSE_S
+ case 45: // BRTRUE_S
+ retVal = -1;
+ idx++;
+ break;
+ case 57: // BRFALSE
+ case 58: // BRTRUE
+ retVal = -1;
+ idx += 4;
+ break;
+ case 103: // CONV_I1
+ case 104: // CONV_I2
+ case 105: // CONV_I4
+ case 106: // CONV_I8
+ case 109: // CONV_U4
+ case 110: // CONV_U8
+ break;
+ case 140: // BOX
+ case 141: // NEWARR
+ idx += 4;
+ break;
+ case 162: // STELEM_REF
+ break;
+ case 254: // PREFIX
+ idx++;
+ // Covers the CEQ instructions used in debug code for some reason.
+ if (idx >= instrs.Length || instrs[idx] >= 6)
+ goto default;
+ break;
+ default:
+ /* Debug.Fail("Warning: User validation code sub-optimial: Unsuported opcode " + instrs[idx] +
+ " at " + idx + " in method " + method.Name); */
+ return -1;
+ }
+ idx++;
+ }
+#endif
+ return -1;
+ }
+
+#if false // This routine is not needed at all, it was used for unit test debugging.
+ [Conditional("DEBUG")]
+ private static void OutputDebugString(string msg)
+ {
+#if !ES_BUILD_PCL
+ msg = msg.TrimEnd('\r', '\n') +
+ string.Format(CultureInfo.InvariantCulture, ", Thrd({0})" + Environment.NewLine, Thread.CurrentThread.ManagedThreadId);
+ System.Diagnostics.Debugger.Log(0, null, msg);
+#endif
+ }
+#endif
+
+ /// <summary>
+ /// Sends an error message to the debugger (outputDebugString), as well as the EventListeners
+ /// It will do this even if the EventSource is not enabled.
+ /// TODO remove flush parameter it is not used.
+ /// </summary>
+ [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")]
+ internal void ReportOutOfBandMessage(string msg, bool flush)
+ {
+ try
+ {
+#if (!ES_BUILD_PCL && !ES_BUILD_PN)
+ // send message to debugger without delay
+ System.Diagnostics.Debugger.Log(0, null, String.Format("EventSource Error: {0}{1}", msg, Environment.NewLine));
+#endif
+
+ // Send it to all listeners.
+ if (m_outOfBandMessageCount < 16 - 1) // Note this is only if size byte
+ m_outOfBandMessageCount++;
+ else
+ {
+ if (m_outOfBandMessageCount == 16)
+ return;
+ m_outOfBandMessageCount = 16; // Mark that we hit the limit. Notify them that this is the case.
+ msg = "Reached message limit. End of EventSource error messages.";
+ }
+
+ WriteEventString(EventLevel.LogAlways, -1, msg);
+ WriteStringToAllListeners("EventSourceMessage", msg);
+ }
+ catch (Exception) { } // If we fail during last chance logging, well, we have to give up....
+ }
+
+ private EventSourceSettings ValidateSettings(EventSourceSettings settings)
+ {
+ var evtFormatMask = EventSourceSettings.EtwManifestEventFormat |
+ EventSourceSettings.EtwSelfDescribingEventFormat;
+ if ((settings & evtFormatMask) == evtFormatMask)
+ {
+ throw new ArgumentException(SR.EventSource_InvalidEventFormat, nameof(settings));
+ }
+
+ // If you did not explicitly ask for manifest, you get self-describing.
+ if ((settings & evtFormatMask) == 0)
+ settings |= EventSourceSettings.EtwSelfDescribingEventFormat;
+ return settings;
+ }
+
+ private bool ThrowOnEventWriteErrors
+ {
+ get { return (m_config & EventSourceSettings.ThrowOnEventWriteErrors) != 0; }
+ set
+ {
+ if (value) m_config |= EventSourceSettings.ThrowOnEventWriteErrors;
+ else m_config &= ~EventSourceSettings.ThrowOnEventWriteErrors;
+ }
+ }
+
+ private bool SelfDescribingEvents
+ {
+ get
+ {
+ Debug.Assert(((m_config & EventSourceSettings.EtwManifestEventFormat) != 0) !=
+ ((m_config & EventSourceSettings.EtwSelfDescribingEventFormat) != 0));
+ return (m_config & EventSourceSettings.EtwSelfDescribingEventFormat) != 0;
+ }
+ set
+ {
+ if (!value)
+ {
+ m_config |= EventSourceSettings.EtwManifestEventFormat;
+ m_config &= ~EventSourceSettings.EtwSelfDescribingEventFormat;
+ }
+ else
+ {
+ m_config |= EventSourceSettings.EtwSelfDescribingEventFormat;
+ m_config &= ~EventSourceSettings.EtwManifestEventFormat;
+ }
+ }
+ }
+
+ // private instance state
+ private string m_name; // My friendly name (privided in ctor)
+ internal int m_id; // A small integer that is unique to this instance.
+ private Guid m_guid; // GUID representing the ETW eventSource to the OS.
+ internal volatile EventMetadata[] m_eventData; // None per-event data
+ private volatile byte[] m_rawManifest; // Bytes to send out representing the event schema
+
+ private EventHandler<EventCommandEventArgs> m_eventCommandExecuted;
+
+ private EventSourceSettings m_config; // configuration information
+
+ private bool m_eventSourceDisposed; // has Dispose been called.
+
+ // Enabling bits
+ private bool m_eventSourceEnabled; // am I enabled (any of my events are enabled for any dispatcher)
+ internal EventLevel m_level; // highest level enabled by any output dispatcher
+ internal EventKeywords m_matchAnyKeyword; // the logical OR of all levels enabled by any output dispatcher (zero is a special case) meaning 'all keywords'
+
+ // Dispatching state
+ internal volatile EventDispatcher m_Dispatchers; // Linked list of code:EventDispatchers we write the data to (we also do ETW specially)
+#if FEATURE_MANAGED_ETW
+ private volatile OverideEventProvider m_provider; // This hooks up ETW commands to our 'OnEventCommand' callback
+#endif
+ private bool m_completelyInited; // The EventSource constructor has returned without exception.
+ private Exception m_constructionException; // If there was an exception construction, this is it
+ private byte m_outOfBandMessageCount; // The number of out of band messages sent (we throttle them
+ private EventCommandEventArgs m_deferredCommands;// If we get commands before we are fully we store them here and run the when we are fully inited.
+
+ private string[] m_traits; // Used to implement GetTraits
+
+ internal static uint s_currentPid; // current process id, used in synthesizing quasi-GUIDs
+ [ThreadStatic]
+ private static byte m_EventSourceExceptionRecurenceCount = 0; // current recursion count inside ThrowEventSourceException
+
+ [ThreadStatic]
+ private static bool m_EventSourceInDecodeObject = false;
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ internal volatile ulong[] m_channelData;
+#endif
+
+ // We use a single instance of ActivityTracker for all EventSources instances to allow correlation between multiple event providers.
+ // We have m_activityTracker field simply because instance field is more efficient than static field fetch.
+ ActivityTracker m_activityTracker;
+ internal const string s_ActivityStartSuffix = "Start";
+ internal const string s_ActivityStopSuffix = "Stop";
+
+ // used for generating GUID from eventsource name
+ private static readonly byte[] namespaceBytes = new byte[] {
+ 0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8,
+ 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB,
+ };
+
+#endregion
+ }
+
+ /// <summary>
+ /// Enables specifying event source configuration options to be used in the EventSource constructor.
+ /// </summary>
+ [Flags]
+ public enum EventSourceSettings
+ {
+ /// <summary>
+ /// This specifies none of the special configuration options should be enabled.
+ /// </summary>
+ Default = 0,
+ /// <summary>
+ /// Normally an EventSource NEVER throws; setting this option will tell it to throw when it encounters errors.
+ /// </summary>
+ ThrowOnEventWriteErrors = 1,
+ /// <summary>
+ /// Setting this option is a directive to the ETW listener should use manifest-based format when
+ /// firing events. This is the default option when defining a type derived from EventSource
+ /// (using the protected EventSource constructors).
+ /// Only one of EtwManifestEventFormat or EtwSelfDescribingEventFormat should be specified
+ /// </summary>
+ EtwManifestEventFormat = 4,
+ /// <summary>
+ /// Setting this option is a directive to the ETW listener should use self-describing event format
+ /// when firing events. This is the default option when creating a new instance of the EventSource
+ /// type (using the public EventSource constructors).
+ /// Only one of EtwManifestEventFormat or EtwSelfDescribingEventFormat should be specified
+ /// </summary>
+ EtwSelfDescribingEventFormat = 8,
+ }
+
+ /// <summary>
+ /// An EventListener represents a target for the events generated by EventSources (that is subclasses
+ /// of <see cref="EventSource"/>), in the current appdomain. When a new EventListener is created
+ /// it is logically attached to all eventSources in that appdomain. When the EventListener is Disposed, then
+ /// it is disconnected from the event eventSources. Note that there is a internal list of STRONG references
+ /// to EventListeners, which means that relying on the lack of references to EventListeners to clean up
+ /// EventListeners will NOT work. You must call EventListener.Dispose explicitly when a dispatcher is no
+ /// longer needed.
+ /// <para>
+ /// Once created, EventListeners can enable or disable on a per-eventSource basis using verbosity levels
+ /// (<see cref="EventLevel"/>) and bitfields (<see cref="EventKeywords"/>) to further restrict the set of
+ /// events to be sent to the dispatcher. The dispatcher can also send arbitrary commands to a particular
+ /// eventSource using the 'SendCommand' method. The meaning of the commands are eventSource specific.
+ /// </para><para>
+ /// The Null Guid (that is (new Guid()) has special meaning as a wildcard for 'all current eventSources in
+ /// the appdomain'. Thus it is relatively easy to turn on all events in the appdomain if desired.
+ /// </para><para>
+ /// It is possible for there to be many EventListener's defined in a single appdomain. Each dispatcher is
+ /// logically independent of the other listeners. Thus when one dispatcher enables or disables events, it
+ /// affects only that dispatcher (other listeners get the events they asked for). It is possible that
+ /// commands sent with 'SendCommand' would do a semantic operation that would affect the other listeners
+ /// (like doing a GC, or flushing data ...), but this is the exception rather than the rule.
+ /// </para><para>
+ /// Thus the model is that each EventSource keeps a list of EventListeners that it is sending events
+ /// to. Associated with each EventSource-dispatcher pair is a set of filtering criteria that determine for
+ /// that eventSource what events that dispatcher will receive.
+ /// </para><para>
+ /// Listeners receive the events on their 'OnEventWritten' method. Thus subclasses of EventListener must
+ /// override this method to do something useful with the data.
+ /// </para><para>
+ /// In addition, when new eventSources are created, the 'OnEventSourceCreate' method is called. The
+ /// invariant associated with this callback is that every eventSource gets exactly one
+ /// 'OnEventSourceCreate' call for ever eventSource that can potentially send it log messages. In
+ /// particular when a EventListener is created, typically a series of OnEventSourceCreate' calls are
+ /// made to notify the new dispatcher of all the eventSources that existed before the EventListener was
+ /// created.
+ /// </para>
+ /// </summary>
+ public class EventListener : IDisposable
+ {
+ private event EventHandler<EventSourceCreatedEventArgs> _EventSourceCreated;
+
+ /// <summary>
+ /// This event is raised whenever a new eventSource is 'attached' to the dispatcher.
+ /// This can happen for all existing EventSources when the EventListener is created
+ /// as well as for any EventSources that come into existence after the EventListener
+ /// has been created.
+ ///
+ /// These 'catch up' events are called during the construction of the EventListener.
+ /// Subclasses need to be prepared for that.
+ ///
+ /// In a multi-threaded environment, it is possible that 'EventSourceEventWrittenCallback'
+ /// events for a particular eventSource to occur BEFORE the EventSourceCreatedCallback is issued.
+ /// </summary>
+ public event EventHandler<EventSourceCreatedEventArgs> EventSourceCreated
+ {
+ add
+ {
+ CallBackForExistingEventSources(false, value);
+
+ this._EventSourceCreated = (EventHandler<EventSourceCreatedEventArgs>)Delegate.Combine(_EventSourceCreated, value);
+ }
+ remove
+ {
+ this._EventSourceCreated = (EventHandler<EventSourceCreatedEventArgs>)Delegate.Remove(_EventSourceCreated, value);
+ }
+ }
+
+ /// <summary>
+ /// This event is raised whenever an event has been written by a EventSource for which
+ /// the EventListener has enabled events.
+ /// </summary>
+ public event EventHandler<EventWrittenEventArgs> EventWritten;
+
+ /// <summary>
+ /// Create a new EventListener in which all events start off turned off (use EnableEvents to turn
+ /// them on).
+ /// </summary>
+ public EventListener()
+ {
+ // This will cause the OnEventSourceCreated callback to fire.
+ CallBackForExistingEventSources(true, (obj, args) => args.EventSource.AddListener((EventListener)obj));
+ }
+
+ /// <summary>
+ /// Dispose should be called when the EventListener no longer desires 'OnEvent*' callbacks. Because
+ /// there is an internal list of strong references to all EventListeners, calling 'Dispose' directly
+ /// is the only way to actually make the listen die. Thus it is important that users of EventListener
+ /// call Dispose when they are done with their logging.
+ /// </summary>
+#if ES_BUILD_STANDALONE
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
+#endif
+ public virtual void Dispose()
+ {
+ lock (EventListenersLock)
+ {
+ if (s_Listeners != null)
+ {
+ if (this == s_Listeners)
+ {
+ EventListener cur = s_Listeners;
+ s_Listeners = this.m_Next;
+ RemoveReferencesToListenerInEventSources(cur);
+ }
+ else
+ {
+ // Find 'this' from the s_Listeners linked list.
+ EventListener prev = s_Listeners;
+ for (;;)
+ {
+ EventListener cur = prev.m_Next;
+ if (cur == null)
+ break;
+ if (cur == this)
+ {
+ // Found our Listener, remove references to to it in the eventSources
+ prev.m_Next = cur.m_Next; // Remove entry.
+ RemoveReferencesToListenerInEventSources(cur);
+ break;
+ }
+ prev = cur;
+ }
+ }
+ }
+ Validate();
+ }
+ }
+ // We don't expose a Dispose(bool), because the contract is that you don't have any non-syncronous
+ // 'cleanup' associated with this object
+
+ /// <summary>
+ /// Enable all events from the eventSource identified by 'eventSource' to the current
+ /// dispatcher that have a verbosity level of 'level' or lower.
+ ///
+ /// This call can have the effect of REDUCING the number of events sent to the
+ /// dispatcher if 'level' indicates a less verbose level than was previously enabled.
+ ///
+ /// This call never has an effect on other EventListeners.
+ ///
+ /// </summary>
+ public void EnableEvents(EventSource eventSource, EventLevel level)
+ {
+ EnableEvents(eventSource, level, EventKeywords.None);
+ }
+ /// <summary>
+ /// Enable all events from the eventSource identified by 'eventSource' to the current
+ /// dispatcher that have a verbosity level of 'level' or lower and have a event keyword
+ /// matching any of the bits in 'matchAnyKeyword'.
+ ///
+ /// This call can have the effect of REDUCING the number of events sent to the
+ /// dispatcher if 'level' indicates a less verbose level than was previously enabled or
+ /// if 'matchAnyKeyword' has fewer keywords set than where previously set.
+ ///
+ /// This call never has an effect on other EventListeners.
+ /// </summary>
+ public void EnableEvents(EventSource eventSource, EventLevel level, EventKeywords matchAnyKeyword)
+ {
+ EnableEvents(eventSource, level, matchAnyKeyword, null);
+ }
+ /// <summary>
+ /// Enable all events from the eventSource identified by 'eventSource' to the current
+ /// dispatcher that have a verbosity level of 'level' or lower and have a event keyword
+ /// matching any of the bits in 'matchAnyKeyword' as well as any (eventSource specific)
+ /// effect passing additional 'key-value' arguments 'arguments' might have.
+ ///
+ /// This call can have the effect of REDUCING the number of events sent to the
+ /// dispatcher if 'level' indicates a less verbose level than was previously enabled or
+ /// if 'matchAnyKeyword' has fewer keywords set than where previously set.
+ ///
+ /// This call never has an effect on other EventListeners.
+ /// </summary>
+ public void EnableEvents(EventSource eventSource, EventLevel level, EventKeywords matchAnyKeyword, IDictionary<string, string> arguments)
+ {
+ if (eventSource == null)
+ {
+ throw new ArgumentNullException(nameof(eventSource));
+ }
+
+ eventSource.SendCommand(this, 0, 0, EventCommand.Update, true, level, matchAnyKeyword, arguments);
+ }
+ /// <summary>
+ /// Disables all events coming from eventSource identified by 'eventSource'.
+ ///
+ /// This call never has an effect on other EventListeners.
+ /// </summary>
+ public void DisableEvents(EventSource eventSource)
+ {
+ if (eventSource == null)
+ {
+ throw new ArgumentNullException(nameof(eventSource));
+ }
+
+ eventSource.SendCommand(this, 0, 0, EventCommand.Update, false, EventLevel.LogAlways, EventKeywords.None, null);
+ }
+
+ /// <summary>
+ /// EventSourceIndex is small non-negative integer (suitable for indexing in an array)
+ /// identifying EventSource. It is unique per-appdomain. Some EventListeners might find
+ /// it useful to store additional information about each eventSource connected to it,
+ /// and EventSourceIndex allows this extra information to be efficiently stored in a
+ /// (growable) array (eg List(T)).
+ /// </summary>
+ public static int EventSourceIndex(EventSource eventSource) { return eventSource.m_id; }
+
+ /// <summary>
+ /// This method is called whenever a new eventSource is 'attached' to the dispatcher.
+ /// This can happen for all existing EventSources when the EventListener is created
+ /// as well as for any EventSources that come into existence after the EventListener
+ /// has been created.
+ ///
+ /// These 'catch up' events are called during the construction of the EventListener.
+ /// Subclasses need to be prepared for that.
+ ///
+ /// In a multi-threaded environment, it is possible that 'OnEventWritten' callbacks
+ /// for a particular eventSource to occur BEFORE the OnEventSourceCreated is issued.
+ /// </summary>
+ /// <param name="eventSource"></param>
+ internal protected virtual void OnEventSourceCreated(EventSource eventSource)
+ {
+ EventHandler<EventSourceCreatedEventArgs> callBack = this._EventSourceCreated;
+ if (callBack != null)
+ {
+ EventSourceCreatedEventArgs args = new EventSourceCreatedEventArgs();
+ args.EventSource = eventSource;
+ callBack(this, args);
+ }
+ }
+
+ /// <summary>
+ /// This method is called whenever an event has been written by a EventSource for which
+ /// the EventListener has enabled events.
+ /// </summary>
+ /// <param name="eventData"></param>
+ internal protected virtual void OnEventWritten(EventWrittenEventArgs eventData)
+ {
+ EventHandler<EventWrittenEventArgs> callBack = this.EventWritten;
+ if (callBack != null)
+ {
+ callBack(this, eventData);
+ }
+ }
+
+
+#region private
+ /// <summary>
+ /// This routine adds newEventSource to the global list of eventSources, it also assigns the
+ /// ID to the eventSource (which is simply the ordinal in the global list).
+ ///
+ /// EventSources currently do not pro-actively remove themselves from this list. Instead
+ /// when eventSources's are GCed, the weak handle in this list naturally gets nulled, and
+ /// we will reuse the slot. Today this list never shrinks (but we do reuse entries
+ /// that are in the list). This seems OK since the expectation is that EventSources
+ /// tend to live for the lifetime of the appdomain anyway (they tend to be used in
+ /// global variables).
+ /// </summary>
+ /// <param name="newEventSource"></param>
+ internal static void AddEventSource(EventSource newEventSource)
+ {
+ lock (EventListenersLock)
+ {
+ if (s_EventSources == null)
+ s_EventSources = new List<WeakReference>(2);
+
+ if (!s_EventSourceShutdownRegistered)
+ {
+ s_EventSourceShutdownRegistered = true;
+ }
+
+
+ // Periodically search the list for existing entries to reuse, this avoids
+ // unbounded memory use if we keep recycling eventSources (an unlikely thing).
+ int newIndex = -1;
+ if (s_EventSources.Count % 64 == 63) // on every block of 64, fill up the block before continuing
+ {
+ int i = s_EventSources.Count; // Work from the top down.
+ while (0 < i)
+ {
+ --i;
+ WeakReference weakRef = s_EventSources[i];
+ if (!weakRef.IsAlive)
+ {
+ newIndex = i;
+ weakRef.Target = newEventSource;
+ break;
+ }
+ }
+ }
+ if (newIndex < 0)
+ {
+ newIndex = s_EventSources.Count;
+ s_EventSources.Add(new WeakReference(newEventSource));
+ }
+ newEventSource.m_id = newIndex;
+
+ // Add every existing dispatcher to the new EventSource
+ for (EventListener listener = s_Listeners; listener != null; listener = listener.m_Next)
+ newEventSource.AddListener(listener);
+
+ Validate();
+ }
+ }
+
+ // Whenver we have async callbacks from native code, there is an ugly issue where
+ // during .NET shutdown native code could be calling the callback, but the CLR
+ // has already prohibited callbacks to managed code in the appdomain, causing the CLR
+ // to throw a COMPLUS_BOOT_EXCEPTION. The guideline we give is that you must unregister
+ // such callbacks on process shutdown or appdomain so that unmanaged code will never
+ // do this. This is what this callback is for.
+ // See bug 724140 for more
+ private static void DisposeOnShutdown(object sender, EventArgs e)
+ {
+ lock (EventListenersLock)
+ {
+ foreach (var esRef in s_EventSources)
+ {
+ EventSource es = esRef.Target as EventSource;
+ if (es != null)
+ es.Dispose();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Helper used in code:Dispose that removes any references to 'listenerToRemove' in any of the
+ /// eventSources in the appdomain.
+ ///
+ /// The EventListenersLock must be held before calling this routine.
+ /// </summary>
+ private static void RemoveReferencesToListenerInEventSources(EventListener listenerToRemove)
+ {
+#if !ES_BUILD_STANDALONE
+ Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock));
+#endif
+ // Foreach existing EventSource in the appdomain
+ foreach (WeakReference eventSourceRef in s_EventSources)
+ {
+ EventSource eventSource = eventSourceRef.Target as EventSource;
+ if (eventSource != null)
+ {
+ // Is the first output dispatcher the dispatcher we are removing?
+ if (eventSource.m_Dispatchers.m_Listener == listenerToRemove)
+ eventSource.m_Dispatchers = eventSource.m_Dispatchers.m_Next;
+ else
+ {
+ // Remove 'listenerToRemove' from the eventSource.m_Dispatchers linked list.
+ EventDispatcher prev = eventSource.m_Dispatchers;
+ for (;;)
+ {
+ EventDispatcher cur = prev.m_Next;
+ if (cur == null)
+ {
+ Debug.Fail("EventSource did not have a registered EventListener!");
+ break;
+ }
+ if (cur.m_Listener == listenerToRemove)
+ {
+ prev.m_Next = cur.m_Next; // Remove entry.
+ break;
+ }
+ prev = cur;
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Checks internal consistency of EventSources/Listeners.
+ /// </summary>
+ [Conditional("DEBUG")]
+ internal static void Validate()
+ {
+ lock (EventListenersLock)
+ {
+ // Get all listeners
+ Dictionary<EventListener, bool> allListeners = new Dictionary<EventListener, bool>();
+ EventListener cur = s_Listeners;
+ while (cur != null)
+ {
+ allListeners.Add(cur, true);
+ cur = cur.m_Next;
+ }
+
+ // For all eventSources
+ int id = -1;
+ foreach (WeakReference eventSourceRef in s_EventSources)
+ {
+ id++;
+ EventSource eventSource = eventSourceRef.Target as EventSource;
+ if (eventSource == null)
+ continue;
+ Debug.Assert(eventSource.m_id == id, "Unexpected event source ID.");
+
+ // None listeners on eventSources exist in the dispatcher list.
+ EventDispatcher dispatcher = eventSource.m_Dispatchers;
+ while (dispatcher != null)
+ {
+ Debug.Assert(allListeners.ContainsKey(dispatcher.m_Listener), "EventSource has a listener not on the global list.");
+ dispatcher = dispatcher.m_Next;
+ }
+
+ // Every dispatcher is on Dispatcher List of every eventSource.
+ foreach (EventListener listener in allListeners.Keys)
+ {
+ dispatcher = eventSource.m_Dispatchers;
+ for (;;)
+ {
+ Debug.Assert(dispatcher != null, "Listener is not on all eventSources.");
+ if (dispatcher.m_Listener == listener)
+ break;
+ dispatcher = dispatcher.m_Next;
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets a global lock that is intended to protect the code:s_Listeners linked list and the
+ /// code:s_EventSources WeakReference list. (We happen to use the s_EventSources list as
+ /// the lock object)
+ /// </summary>
+ internal static object EventListenersLock
+ {
+ get
+ {
+ if (s_EventSources == null)
+ Interlocked.CompareExchange(ref s_EventSources, new List<WeakReference>(2), null);
+ return s_EventSources;
+ }
+ }
+
+ private void CallBackForExistingEventSources(bool addToListenersList, EventHandler<EventSourceCreatedEventArgs> callback)
+ {
+ lock (EventListenersLock)
+ {
+ // Disallow creating EventListener reentrancy.
+ if (s_CreatingListener)
+ {
+ throw new InvalidOperationException(SR.EventSource_ListenerCreatedInsideCallback);
+ }
+
+ try
+ {
+ s_CreatingListener = true;
+
+ if (addToListenersList)
+ {
+ // Add to list of listeners in the system, do this BEFORE firing the 'OnEventSourceCreated' so that
+ // Those added sources see this listener.
+ this.m_Next = s_Listeners;
+ s_Listeners = this;
+ }
+
+ // Find all existing eventSources call OnEventSourceCreated to 'catchup'
+ // Note that we DO have reentrancy here because 'AddListener' calls out to user code (via OnEventSourceCreated callback)
+ // We tolerate this by iterating over a copy of the list here. New event sources will take care of adding listeners themselves
+ // EventSources are not guaranteed to be added at the end of the s_EventSource list -- We re-use slots when a new source
+ // is created.
+ WeakReference[] eventSourcesSnapshot = s_EventSources.ToArray();
+
+ for (int i = 0; i < eventSourcesSnapshot.Length; i++)
+ {
+ WeakReference eventSourceRef = eventSourcesSnapshot[i];
+ EventSource eventSource = eventSourceRef.Target as EventSource;
+ if (eventSource != null)
+ {
+ EventSourceCreatedEventArgs args = new EventSourceCreatedEventArgs();
+ args.EventSource = eventSource;
+ callback(this, args);
+ }
+ }
+
+ Validate();
+ }
+ finally
+ {
+ s_CreatingListener = false;
+ }
+ }
+
+ }
+
+ // Instance fields
+ internal volatile EventListener m_Next; // These form a linked list in s_Listeners
+
+ // static fields
+
+ /// <summary>
+ /// The list of all listeners in the appdomain. Listeners must be explicitly disposed to remove themselves
+ /// from this list. Note that EventSources point to their listener but NOT the reverse.
+ /// </summary>
+ internal static EventListener s_Listeners;
+ /// <summary>
+ /// The list of all active eventSources in the appdomain. Note that eventSources do NOT
+ /// remove themselves from this list this is a weak list and the GC that removes them may
+ /// not have happened yet. Thus it can contain event sources that are dead (thus you have
+ /// to filter those out.
+ /// </summary>
+ internal static List<WeakReference> s_EventSources;
+
+ /// <summary>
+ /// Used to disallow reentrancy.
+ /// </summary>
+ private static bool s_CreatingListener = false;
+
+ /// <summary>
+ /// Used to register AD/Process shutdown callbacks.
+ /// </summary>
+ private static bool s_EventSourceShutdownRegistered = false;
+#endregion
+ }
+
+ /// <summary>
+ /// Passed to the code:EventSource.OnEventCommand callback
+ /// </summary>
+ public class EventCommandEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Gets the command for the callback.
+ /// </summary>
+ public EventCommand Command { get; internal set; }
+
+ /// <summary>
+ /// Gets the arguments for the callback.
+ /// </summary>
+ public IDictionary<String, String> Arguments { get; internal set; }
+
+ /// <summary>
+ /// Enables the event that has the specified identifier.
+ /// </summary>
+ /// <param name="eventId">Event ID of event to be enabled</param>
+ /// <returns>true if eventId is in range</returns>
+ public bool EnableEvent(int eventId)
+ {
+ if (Command != EventCommand.Enable && Command != EventCommand.Disable)
+ throw new InvalidOperationException();
+ return eventSource.EnableEventForDispatcher(dispatcher, eventId, true);
+ }
+
+ /// <summary>
+ /// Disables the event that have the specified identifier.
+ /// </summary>
+ /// <param name="eventId">Event ID of event to be disabled</param>
+ /// <returns>true if eventId is in range</returns>
+ public bool DisableEvent(int eventId)
+ {
+ if (Command != EventCommand.Enable && Command != EventCommand.Disable)
+ throw new InvalidOperationException();
+ return eventSource.EnableEventForDispatcher(dispatcher, eventId, false);
+ }
+
+#region private
+
+ internal EventCommandEventArgs(EventCommand command, IDictionary<string, string> arguments, EventSource eventSource,
+ EventListener listener, int perEventSourceSessionId, int etwSessionId, bool enable, EventLevel level, EventKeywords matchAnyKeyword)
+ {
+ this.Command = command;
+ this.Arguments = arguments;
+ this.eventSource = eventSource;
+ this.listener = listener;
+ this.perEventSourceSessionId = perEventSourceSessionId;
+ this.etwSessionId = etwSessionId;
+ this.enable = enable;
+ this.level = level;
+ this.matchAnyKeyword = matchAnyKeyword;
+ }
+
+ internal EventSource eventSource;
+ internal EventDispatcher dispatcher;
+
+ // These are the arguments of sendCommand and are only used for deferring commands until after we are fully initialized.
+ internal EventListener listener;
+ internal int perEventSourceSessionId;
+ internal int etwSessionId;
+ internal bool enable;
+ internal EventLevel level;
+ internal EventKeywords matchAnyKeyword;
+ internal EventCommandEventArgs nextCommand; // We form a linked list of these deferred commands.
+
+#endregion
+ }
+
+ /// <summary>
+ /// EventSourceCreatedEventArgs is passed to <see cref="EventListener.EventSourceCreated"/>
+ /// </summary>
+ public class EventSourceCreatedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// The EventSource that is attaching to the listener.
+ /// </summary>
+ public EventSource EventSource
+ {
+ get;
+ internal set;
+ }
+ }
+
+ /// <summary>
+ /// EventWrittenEventArgs is passed to the user-provided override for
+ /// <see cref="EventListener.OnEventWritten"/> when an event is fired.
+ /// </summary>
+ public class EventWrittenEventArgs : EventArgs
+ {
+ /// <summary>
+ /// The name of the event.
+ /// </summary>
+ public string EventName
+ {
+ get
+ {
+ if (m_eventName != null || EventId < 0) // TraceLogging convention EventID == -1
+ {
+ return m_eventName;
+ }
+ else
+ return m_eventSource.m_eventData[EventId].Name;
+ }
+ internal set
+ {
+ m_eventName = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the event ID for the event that was written.
+ /// </summary>
+ public int EventId { get; internal set; }
+
+ /// <summary>
+ /// Gets the activity ID for the thread on which the event was written.
+ /// </summary>
+ public Guid ActivityId
+ {
+ get
+ {
+ Guid activityId = m_activityId;
+ if (activityId == Guid.Empty)
+ {
+ activityId = EventSource.CurrentThreadActivityId;
+ }
+
+ return activityId;
+ }
+ internal set
+ {
+ m_activityId = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the related activity ID if one was specified when the event was written.
+ /// </summary>
+ public Guid RelatedActivityId
+ {
+ get;
+ internal set;
+ }
+
+ /// <summary>
+ /// Gets the payload for the event.
+ /// </summary>
+ public ReadOnlyCollection<Object> Payload { get; internal set; }
+
+ /// <summary>
+ /// Gets the payload argument names.
+ /// </summary>
+ public ReadOnlyCollection<string> PayloadNames
+ {
+ get
+ {
+ // For contract based events we create the list lazily.
+ // You can have m_payloadNames be null in the TraceLogging case (EventID < 0) so only
+ // do the lazy init if you know it is contract based (EventID >= 0)
+ if (EventId >= 0 && m_payloadNames == null)
+ {
+
+ var names = new List<string>();
+ foreach (var parameter in m_eventSource.m_eventData[EventId].Parameters)
+ {
+ names.Add(parameter.Name);
+ }
+ m_payloadNames = new ReadOnlyCollection<string>(names);
+ }
+
+ return m_payloadNames;
+ }
+
+ internal set
+ {
+ m_payloadNames = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the event source object.
+ /// </summary>
+ public EventSource EventSource { get { return m_eventSource; } }
+
+ /// <summary>
+ /// Gets the keywords for the event.
+ /// </summary>
+ public EventKeywords Keywords
+ {
+ get
+ {
+ if (EventId < 0) // TraceLogging convention EventID == -1
+ return m_keywords;
+
+ return (EventKeywords)m_eventSource.m_eventData[EventId].Descriptor.Keywords;
+ }
+ }
+
+ /// <summary>
+ /// Gets the operation code for the event.
+ /// </summary>
+ public EventOpcode Opcode
+ {
+ get
+ {
+ if (EventId <= 0) // TraceLogging convention EventID == -1
+ return m_opcode;
+ return (EventOpcode)m_eventSource.m_eventData[EventId].Descriptor.Opcode;
+ }
+ }
+
+ /// <summary>
+ /// Gets the task for the event.
+ /// </summary>
+ public EventTask Task
+ {
+ get
+ {
+ if (EventId <= 0) // TraceLogging convention EventID == -1
+ return EventTask.None;
+
+ return (EventTask)m_eventSource.m_eventData[EventId].Descriptor.Task;
+ }
+ }
+
+ /// <summary>
+ /// Any provider/user defined options associated with the event.
+ /// </summary>
+ public EventTags Tags
+ {
+ get
+ {
+ if (EventId <= 0) // TraceLogging convention EventID == -1
+ return m_tags;
+ return m_eventSource.m_eventData[EventId].Tags;
+ }
+ }
+
+ /// <summary>
+ /// Gets the message for the event. If the message has {N} parameters they are NOT substituted.
+ /// </summary>
+ public string Message
+ {
+ get
+ {
+ if (EventId <= 0) // TraceLogging convention EventID == -1
+ return m_message;
+ else
+ return m_eventSource.m_eventData[EventId].Message;
+ }
+ internal set
+ {
+ m_message = value;
+ }
+ }
+
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ /// <summary>
+ /// Gets the channel for the event.
+ /// </summary>
+ public EventChannel Channel
+ {
+ get
+ {
+ if (EventId <= 0) // TraceLogging convention EventID == -1
+ return EventChannel.None;
+ return (EventChannel)m_eventSource.m_eventData[EventId].Descriptor.Channel;
+ }
+ }
+#endif
+
+ /// <summary>
+ /// Gets the version of the event.
+ /// </summary>
+ public byte Version
+ {
+ get
+ {
+ if (EventId <= 0) // TraceLogging convention EventID == -1
+ return 0;
+ return m_eventSource.m_eventData[EventId].Descriptor.Version;
+ }
+ }
+
+ /// <summary>
+ /// Gets the level for the event.
+ /// </summary>
+ public EventLevel Level
+ {
+ get
+ {
+ if (EventId <= 0) // TraceLogging convention EventID == -1
+ return m_level;
+ return (EventLevel)m_eventSource.m_eventData[EventId].Descriptor.Level;
+ }
+ }
+
+#region private
+ internal EventWrittenEventArgs(EventSource eventSource)
+ {
+ m_eventSource = eventSource;
+ }
+ private string m_message;
+ private string m_eventName;
+ private EventSource m_eventSource;
+ private ReadOnlyCollection<string> m_payloadNames;
+ private Guid m_activityId;
+ internal EventTags m_tags;
+ internal EventOpcode m_opcode;
+ internal EventLevel m_level;
+ internal EventKeywords m_keywords;
+#endregion
+ }
+
+ /// <summary>
+ /// Allows customizing defaults and specifying localization support for the event source class to which it is applied.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class EventSourceAttribute : Attribute
+ {
+ /// <summary>
+ /// Overrides the ETW name of the event source (which defaults to the class name)
+ /// </summary>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Overrides the default (calculated) Guid of an EventSource type. Explicitly defining a GUID is discouraged,
+ /// except when upgrading existing ETW providers to using event sources.
+ /// </summary>
+ public string Guid { get; set; }
+
+ /// <summary>
+ /// <para>
+ /// EventSources support localization of events. The names used for events, opcodes, tasks, keywords and maps
+ /// can be localized to several languages if desired. This works by creating a ResX style string table
+ /// (by simply adding a 'Resource File' to your project). This resource file is given a name e.g.
+ /// 'DefaultNameSpace.ResourceFileName' which can be passed to the ResourceManager constructor to read the
+ /// resources. This name is the value of the LocalizationResources property.
+ /// </para><para>
+ /// If LocalizationResources property is non-null, then EventSource will look up the localized strings for events by
+ /// using the following resource naming scheme
+ /// </para>
+ /// <para>* event_EVENTNAME</para>
+ /// <para>* task_TASKNAME</para>
+ /// <para>* keyword_KEYWORDNAME</para>
+ /// <para>* map_MAPNAME</para>
+ /// <para>
+ /// where the capitalized name is the name of the event, task, keyword, or map value that should be localized.
+ /// Note that the localized string for an event corresponds to the Message string, and can have {0} values
+ /// which represent the payload values.
+ /// </para>
+ /// </summary>
+ public string LocalizationResources { get; set; }
+ }
+
+ /// <summary>
+ /// Any instance methods in a class that subclasses <see cref="EventSource"/> and that return void are
+ /// assumed by default to be methods that generate an ETW event. Enough information can be deduced from the
+ /// name of the method and its signature to generate basic schema information for the event. The
+ /// <see cref="EventAttribute"/> class allows you to specify additional event schema information for an event if
+ /// desired.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class EventAttribute : Attribute
+ {
+ /// <summary>Construct an EventAttribute with specified eventId</summary>
+ /// <param name="eventId">ID of the ETW event (an integer between 1 and 65535)</param>
+ public EventAttribute(int eventId) { this.EventId = eventId; Level = EventLevel.Informational; this.m_opcodeSet = false; }
+ /// <summary>Event's ID</summary>
+ public int EventId { get; private set; }
+ /// <summary>Event's severity level: indicates the severity or verbosity of the event</summary>
+ public EventLevel Level { get; set; }
+ /// <summary>Event's keywords: allows classification of events by "categories"</summary>
+ public EventKeywords Keywords { get; set; }
+ /// <summary>Event's operation code: allows defining operations, generally used with Tasks</summary>
+ public EventOpcode Opcode
+ {
+ get
+ {
+ return m_opcode;
+ }
+ set
+ {
+ this.m_opcode = value;
+ this.m_opcodeSet = true;
+ }
+ }
+
+ internal bool IsOpcodeSet
+ {
+ get
+ {
+ return m_opcodeSet;
+ }
+ }
+
+ /// <summary>Event's task: allows logical grouping of events</summary>
+ public EventTask Task { get; set; }
+#if FEATURE_MANAGED_ETW_CHANNELS
+ /// <summary>Event's channel: defines an event log as an additional destination for the event</summary>
+ public EventChannel Channel { get; set; }
+#endif
+ /// <summary>Event's version</summary>
+ public byte Version { get; set; }
+
+ /// <summary>
+ /// This can be specified to enable formatting and localization of the event's payload. You can
+ /// use standard .NET substitution operators (eg {1}) in the string and they will be replaced
+ /// with the 'ToString()' of the corresponding part of the event payload.
+ /// </summary>
+ public string Message { get; set; }
+
+ /// <summary>
+ /// User defined options associated with the event. These do not have meaning to the EventSource but
+ /// are passed through to listeners which given them semantics.
+ /// </summary>
+ public EventTags Tags { get; set; }
+
+ /// <summary>
+ /// Allows fine control over the Activity IDs generated by start and stop events
+ /// </summary>
+ public EventActivityOptions ActivityOptions { get; set; }
+
+#region private
+ EventOpcode m_opcode;
+ private bool m_opcodeSet;
+#endregion
+ }
+
+ /// <summary>
+ /// By default all instance methods in a class that subclasses code:EventSource that and return
+ /// void are assumed to be methods that generate an event. This default can be overridden by specifying
+ /// the code:NonEventAttribute
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class NonEventAttribute : Attribute
+ {
+ /// <summary>
+ /// Constructs a default NonEventAttribute
+ /// </summary>
+ public NonEventAttribute() { }
+ }
+
+ // FUTURE we may want to expose this at some point once we have a partner that can help us validate the design.
+#if FEATURE_MANAGED_ETW_CHANNELS
+ /// <summary>
+ /// EventChannelAttribute allows customizing channels supported by an EventSource. This attribute must be
+ /// applied to an member of type EventChannel defined in a Channels class nested in the EventSource class:
+ /// <code>
+ /// public static class Channels
+ /// {
+ /// [Channel(Enabled = true, EventChannelType = EventChannelType.Admin)]
+ /// public const EventChannel Admin = (EventChannel)16;
+ ///
+ /// [Channel(Enabled = false, EventChannelType = EventChannelType.Operational)]
+ /// public const EventChannel Operational = (EventChannel)17;
+ /// }
+ /// </code>
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Field)]
+#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ public
+#endif
+ class EventChannelAttribute : Attribute
+ {
+ /// <summary>
+ /// Specified whether the channel is enabled by default
+ /// </summary>
+ public bool Enabled { get; set; }
+
+ /// <summary>
+ /// Legal values are in EventChannelType
+ /// </summary>
+ public EventChannelType EventChannelType { get; set; }
+
+#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ /// <summary>
+ /// Specifies the isolation for the channel
+ /// </summary>
+ public EventChannelIsolation Isolation { get; set; }
+
+ /// <summary>
+ /// Specifies an SDDL access descriptor that controls access to the log file that backs the channel.
+ /// See MSDN ((http://msdn.microsoft.com/en-us/library/windows/desktop/aa382741.aspx) for details.
+ /// </summary>
+ public string Access { get; set; }
+
+ /// <summary>
+ /// Allows importing channels defined in external manifests
+ /// </summary>
+ public string ImportChannel { get; set; }
+#endif
+
+ // TODO: there is a convention that the name is the Provider/Type Should we provide an override?
+ // public string Name { get; set; }
+ }
+
+ /// <summary>
+ /// Allowed channel types
+ /// </summary>
+#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ public
+#endif
+ enum EventChannelType
+ {
+ /// <summary>The admin channel</summary>
+ Admin = 1,
+ /// <summary>The operational channel</summary>
+ Operational,
+ /// <summary>The Analytic channel</summary>
+ Analytic,
+ /// <summary>The debug channel</summary>
+ Debug,
+ }
+
+#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ /// <summary>
+ /// Allowed isolation levels. See MSDN (http://msdn.microsoft.com/en-us/library/windows/desktop/aa382741.aspx)
+ /// for the default permissions associated with each level. EventChannelIsolation and Access allows control over the
+ /// access permissions for the channel and backing file.
+ /// </summary>
+ public
+ enum EventChannelIsolation
+ {
+ /// <summary>
+ /// This is the default isolation level. All channels that specify Application isolation use the same ETW session
+ /// </summary>
+ Application = 1,
+ /// <summary>
+ /// All channels that specify System isolation use the same ETW session
+ /// </summary>
+ System,
+ /// <summary>
+ /// Use sparingly! When specifying Custom isolation, a separate ETW session is created for the channel.
+ /// Using Custom isolation lets you control the access permissions for the channel and backing file.
+ /// Because there are only 64 ETW sessions available, you should limit your use of Custom isolation.
+ /// </summary>
+ Custom,
+ }
+#endif
+#endif
+
+ /// <summary>
+ /// Describes the pre-defined command (EventCommandEventArgs.Command property) that is passed to the OnEventCommand callback.
+ /// </summary>
+ public enum EventCommand
+ {
+ /// <summary>
+ /// Update EventSource state
+ /// </summary>
+ Update = 0,
+ /// <summary>
+ /// Request EventSource to generate and send its manifest
+ /// </summary>
+ SendManifest = -1,
+ /// <summary>
+ /// Enable event
+ /// </summary>
+ Enable = -2,
+ /// <summary>
+ /// Disable event
+ /// </summary>
+ Disable = -3
+ };
+
+
+#region private classes
+
+ // holds a bitfield representing a session mask
+ /// <summary>
+ /// A SessionMask represents a set of (at most MAX) sessions as a bit mask. The perEventSourceSessionId
+ /// is the index in the SessionMask of the bit that will be set. These can translate to
+ /// EventSource's reserved keywords bits using the provided ToEventKeywords() and
+ /// FromEventKeywords() methods.
+ /// </summary>
+ internal struct SessionMask
+ {
+ public SessionMask(SessionMask m)
+ { m_mask = m.m_mask; }
+
+ public SessionMask(uint mask = 0)
+ { m_mask = mask & MASK; }
+
+ public bool IsEqualOrSupersetOf(SessionMask m)
+ {
+ return (this.m_mask | m.m_mask) == this.m_mask;
+ }
+
+ public static SessionMask All
+ {
+ get { return new SessionMask(MASK); }
+ }
+
+ public static SessionMask FromId(int perEventSourceSessionId)
+ {
+ Debug.Assert(perEventSourceSessionId < MAX);
+ return new SessionMask((uint)1 << perEventSourceSessionId);
+ }
+
+ public ulong ToEventKeywords()
+ {
+ return (ulong)m_mask << SHIFT_SESSION_TO_KEYWORD;
+ }
+
+ public static SessionMask FromEventKeywords(ulong m)
+ {
+ return new SessionMask((uint)(m >> SHIFT_SESSION_TO_KEYWORD));
+ }
+
+ public bool this[int perEventSourceSessionId]
+ {
+ get
+ {
+ Debug.Assert(perEventSourceSessionId < MAX);
+ return (m_mask & (1 << perEventSourceSessionId)) != 0;
+ }
+ set
+ {
+ Debug.Assert(perEventSourceSessionId < MAX);
+ if (value) m_mask |= ((uint)1 << perEventSourceSessionId);
+ else m_mask &= ~((uint)1 << perEventSourceSessionId);
+ }
+ }
+
+ public static SessionMask operator |(SessionMask m1, SessionMask m2)
+ {
+ return new SessionMask(m1.m_mask | m2.m_mask);
+ }
+
+ public static SessionMask operator &(SessionMask m1, SessionMask m2)
+ {
+ return new SessionMask(m1.m_mask & m2.m_mask);
+ }
+
+ public static SessionMask operator ^(SessionMask m1, SessionMask m2)
+ {
+ return new SessionMask(m1.m_mask ^ m2.m_mask);
+ }
+
+ public static SessionMask operator ~(SessionMask m)
+ {
+ return new SessionMask(MASK & ~(m.m_mask));
+ }
+
+ public static explicit operator ulong(SessionMask m)
+ { return m.m_mask; }
+
+ public static explicit operator uint(SessionMask m)
+ { return m.m_mask; }
+
+ private uint m_mask;
+
+ internal const int SHIFT_SESSION_TO_KEYWORD = 44; // bits 44-47 inclusive are reserved
+ internal const uint MASK = 0x0fU; // the mask of 4 reserved bits
+ internal const uint MAX = 4; // maximum number of simultaneous ETW sessions supported
+ }
+
+ /// <summary>
+ /// code:EventDispatchers are a simple 'helper' structure that holds the filtering state
+ /// (m_EventEnabled) for a particular EventSource X EventListener tuple
+ ///
+ /// Thus a single EventListener may have many EventDispatchers (one for every EventSource
+ /// that EventListener has activate) and a Single EventSource may also have many
+ /// event Dispatchers (one for every EventListener that has activated it).
+ ///
+ /// Logically a particular EventDispatcher belongs to exactly one EventSource and exactly
+ /// one EventListener (although EventDispatcher does not 'remember' the EventSource it is
+ /// associated with.
+ /// </summary>
+ internal class EventDispatcher
+ {
+ internal EventDispatcher(EventDispatcher next, bool[] eventEnabled, EventListener listener)
+ {
+ m_Next = next;
+ m_EventEnabled = eventEnabled;
+ m_Listener = listener;
+ }
+
+ // Instance fields
+ readonly internal EventListener m_Listener; // The dispatcher this entry is for
+ internal bool[] m_EventEnabled; // For every event in a the eventSource, is it enabled?
+
+ // Only guaranteed to exist after a InsureInit()
+ internal EventDispatcher m_Next; // These form a linked list in code:EventSource.m_Dispatchers
+ // Of all listeners for that eventSource.
+ }
+
+ /// <summary>
+ /// Flags that can be used with EventSource.GenerateManifest to control how the ETW manifest for the EventSource is
+ /// generated.
+ /// </summary>
+ [Flags]
+ public enum EventManifestOptions
+ {
+ /// <summary>
+ /// Only the resources associated with current UI culture are included in the manifest
+ /// </summary>
+ None = 0x0,
+ /// <summary>
+ /// Throw exceptions for any inconsistency encountered
+ /// </summary>
+ Strict = 0x1,
+ /// <summary>
+ /// Generate a "resources" node under "localization" for every satellite assembly provided
+ /// </summary>
+ AllCultures = 0x2,
+ /// <summary>
+ /// Generate the manifest only if the event source needs to be registered on the machine,
+ /// otherwise return null (but still perform validation if Strict is specified)
+ /// </summary>
+ OnlyIfNeededForRegistration = 0x4,
+ /// <summary>
+ /// When generating the manifest do *not* enforce the rule that the current EventSource class
+ /// must be the base class for the user-defined type passed in. This allows validation of .net
+ /// event sources using the new validation code
+ /// </summary>
+ AllowEventSourceOverride = 0x8,
+ }
+
+ /// <summary>
+ /// ManifestBuilder is designed to isolate the details of the message of the event from the
+ /// rest of EventSource. This one happens to create XML.
+ /// </summary>
+ internal partial class ManifestBuilder
+ {
+ /// <summary>
+ /// Build a manifest for 'providerName' with the given GUID, which will be packaged into 'dllName'.
+ /// 'resources, is a resource manager. If specified all messages are localized using that manager.
+ /// </summary>
+ public ManifestBuilder(string providerName, Guid providerGuid, string dllName, ResourceManager resources,
+ EventManifestOptions flags)
+ {
+#if FEATURE_MANAGED_ETW_CHANNELS
+ this.providerName = providerName;
+#endif
+ this.flags = flags;
+
+ this.resources = resources;
+ sb = new StringBuilder();
+ events = new StringBuilder();
+ templates = new StringBuilder();
+ opcodeTab = new Dictionary<int, string>();
+ stringTab = new Dictionary<string, string>();
+ errors = new List<string>();
+ perEventByteArrayArgIndices = new Dictionary<string, List<int>>();
+
+ sb.AppendLine("<instrumentationManifest xmlns=\"http://schemas.microsoft.com/win/2004/08/events\">");
+ sb.AppendLine(" <instrumentation xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:win=\"http://manifests.microsoft.com/win/2004/08/windows/events\">");
+ sb.AppendLine(" <events xmlns=\"http://schemas.microsoft.com/win/2004/08/events\">");
+ sb.Append("<provider name=\"").Append(providerName).
+ Append("\" guid=\"{").Append(providerGuid.ToString()).Append("}");
+ if (dllName != null)
+ sb.Append("\" resourceFileName=\"").Append(dllName).Append("\" messageFileName=\"").Append(dllName);
+
+ var symbolsName = providerName.Replace("-", "").Replace(".", "_"); // Period and - are illegal replace them.
+ sb.Append("\" symbol=\"").Append(symbolsName);
+ sb.Append("\">").AppendLine();
+ }
+
+ public void AddOpcode(string name, int value)
+ {
+ if ((flags & EventManifestOptions.Strict) != 0)
+ {
+ if (value <= 10 || value >= 239)
+ {
+ ManifestError(SR.Format(SR.EventSource_IllegalOpcodeValue, name, value));
+ }
+ string prevName;
+ if (opcodeTab.TryGetValue(value, out prevName) && !name.Equals(prevName, StringComparison.Ordinal))
+ {
+ ManifestError(SR.Format(SR.EventSource_OpcodeCollision, name, prevName, value));
+ }
+ }
+ opcodeTab[value] = name;
+ }
+ public void AddTask(string name, int value)
+ {
+ if ((flags & EventManifestOptions.Strict) != 0)
+ {
+ if (value <= 0 || value >= 65535)
+ {
+ ManifestError(SR.Format(SR.EventSource_IllegalTaskValue, name, value));
+ }
+ string prevName;
+ if (taskTab != null && taskTab.TryGetValue(value, out prevName) && !name.Equals(prevName, StringComparison.Ordinal))
+ {
+ ManifestError(SR.Format(SR.EventSource_TaskCollision, name, prevName, value));
+ }
+ }
+ if (taskTab == null)
+ taskTab = new Dictionary<int, string>();
+ taskTab[value] = name;
+ }
+ public void AddKeyword(string name, ulong value)
+ {
+ if ((value & (value - 1)) != 0) // Is it a power of 2?
+ {
+ ManifestError(SR.Format(SR.EventSource_KeywordNeedPowerOfTwo, "0x" + value.ToString("x", CultureInfo.CurrentCulture), name), true);
+ }
+ if ((flags & EventManifestOptions.Strict) != 0)
+ {
+ if (value >= 0x0000100000000000UL && !name.StartsWith("Session", StringComparison.Ordinal))
+ {
+ ManifestError(SR.Format(SR.EventSource_IllegalKeywordsValue, name, "0x" + value.ToString("x", CultureInfo.CurrentCulture)));
+ }
+ string prevName;
+ if (keywordTab != null && keywordTab.TryGetValue(value, out prevName) && !name.Equals(prevName, StringComparison.Ordinal))
+ {
+ ManifestError(SR.Format(SR.EventSource_KeywordCollision, name, prevName, "0x" + value.ToString("x", CultureInfo.CurrentCulture)));
+ }
+ }
+ if (keywordTab == null)
+ keywordTab = new Dictionary<ulong, string>();
+ keywordTab[value] = name;
+ }
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ /// <summary>
+ /// Add a channel. channelAttribute can be null
+ /// </summary>
+ public void AddChannel(string name, int value, EventChannelAttribute channelAttribute)
+ {
+ EventChannel chValue = (EventChannel)value;
+ if (value < (int)EventChannel.Admin || value > 255)
+ ManifestError(SR.Format(SR.EventSource_EventChannelOutOfRange, name, value));
+ else if (chValue >= EventChannel.Admin && chValue <= EventChannel.Debug &&
+ channelAttribute != null && EventChannelToChannelType(chValue) != channelAttribute.EventChannelType)
+ {
+ // we want to ensure developers do not define EventChannels that conflict with the builtin ones,
+ // but we want to allow them to override the default ones...
+ ManifestError(SR.Format(SR.EventSource_ChannelTypeDoesNotMatchEventChannelValue,
+ name, ((EventChannel)value).ToString()));
+ }
+
+ // TODO: validate there are no conflicting manifest exposed names (generally following the format "provider/type")
+
+ ulong kwd = GetChannelKeyword(chValue);
+
+ if (channelTab == null)
+ channelTab = new Dictionary<int, ChannelInfo>(4);
+ channelTab[value] = new ChannelInfo { Name = name, Keywords = kwd, Attribs = channelAttribute };
+ }
+
+ private EventChannelType EventChannelToChannelType(EventChannel channel)
+ {
+#if !ES_BUILD_STANDALONE
+ Debug.Assert(channel >= EventChannel.Admin && channel <= EventChannel.Debug);
+#endif
+ return (EventChannelType)((int)channel - (int)EventChannel.Admin + (int)EventChannelType.Admin);
+ }
+ private EventChannelAttribute GetDefaultChannelAttribute(EventChannel channel)
+ {
+ EventChannelAttribute attrib = new EventChannelAttribute();
+ attrib.EventChannelType = EventChannelToChannelType(channel);
+ if (attrib.EventChannelType <= EventChannelType.Operational)
+ attrib.Enabled = true;
+ return attrib;
+ }
+
+ public ulong[] GetChannelData()
+ {
+ if (this.channelTab == null)
+ {
+ return new ulong[0];
+ }
+
+ // We create an array indexed by the channel id for fast look up.
+ // E.g. channelMask[Admin] will give you the bit mask for Admin channel.
+ int maxkey = -1;
+ foreach (var item in this.channelTab.Keys)
+ {
+ if (item > maxkey)
+ {
+ maxkey = item;
+ }
+ }
+
+ ulong[] channelMask = new ulong[maxkey + 1];
+ foreach (var item in this.channelTab)
+ {
+ channelMask[item.Key] = item.Value.Keywords;
+ }
+
+ return channelMask;
+ }
+
+#endif
+ public void StartEvent(string eventName, EventAttribute eventAttribute)
+ {
+ Debug.Assert(numParams == 0);
+ Debug.Assert(this.eventName == null);
+ this.eventName = eventName;
+ numParams = 0;
+ byteArrArgIndices = null;
+
+ events.Append(" <event").
+ Append(" value=\"").Append(eventAttribute.EventId).Append("\"").
+ Append(" version=\"").Append(eventAttribute.Version).Append("\"").
+ Append(" level=\"").Append(GetLevelName(eventAttribute.Level)).Append("\"").
+ Append(" symbol=\"").Append(eventName).Append("\"");
+
+ // at this point we add to the manifest's stringTab a message that is as-of-yet
+ // "untranslated to manifest convention", b/c we don't have the number or position
+ // of any byte[] args (which require string format index updates)
+ WriteMessageAttrib(events, "event", eventName, eventAttribute.Message);
+
+ if (eventAttribute.Keywords != 0)
+ events.Append(" keywords=\"").Append(GetKeywords((ulong)eventAttribute.Keywords, eventName)).Append("\"");
+ if (eventAttribute.Opcode != 0)
+ events.Append(" opcode=\"").Append(GetOpcodeName(eventAttribute.Opcode, eventName)).Append("\"");
+ if (eventAttribute.Task != 0)
+ events.Append(" task=\"").Append(GetTaskName(eventAttribute.Task, eventName)).Append("\"");
+#if FEATURE_MANAGED_ETW_CHANNELS
+ if (eventAttribute.Channel != 0)
+ {
+ events.Append(" channel=\"").Append(GetChannelName(eventAttribute.Channel, eventName, eventAttribute.Message)).Append("\"");
+ }
+#endif
+ }
+
+ public void AddEventParameter(Type type, string name)
+ {
+ if (numParams == 0)
+ templates.Append(" <template tid=\"").Append(eventName).Append("Args\">").AppendLine();
+ if (type == typeof(byte[]))
+ {
+ // mark this index as "extraneous" (it has no parallel in the managed signature)
+ // we use these values in TranslateToManifestConvention()
+ if (byteArrArgIndices == null)
+ byteArrArgIndices = new List<int>(4);
+ byteArrArgIndices.Add(numParams);
+
+ // add an extra field to the template representing the length of the binary blob
+ numParams++;
+ templates.Append(" <data name=\"").Append(name).Append("Size\" inType=\"win:UInt32\"/>").AppendLine();
+ }
+ numParams++;
+ templates.Append(" <data name=\"").Append(name).Append("\" inType=\"").Append(GetTypeName(type)).Append("\"");
+ // TODO: for 'byte*' types it assumes the user provided length is named using the same naming convention
+ // as for 'byte[]' args (blob_arg_name + "Size")
+ if ((type.IsArray || type.IsPointer) && type.GetElementType() == typeof(byte))
+ {
+ // add "length" attribute to the "blob" field in the template (referencing the field added above)
+ templates.Append(" length=\"").Append(name).Append("Size\"");
+ }
+ // ETW does not support 64-bit value maps, so we don't specify these as ETW maps
+ if (type.IsEnum() && Enum.GetUnderlyingType(type) != typeof(UInt64) && Enum.GetUnderlyingType(type) != typeof(Int64))
+ {
+ templates.Append(" map=\"").Append(type.Name).Append("\"");
+ if (mapsTab == null)
+ mapsTab = new Dictionary<string, Type>();
+ if (!mapsTab.ContainsKey(type.Name))
+ mapsTab.Add(type.Name, type); // Remember that we need to dump the type enumeration
+ }
+
+ templates.Append("/>").AppendLine();
+ }
+ public void EndEvent()
+ {
+ if (numParams > 0)
+ {
+ templates.Append(" </template>").AppendLine();
+ events.Append(" template=\"").Append(eventName).Append("Args\"");
+ }
+ events.Append("/>").AppendLine();
+
+ if (byteArrArgIndices != null)
+ perEventByteArrayArgIndices[eventName] = byteArrArgIndices;
+
+ // at this point we have all the information we need to translate the C# Message
+ // to the manifest string we'll put in the stringTab
+ string msg;
+ if (stringTab.TryGetValue("event_" + eventName, out msg))
+ {
+ msg = TranslateToManifestConvention(msg, eventName);
+ stringTab["event_" + eventName] = msg;
+ }
+
+ eventName = null;
+ numParams = 0;
+ byteArrArgIndices = null;
+ }
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ // Channel keywords are generated one per channel to allow channel based filtering in event viewer. These keywords are autogenerated
+ // by mc.exe for compiling a manifest and are based on the order of the channels (fields) in the Channels inner class (when advanced
+ // channel support is enabled), or based on the order the predefined channels appear in the EventAttribute properties (for simple
+ // support). The manifest generated *MUST* have the channels specified in the same order (that's how our computed keywords are mapped
+ // to channels by the OS infrastructure).
+ // If channelKeyworkds is present, and has keywords bits in the ValidPredefinedChannelKeywords then it is
+ // assumed that that the keyword for that channel should be that bit.
+ // otherwise we allocate a channel bit for the channel.
+ // explicit channel bits are only used by WCF to mimic an existing manifest,
+ // so we don't dont do error checking.
+ public ulong GetChannelKeyword(EventChannel channel, ulong channelKeyword = 0)
+ {
+ // strip off any non-channel keywords, since we are only interested in channels here.
+ channelKeyword &= ValidPredefinedChannelKeywords;
+ if (channelTab == null)
+ {
+ channelTab = new Dictionary<int, ChannelInfo>(4);
+ }
+
+ if (channelTab.Count == MaxCountChannels)
+ ManifestError(SR.EventSource_MaxChannelExceeded);
+
+ ChannelInfo info;
+ if (!channelTab.TryGetValue((int)channel, out info))
+ {
+ // If we were not given an explicit channel, allocate one.
+ if (channelKeyword != 0)
+ {
+ channelKeyword = nextChannelKeywordBit;
+ nextChannelKeywordBit >>= 1;
+ }
+ }
+ else
+ {
+ channelKeyword = info.Keywords;
+ }
+
+ return channelKeyword;
+ }
+#endif
+
+ public byte[] CreateManifest()
+ {
+ string str = CreateManifestString();
+ return Encoding.UTF8.GetBytes(str);
+ }
+
+ public IList<string> Errors { get { return errors; } }
+
+ /// <summary>
+ /// When validating an event source it adds the error to the error collection.
+ /// When not validating it throws an exception if runtimeCritical is "true".
+ /// Otherwise the error is ignored.
+ /// </summary>
+ /// <param name="msg"></param>
+ /// <param name="runtimeCritical"></param>
+ public void ManifestError(string msg, bool runtimeCritical = false)
+ {
+ if ((flags & EventManifestOptions.Strict) != 0)
+ errors.Add(msg);
+ else if (runtimeCritical)
+ throw new ArgumentException(msg);
+ }
+
+ private string CreateManifestString()
+ {
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ // Write out the channels
+ if (channelTab != null)
+ {
+ sb.Append(" <channels>").AppendLine();
+ var sortedChannels = new List<KeyValuePair<int, ChannelInfo>>();
+ foreach (KeyValuePair<int, ChannelInfo> p in channelTab) { sortedChannels.Add(p); }
+ sortedChannels.Sort((p1, p2) => -Comparer<ulong>.Default.Compare(p1.Value.Keywords, p2.Value.Keywords));
+ foreach (var kvpair in sortedChannels)
+ {
+ int channel = kvpair.Key;
+ ChannelInfo channelInfo = kvpair.Value;
+
+ string channelType = null;
+ string elementName = "channel";
+ bool enabled = false;
+ string fullName = null;
+#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ string isolation = null;
+ string access = null;
+#endif
+ if (channelInfo.Attribs != null)
+ {
+ var attribs = channelInfo.Attribs;
+ if (Enum.IsDefined(typeof(EventChannelType), attribs.EventChannelType))
+ channelType = attribs.EventChannelType.ToString();
+ enabled = attribs.Enabled;
+#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ if (attribs.ImportChannel != null)
+ {
+ fullName = attribs.ImportChannel;
+ elementName = "importChannel";
+ }
+ if (Enum.IsDefined(typeof(EventChannelIsolation), attribs.Isolation))
+ isolation = attribs.Isolation.ToString();
+ access = attribs.Access;
+#endif
+ }
+ if (fullName == null)
+ fullName = providerName + "/" + channelInfo.Name;
+
+ sb.Append(" <").Append(elementName);
+ sb.Append(" chid=\"").Append(channelInfo.Name).Append("\"");
+ sb.Append(" name=\"").Append(fullName).Append("\"");
+ if (elementName == "channel") // not applicable to importChannels.
+ {
+ WriteMessageAttrib(sb, "channel", channelInfo.Name, null);
+ sb.Append(" value=\"").Append(channel).Append("\"");
+ if (channelType != null)
+ sb.Append(" type=\"").Append(channelType).Append("\"");
+ sb.Append(" enabled=\"").Append(enabled.ToString().ToLower()).Append("\"");
+#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+ if (access != null)
+ sb.Append(" access=\"").Append(access).Append("\"");
+ if (isolation != null)
+ sb.Append(" isolation=\"").Append(isolation).Append("\"");
+#endif
+ }
+ sb.Append("/>").AppendLine();
+ }
+ sb.Append(" </channels>").AppendLine();
+ }
+#endif
+
+ // Write out the tasks
+ if (taskTab != null)
+ {
+
+ sb.Append(" <tasks>").AppendLine();
+ var sortedTasks = new List<int>(taskTab.Keys);
+ sortedTasks.Sort();
+ foreach (int task in sortedTasks)
+ {
+ sb.Append(" <task");
+ WriteNameAndMessageAttribs(sb, "task", taskTab[task]);
+ sb.Append(" value=\"").Append(task).Append("\"/>").AppendLine();
+ }
+ sb.Append(" </tasks>").AppendLine();
+ }
+
+ // Write out the maps
+ if (mapsTab != null)
+ {
+ sb.Append(" <maps>").AppendLine();
+ foreach (Type enumType in mapsTab.Values)
+ {
+ bool isbitmap = EventSource.GetCustomAttributeHelper(enumType, typeof(FlagsAttribute), flags) != null;
+ string mapKind = isbitmap ? "bitMap" : "valueMap";
+ sb.Append(" <").Append(mapKind).Append(" name=\"").Append(enumType.Name).Append("\">").AppendLine();
+
+ // write out each enum value
+ FieldInfo[] staticFields = enumType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static);
+ foreach (FieldInfo staticField in staticFields)
+ {
+ object constantValObj = staticField.GetRawConstantValue();
+ if (constantValObj != null)
+ {
+ long hexValue;
+ if (constantValObj is int)
+ hexValue = ((int)constantValObj);
+ else if (constantValObj is long)
+ hexValue = ((long)constantValObj);
+ else
+ continue;
+
+ // ETW requires all bitmap values to be powers of 2. Skip the ones that are not.
+ // TODO: Warn people about the dropping of values.
+ if (isbitmap && ((hexValue & (hexValue - 1)) != 0 || hexValue == 0))
+ continue;
+
+ sb.Append(" <map value=\"0x").Append(hexValue.ToString("x", CultureInfo.InvariantCulture)).Append("\"");
+ WriteMessageAttrib(sb, "map", enumType.Name + "." + staticField.Name, staticField.Name);
+ sb.Append("/>").AppendLine();
+ }
+ }
+ sb.Append(" </").Append(mapKind).Append(">").AppendLine();
+ }
+ sb.Append(" </maps>").AppendLine();
+ }
+
+ // Write out the opcodes
+ sb.Append(" <opcodes>").AppendLine();
+ var sortedOpcodes = new List<int>(opcodeTab.Keys);
+ sortedOpcodes.Sort();
+ foreach (int opcode in sortedOpcodes)
+ {
+ sb.Append(" <opcode");
+ WriteNameAndMessageAttribs(sb, "opcode", opcodeTab[opcode]);
+ sb.Append(" value=\"").Append(opcode).Append("\"/>").AppendLine();
+ }
+ sb.Append(" </opcodes>").AppendLine();
+
+ // Write out the keywords
+ if (keywordTab != null)
+ {
+ sb.Append(" <keywords>").AppendLine();
+ var sortedKeywords = new List<ulong>(keywordTab.Keys);
+ sortedKeywords.Sort();
+ foreach (ulong keyword in sortedKeywords)
+ {
+ sb.Append(" <keyword");
+ WriteNameAndMessageAttribs(sb, "keyword", keywordTab[keyword]);
+ sb.Append(" mask=\"0x").Append(keyword.ToString("x", CultureInfo.InvariantCulture)).Append("\"/>").AppendLine();
+ }
+ sb.Append(" </keywords>").AppendLine();
+ }
+
+ sb.Append(" <events>").AppendLine();
+ sb.Append(events);
+ sb.Append(" </events>").AppendLine();
+
+ sb.Append(" <templates>").AppendLine();
+ if (templates.Length > 0)
+ {
+ sb.Append(templates);
+ }
+ else
+ {
+ // Work around a cornercase ETW issue where a manifest with no templates causes
+ // ETW events to not get sent to their associated channel.
+ sb.Append(" <template tid=\"_empty\"></template>").AppendLine();
+ }
+ sb.Append(" </templates>").AppendLine();
+
+ sb.Append("</provider>").AppendLine();
+ sb.Append("</events>").AppendLine();
+ sb.Append("</instrumentation>").AppendLine();
+
+ // Output the localization information.
+ sb.Append("<localization>").AppendLine();
+
+ List<CultureInfo> cultures = null;
+ if (resources != null && (flags & EventManifestOptions.AllCultures) != 0)
+ {
+ cultures = GetSupportedCultures(resources);
+ }
+ else
+ {
+ cultures = new List<CultureInfo>();
+ cultures.Add(CultureInfo.CurrentUICulture);
+ }
+#if ES_BUILD_STANDALONE || ES_BUILD_PN
+ var sortedStrings = new List<string>(stringTab.Keys);
+ sortedStrings.Sort();
+#else
+ // DD 947936
+ var sortedStrings = new string[stringTab.Keys.Count];
+ stringTab.Keys.CopyTo(sortedStrings, 0);
+ // Avoid using public Array.Sort as that attempts to access BinaryCompatibility. Unfortunately FrameworkEventSource gets called
+ // very early in the app domain creation, when _FusionStore is not set up yet, resulting in a failure to run the static constructory
+ // for BinaryCompatibility. This failure is then cached and a TypeInitializationException is thrown every time some code attampts to
+ // access BinaryCompatibility.
+ ArraySortHelper<string>.IntrospectiveSort(sortedStrings, 0, sortedStrings.Length, string.Compare);
+#endif
+ foreach (var ci in cultures)
+ {
+ sb.Append(" <resources culture=\"").Append(ci.Name).Append("\">").AppendLine();
+ sb.Append(" <stringTable>").AppendLine();
+
+ foreach (var stringKey in sortedStrings)
+ {
+ string val = GetLocalizedMessage(stringKey, ci, etwFormat: true);
+ sb.Append(" <string id=\"").Append(stringKey).Append("\" value=\"").Append(val).Append("\"/>").AppendLine();
+ }
+ sb.Append(" </stringTable>").AppendLine();
+ sb.Append(" </resources>").AppendLine();
+ }
+ sb.Append("</localization>").AppendLine();
+ sb.AppendLine("</instrumentationManifest>");
+ return sb.ToString();
+ }
+
+#region private
+ private void WriteNameAndMessageAttribs(StringBuilder stringBuilder, string elementName, string name)
+ {
+ stringBuilder.Append(" name=\"").Append(name).Append("\"");
+ WriteMessageAttrib(sb, elementName, name, name);
+ }
+ private void WriteMessageAttrib(StringBuilder stringBuilder, string elementName, string name, string value)
+ {
+ string key = elementName + "_" + name;
+ // See if the user wants things localized.
+ if (resources != null)
+ {
+ // resource fallback: strings in the neutral culture will take precedence over inline strings
+ string localizedString = resources.GetString(key, CultureInfo.InvariantCulture);
+ if (localizedString != null)
+ value = localizedString;
+ }
+ if (value == null)
+ return;
+
+ stringBuilder.Append(" message=\"$(string.").Append(key).Append(")\"");
+ string prevValue;
+ if (stringTab.TryGetValue(key, out prevValue) && !prevValue.Equals(value))
+ {
+ ManifestError(SR.Format(SR.EventSource_DuplicateStringKey, key), true);
+ return;
+ }
+
+ stringTab[key] = value;
+ }
+ internal string GetLocalizedMessage(string key, CultureInfo ci, bool etwFormat)
+ {
+ string value = null;
+ if (resources != null)
+ {
+ string localizedString = resources.GetString(key, ci);
+ if (localizedString != null)
+ {
+ value = localizedString;
+ if (etwFormat && key.StartsWith("event_", StringComparison.Ordinal))
+ {
+ var evtName = key.Substring("event_".Length);
+ value = TranslateToManifestConvention(value, evtName);
+ }
+ }
+ }
+ if (etwFormat && value == null)
+ stringTab.TryGetValue(key, out value);
+
+ return value;
+ }
+
+ /// <summary>
+ /// There's no API to enumerate all languages an assembly is localized into, so instead
+ /// we enumerate through all the "known" cultures and attempt to load a corresponding satellite
+ /// assembly
+ /// </summary>
+ /// <param name="resources"></param>
+ /// <returns></returns>
+ private static List<CultureInfo> GetSupportedCultures(ResourceManager resources)
+ {
+ var cultures = new List<CultureInfo>();
+
+ if (!cultures.Contains(CultureInfo.CurrentUICulture))
+ cultures.Insert(0, CultureInfo.CurrentUICulture);
+ return cultures;
+ }
+
+ private static string GetLevelName(EventLevel level)
+ {
+ return (((int)level >= 16) ? "" : "win:") + level.ToString();
+ }
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ private string GetChannelName(EventChannel channel, string eventName, string eventMessage)
+ {
+ ChannelInfo info = null;
+ if (channelTab == null || !channelTab.TryGetValue((int)channel, out info))
+ {
+ if (channel < EventChannel.Admin) // || channel > EventChannel.Debug)
+ ManifestError(SR.Format(SR.EventSource_UndefinedChannel, channel, eventName));
+
+ // allow channels to be auto-defined. The well known ones get their well known names, and the
+ // rest get names Channel<N>. This allows users to modify the Manifest if they want more advanced features.
+ if (channelTab == null)
+ channelTab = new Dictionary<int, ChannelInfo>(4);
+
+ string channelName = channel.ToString(); // For well know channels this is a nice name, otherwise a number
+ if (EventChannel.Debug < channel)
+ channelName = "Channel" + channelName; // Add a 'Channel' prefix for numbers.
+
+ AddChannel(channelName, (int)channel, GetDefaultChannelAttribute(channel));
+ if (!channelTab.TryGetValue((int)channel, out info))
+ ManifestError(SR.Format(SR.EventSource_UndefinedChannel, channel, eventName));
+ }
+ // events that specify admin channels *must* have non-null "Message" attributes
+ if (resources != null && eventMessage == null)
+ eventMessage = resources.GetString("event_" + eventName, CultureInfo.InvariantCulture);
+ if (info.Attribs.EventChannelType == EventChannelType.Admin && eventMessage == null)
+ ManifestError(SR.Format(SR.EventSource_EventWithAdminChannelMustHaveMessage, eventName, info.Name));
+ return info.Name;
+ }
+#endif
+ private string GetTaskName(EventTask task, string eventName)
+ {
+ if (task == EventTask.None)
+ return "";
+
+ string ret;
+ if (taskTab == null)
+ taskTab = new Dictionary<int, string>();
+ if (!taskTab.TryGetValue((int)task, out ret))
+ ret = taskTab[(int)task] = eventName;
+ return ret;
+ }
+
+ private string GetOpcodeName(EventOpcode opcode, string eventName)
+ {
+ switch (opcode)
+ {
+ case EventOpcode.Info:
+ return "win:Info";
+ case EventOpcode.Start:
+ return "win:Start";
+ case EventOpcode.Stop:
+ return "win:Stop";
+ case EventOpcode.DataCollectionStart:
+ return "win:DC_Start";
+ case EventOpcode.DataCollectionStop:
+ return "win:DC_Stop";
+ case EventOpcode.Extension:
+ return "win:Extension";
+ case EventOpcode.Reply:
+ return "win:Reply";
+ case EventOpcode.Resume:
+ return "win:Resume";
+ case EventOpcode.Suspend:
+ return "win:Suspend";
+ case EventOpcode.Send:
+ return "win:Send";
+ case EventOpcode.Receive:
+ return "win:Receive";
+ }
+
+ string ret;
+ if (opcodeTab == null || !opcodeTab.TryGetValue((int)opcode, out ret))
+ {
+ ManifestError(SR.Format(SR.EventSource_UndefinedOpcode, opcode, eventName), true);
+ ret = null;
+ }
+ return ret;
+ }
+
+ private string GetKeywords(ulong keywords, string eventName)
+ {
+#if FEATURE_MANAGED_ETW_CHANNELS
+ // ignore keywords associate with channels
+ // See ValidPredefinedChannelKeywords def for more.
+ keywords &= ~ValidPredefinedChannelKeywords;
+#endif
+
+ string ret = "";
+ for (ulong bit = 1; bit != 0; bit <<= 1)
+ {
+ if ((keywords & bit) != 0)
+ {
+ string keyword = null;
+ if ((keywordTab == null || !keywordTab.TryGetValue(bit, out keyword)) &&
+ (bit >= (ulong)0x1000000000000))
+ {
+ // do not report Windows reserved keywords in the manifest (this allows the code
+ // to be resilient to potential renaming of these keywords)
+ keyword = string.Empty;
+ }
+ if (keyword == null)
+ {
+ ManifestError(SR.Format(SR.EventSource_UndefinedKeyword, "0x" + bit.ToString("x", CultureInfo.CurrentCulture), eventName), true);
+ keyword = string.Empty;
+ }
+ if (ret.Length != 0 && keyword.Length != 0)
+ ret = ret + " ";
+ ret = ret + keyword;
+ }
+ }
+ return ret;
+ }
+
+ private string GetTypeName(Type type)
+ {
+ if (type.IsEnum())
+ {
+ FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
+ var typeName = GetTypeName(fields[0].FieldType);
+ return typeName.Replace("win:Int", "win:UInt"); // ETW requires enums to be unsigned.
+ }
+
+ return GetTypeNameHelper(type);
+ }
+
+ private static void UpdateStringBuilder(ref StringBuilder stringBuilder, string eventMessage, int startIndex, int count)
+ {
+ if (stringBuilder == null)
+ stringBuilder = new StringBuilder();
+ stringBuilder.Append(eventMessage, startIndex, count);
+ }
+
+ private static readonly string[] s_escapes = { "&amp;", "&lt;", "&gt;", "&apos;", "&quot;", "%r", "%n", "%t" };
+ // Manifest messages use %N conventions for their message substitutions. Translate from
+ // .NET conventions. We can't use RegEx for this (we are in mscorlib), so we do it 'by hand'
+ private string TranslateToManifestConvention(string eventMessage, string evtName)
+ {
+ StringBuilder stringBuilder = null; // We lazily create this
+ int writtenSoFar = 0;
+ int chIdx = -1;
+ for (int i = 0; ;)
+ {
+ if (i >= eventMessage.Length)
+ {
+ if (stringBuilder == null)
+ return eventMessage;
+ UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar);
+ return stringBuilder.ToString();
+ }
+
+ if (eventMessage[i] == '%')
+ {
+ // handle format message escaping character '%' by escaping it
+ UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar);
+ stringBuilder.Append("%%");
+ i++;
+ writtenSoFar = i;
+ }
+ else if (i < eventMessage.Length - 1 &&
+ (eventMessage[i] == '{' && eventMessage[i + 1] == '{' || eventMessage[i] == '}' && eventMessage[i + 1] == '}'))
+ {
+ // handle C# escaped '{" and '}'
+ UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar);
+ stringBuilder.Append(eventMessage[i]);
+ i++; i++;
+ writtenSoFar = i;
+ }
+ else if (eventMessage[i] == '{')
+ {
+ int leftBracket = i;
+ i++;
+ int argNum = 0;
+ while (i < eventMessage.Length && Char.IsDigit(eventMessage[i]))
+ {
+ argNum = argNum * 10 + eventMessage[i] - '0';
+ i++;
+ }
+ if (i < eventMessage.Length && eventMessage[i] == '}')
+ {
+ i++;
+ UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, leftBracket - writtenSoFar);
+ int manIndex = TranslateIndexToManifestConvention(argNum, evtName);
+ stringBuilder.Append('%').Append(manIndex);
+ // An '!' after the insert specifier {n} will be interpreted as a literal.
+ // We'll escape it so that mc.exe does not attempt to consider it the
+ // beginning of a format string.
+ if (i < eventMessage.Length && eventMessage[i] == '!')
+ {
+ i++;
+ stringBuilder.Append("%!");
+ }
+ writtenSoFar = i;
+ }
+ else
+ {
+ ManifestError(SR.Format(SR.EventSource_UnsupportedMessageProperty, evtName, eventMessage));
+ }
+ }
+ else if ((chIdx = "&<>'\"\r\n\t".IndexOf(eventMessage[i])) >= 0)
+ {
+ UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar);
+ i++;
+ stringBuilder.Append(s_escapes[chIdx]);
+ writtenSoFar = i;
+ }
+ else
+ i++;
+ }
+ }
+
+ private int TranslateIndexToManifestConvention(int idx, string evtName)
+ {
+ List<int> byteArrArgIndices;
+ if (perEventByteArrayArgIndices.TryGetValue(evtName, out byteArrArgIndices))
+ {
+ foreach (var byArrIdx in byteArrArgIndices)
+ {
+ if (idx >= byArrIdx)
+ ++idx;
+ else
+ break;
+ }
+ }
+ return idx + 1;
+ }
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ class ChannelInfo
+ {
+ public string Name;
+ public ulong Keywords;
+ public EventChannelAttribute Attribs;
+ }
+#endif
+
+ Dictionary<int, string> opcodeTab;
+ Dictionary<int, string> taskTab;
+#if FEATURE_MANAGED_ETW_CHANNELS
+ Dictionary<int, ChannelInfo> channelTab;
+#endif
+ Dictionary<ulong, string> keywordTab;
+ Dictionary<string, Type> mapsTab;
+
+ Dictionary<string, string> stringTab; // Maps unlocalized strings to localized ones
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ // WCF used EventSource to mimic a existing ETW manifest. To support this
+ // in just their case, we allowed them to specify the keywords associated
+ // with their channels explicitly. ValidPredefinedChannelKeywords is
+ // this set of channel keywords that we allow to be explicitly set. You
+ // can ignore these bits otherwise.
+ internal const ulong ValidPredefinedChannelKeywords = 0xF000000000000000;
+ ulong nextChannelKeywordBit = 0x8000000000000000; // available Keyword bit to be used for next channel definition, grows down
+ const int MaxCountChannels = 8; // a manifest can defined at most 8 ETW channels
+#endif
+
+ StringBuilder sb; // Holds the provider information.
+ StringBuilder events; // Holds the events.
+ StringBuilder templates;
+
+#if FEATURE_MANAGED_ETW_CHANNELS
+ string providerName;
+#endif
+ ResourceManager resources; // Look up localized strings here.
+ EventManifestOptions flags;
+ IList<string> errors; // list of currently encountered errors
+ Dictionary<string, List<int>> perEventByteArrayArgIndices; // "event_name" -> List_of_Indices_of_Byte[]_Arg
+
+ // State we track between StartEvent and EndEvent.
+ string eventName; // Name of the event currently being processed.
+ int numParams; // keeps track of the number of args the event has.
+ List<int> byteArrArgIndices; // keeps track of the index of each byte[] argument
+#endregion
+ }
+
+ /// <summary>
+ /// Used to send the m_rawManifest into the event dispatcher as a series of events.
+ /// </summary>
+ internal struct ManifestEnvelope
+ {
+ public const int MaxChunkSize = 0xFF00;
+ public enum ManifestFormats : byte
+ {
+ SimpleXmlFormat = 1, // simply dump the XML manifest as UTF8
+ }
+
+#if FEATURE_MANAGED_ETW
+ public ManifestFormats Format;
+ public byte MajorVersion;
+ public byte MinorVersion;
+ public byte Magic;
+ public ushort TotalChunks;
+ public ushort ChunkNumber;
+#endif
+ };
+
+#endregion
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSourceException.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSourceException.cs
new file mode 100644
index 0000000000..be1bf3940a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSourceException.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Resources;
+using System.Runtime.Serialization;
+
+#if ES_BUILD_STANDALONE
+using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Exception that is thrown when an error occurs during EventSource operation.
+ /// </summary>
+#if !ES_BUILD_PCL
+ [Serializable]
+#endif
+ public class EventSourceException : Exception
+ {
+ /// <summary>
+ /// Initializes a new instance of the EventSourceException class.
+ /// </summary>
+ public EventSourceException() :
+ base(SR.EventSource_ListenerWriteFailure) { }
+
+ /// <summary>
+ /// Initializes a new instance of the EventSourceException class with a specified error message.
+ /// </summary>
+ public EventSourceException(string message) : base(message) { }
+
+ /// <summary>
+ /// Initializes a new instance of the EventSourceException class with a specified error message
+ /// and a reference to the inner exception that is the cause of this exception.
+ /// </summary>
+ public EventSourceException(string message, Exception innerException) : base(message, innerException) { }
+
+#if !ES_BUILD_PCL
+ /// <summary>
+ /// Initializes a new instance of the EventSourceException class with serialized data.
+ /// </summary>
+ protected EventSourceException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+#endif
+
+ internal EventSourceException(Exception innerException) :
+ base(SR.EventSource_ListenerWriteFailure, innerException) { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/IEventProvider.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/IEventProvider.cs
new file mode 100644
index 0000000000..d34f80eb51
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/IEventProvider.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.Win32;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ // Represents the interface between EventProvider and an external logging mechanism.
+ internal interface IEventProvider
+ {
+ // Register an event provider.
+ unsafe uint EventRegister(
+ EventSource eventSource,
+ UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback,
+ void* callbackContext,
+ ref long registrationHandle);
+
+ // Unregister an event provider.
+ uint EventUnregister(long registrationHandle);
+
+ // Write an event.
+ unsafe int EventWriteTransferWrapper(
+ long registrationHandle,
+ ref EventDescriptor eventDescriptor,
+ IntPtr eventHandle,
+ Guid* activityId,
+ Guid* relatedActivityId,
+ int userDataCount,
+ EventProvider.EventData* userData);
+
+ // Get or set the per-thread activity ID.
+ int EventActivityIdControl(UnsafeNativeMethods.ManifestEtw.ActivityControl ControlCode, ref Guid ActivityId);
+
+ // Define an EventPipeEvent handle.
+ unsafe IntPtr DefineEventHandle(uint eventID, string eventName, Int64 keywords, uint eventVersion, uint level, byte *pMetadata, uint metadataLength);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/StubEnvironment.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/StubEnvironment.cs
new file mode 100644
index 0000000000..1b3ca8004c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/StubEnvironment.cs
@@ -0,0 +1,373 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing.Internal
+#else
+namespace System.Diagnostics.Tracing.Internal
+#endif
+{
+#if ES_BUILD_AGAINST_DOTNET_V35
+ using Microsoft.Internal;
+#endif
+ using Microsoft.Reflection;
+ using System.Reflection;
+
+ internal static class Environment
+ {
+ public static readonly string NewLine = System.Environment.NewLine;
+
+ public static int TickCount
+ { get { return System.Environment.TickCount; } }
+
+ public static string GetResourceString(string key, params object[] args)
+ {
+ string fmt = rm.GetString(key);
+ if (fmt != null)
+ return string.Format(fmt, args);
+
+ string sargs = String.Empty;
+ foreach(var arg in args)
+ {
+ if (sargs != String.Empty)
+ sargs += ", ";
+ sargs += arg.ToString();
+ }
+
+ return key + " (" + sargs + ")";
+ }
+
+ public static string GetRuntimeResourceString(string key, params object[] args)
+ {
+ return GetResourceString(key, args);
+ }
+
+ private static System.Resources.ResourceManager rm = new System.Resources.ResourceManager("Microsoft.Diagnostics.Tracing.Messages", typeof(Environment).Assembly());
+ }
+}
+
+#if ES_BUILD_AGAINST_DOTNET_V35
+
+namespace Microsoft.Internal
+{
+ using System.Text;
+
+ internal static class Tuple
+ {
+ public static Tuple<T1> Create<T1>(T1 item1)
+ {
+ return new Tuple<T1>(item1);
+ }
+
+ public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2)
+ {
+ return new Tuple<T1, T2>(item1, item2);
+ }
+ }
+
+ [Serializable]
+ internal class Tuple<T1>
+ {
+ private readonly T1 m_Item1;
+
+ public T1 Item1 { get { return m_Item1; } }
+
+ public Tuple(T1 item1)
+ {
+ m_Item1 = item1;
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("(");
+ sb.Append(m_Item1);
+ sb.Append(")");
+ return sb.ToString();
+ }
+
+ int Size
+ {
+ get
+ {
+ return 1;
+ }
+ }
+ }
+
+ [Serializable]
+ public class Tuple<T1, T2>
+ {
+ private readonly T1 m_Item1;
+ private readonly T2 m_Item2;
+
+ public T1 Item1 { get { return m_Item1; } }
+ public T2 Item2 { get { return m_Item2; } }
+
+ public Tuple(T1 item1, T2 item2)
+ {
+ m_Item1 = item1;
+ m_Item2 = item2;
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("(");
+ sb.Append(m_Item1);
+ sb.Append(", ");
+ sb.Append(m_Item2);
+ sb.Append(")");
+ return sb.ToString();
+ }
+
+ int Size
+ {
+ get
+ {
+ return 2;
+ }
+ }
+ }
+}
+
+#endif
+
+namespace Microsoft.Reflection
+{
+ using System.Reflection;
+
+#if ES_BUILD_PCL
+ [Flags]
+ public enum BindingFlags
+ {
+ DeclaredOnly = 0x02, // Only look at the members declared on the Type
+ Instance = 0x04, // Include Instance members in search
+ Static = 0x08, // Include Static members in search
+ Public = 0x10, // Include Public members in search
+ NonPublic = 0x20, // Include Non-Public members in search
+ }
+
+ public enum TypeCode {
+ Empty = 0, // Null reference
+ Object = 1, // Instance that isn't a value
+ DBNull = 2, // Database null value
+ Boolean = 3, // Boolean
+ Char = 4, // Unicode character
+ SByte = 5, // Signed 8-bit integer
+ Byte = 6, // Unsigned 8-bit integer
+ Int16 = 7, // Signed 16-bit integer
+ UInt16 = 8, // Unsigned 16-bit integer
+ Int32 = 9, // Signed 32-bit integer
+ UInt32 = 10, // Unsigned 32-bit integer
+ Int64 = 11, // Signed 64-bit integer
+ UInt64 = 12, // Unsigned 64-bit integer
+ Single = 13, // IEEE 32-bit float
+ Double = 14, // IEEE 64-bit double
+ Decimal = 15, // Decimal
+ DateTime = 16, // DateTime
+ String = 18, // Unicode character string
+ }
+#endif
+ static class ReflectionExtensions
+ {
+#if (!ES_BUILD_PCL && !ES_BUILD_PN)
+
+ //
+ // Type extension methods
+ //
+ public static bool IsEnum(this Type type) { return type.IsEnum; }
+ public static bool IsAbstract(this Type type) { return type.IsAbstract; }
+ public static bool IsSealed(this Type type) { return type.IsSealed; }
+ public static bool IsValueType(this Type type) { return type.IsValueType; }
+ public static bool IsGenericType(this Type type) { return type.IsGenericType; }
+ public static Type BaseType(this Type type) { return type.BaseType; }
+ public static Assembly Assembly(this Type type) { return type.Assembly; }
+ public static TypeCode GetTypeCode(this Type type) { return Type.GetTypeCode(type); }
+
+ public static bool ReflectionOnly(this Assembly assm) { return assm.ReflectionOnly; }
+
+#else // ES_BUILD_PCL
+
+ //
+ // Type extension methods
+ //
+ public static bool IsEnum(this Type type) { return type.GetTypeInfo().IsEnum; }
+ public static bool IsAbstract(this Type type) { return type.GetTypeInfo().IsAbstract; }
+ public static bool IsSealed(this Type type) { return type.GetTypeInfo().IsSealed; }
+ public static bool IsValueType(this Type type) { return type.GetTypeInfo().IsValueType; }
+ public static bool IsGenericType(this Type type) { return type.IsConstructedGenericType; }
+ public static Type BaseType(this Type type) { return type.GetTypeInfo().BaseType; }
+ public static Assembly Assembly(this Type type) { return type.GetTypeInfo().Assembly; }
+ public static IEnumerable<PropertyInfo> GetProperties(this Type type)
+ {
+#if ES_BUILD_PN
+ return type.GetProperties();
+#else
+ return type.GetRuntimeProperties();
+#endif
+ }
+ public static MethodInfo GetGetMethod(this PropertyInfo propInfo) { return propInfo.GetMethod; }
+ public static Type[] GetGenericArguments(this Type type) { return type.GenericTypeArguments; }
+
+ public static MethodInfo[] GetMethods(this Type type, BindingFlags flags)
+ {
+ // Minimal implementation to cover only the cases we need
+ System.Diagnostics.Debug.Assert((flags & BindingFlags.DeclaredOnly) != 0);
+ System.Diagnostics.Debug.Assert((flags & ~(BindingFlags.DeclaredOnly|BindingFlags.Instance|BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic)) == 0);
+ Func<MethodInfo, bool> visFilter;
+ Func<MethodInfo, bool> instFilter;
+ switch (flags & (BindingFlags.Public | BindingFlags.NonPublic))
+ {
+ case 0: visFilter = mi => false; break;
+ case BindingFlags.Public: visFilter = mi => mi.IsPublic; break;
+ case BindingFlags.NonPublic: visFilter = mi => !mi.IsPublic; break;
+ default: visFilter = mi => true; break;
+ }
+ switch (flags & (BindingFlags.Instance | BindingFlags.Static))
+ {
+ case 0: instFilter = mi => false; break;
+ case BindingFlags.Instance: instFilter = mi => !mi.IsStatic; break;
+ case BindingFlags.Static: instFilter = mi => mi.IsStatic; break;
+ default: instFilter = mi => true; break;
+ }
+ List<MethodInfo> methodInfos = new List<MethodInfo>();
+ foreach (var declaredMethod in type.GetTypeInfo().DeclaredMethods)
+ {
+ if (visFilter(declaredMethod) && instFilter(declaredMethod))
+ methodInfos.Add(declaredMethod);
+ }
+ return methodInfos.ToArray();
+ }
+ public static FieldInfo[] GetFields(this Type type, BindingFlags flags)
+ {
+ // Minimal implementation to cover only the cases we need
+ System.Diagnostics.Debug.Assert((flags & BindingFlags.DeclaredOnly) != 0);
+ System.Diagnostics.Debug.Assert((flags & ~(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) == 0);
+ Func<FieldInfo, bool> visFilter;
+ Func<FieldInfo, bool> instFilter;
+ switch (flags & (BindingFlags.Public | BindingFlags.NonPublic))
+ {
+ case 0: visFilter = fi => false; break;
+ case BindingFlags.Public: visFilter = fi => fi.IsPublic; break;
+ case BindingFlags.NonPublic: visFilter = fi => !fi.IsPublic; break;
+ default: visFilter = fi => true; break;
+ }
+ switch (flags & (BindingFlags.Instance | BindingFlags.Static))
+ {
+ case 0: instFilter = fi => false; break;
+ case BindingFlags.Instance: instFilter = fi => !fi.IsStatic; break;
+ case BindingFlags.Static: instFilter = fi => fi.IsStatic; break;
+ default: instFilter = fi => true; break;
+ }
+ List<FieldInfo> fieldInfos = new List<FieldInfo>();
+ foreach (var declaredField in type.GetTypeInfo().DeclaredFields)
+ {
+ if (visFilter(declaredField) && instFilter(declaredField))
+ fieldInfos.Add(declaredField);
+ }
+ return fieldInfos.ToArray();
+ }
+ public static Type GetNestedType(this Type type, string nestedTypeName)
+ {
+ TypeInfo ti = null;
+ foreach(var nt in type.GetTypeInfo().DeclaredNestedTypes)
+ {
+ if (nt.Name == nestedTypeName)
+ {
+ ti = nt;
+ break;
+ }
+ }
+ return ti == null ? null : ti.AsType();
+ }
+ public static TypeCode GetTypeCode(this Type type)
+ {
+ if (type == typeof(bool)) return TypeCode.Boolean;
+ else if (type == typeof(byte)) return TypeCode.Byte;
+ else if (type == typeof(char)) return TypeCode.Char;
+ else if (type == typeof(ushort)) return TypeCode.UInt16;
+ else if (type == typeof(uint)) return TypeCode.UInt32;
+ else if (type == typeof(ulong)) return TypeCode.UInt64;
+ else if (type == typeof(sbyte)) return TypeCode.SByte;
+ else if (type == typeof(short)) return TypeCode.Int16;
+ else if (type == typeof(int)) return TypeCode.Int32;
+ else if (type == typeof(long)) return TypeCode.Int64;
+ else if (type == typeof(string)) return TypeCode.String;
+ else if (type == typeof(float)) return TypeCode.Single;
+ else if (type == typeof(double)) return TypeCode.Double;
+ else if (type == typeof(DateTime)) return TypeCode.DateTime;
+ else if (type == (typeof(Decimal))) return TypeCode.Decimal;
+ else return TypeCode.Object;
+ }
+
+ //
+ // FieldInfo extension methods
+ //
+ public static object GetRawConstantValue(this FieldInfo fi)
+ { return fi.GetValue(null); }
+
+ //
+ // Assembly extension methods
+ //
+ public static bool ReflectionOnly(this Assembly assm)
+ {
+ // In PCL we can't load in reflection-only context
+ return false;
+ }
+
+#endif
+ }
+}
+
+// Defining some no-ops in PCL builds
+#if ES_BUILD_PCL
+namespace System.Security
+{
+ class SuppressUnmanagedCodeSecurityAttribute : Attribute { }
+
+ enum SecurityAction { Demand }
+}
+
+namespace System.Security.Permissions
+{
+ class HostProtectionAttribute : Attribute { public bool MayLeakOnAbort { get; set; } }
+ class PermissionSetAttribute : Attribute
+ {
+ public PermissionSetAttribute(System.Security.SecurityAction action) { }
+ public bool Unrestricted { get; set; }
+ }
+}
+#endif
+
+#if ES_BUILD_PN
+namespace System
+{
+ internal static class AppDomain
+ {
+ public static int GetCurrentThreadId()
+ {
+ return Internal.Runtime.Augments.RuntimeThread.CurrentThread.ManagedThreadId;
+ }
+ }
+}
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Win32
+{
+ using System.Runtime.InteropServices;
+ using System.Security;
+
+ [SuppressUnmanagedCodeSecurityAttribute()]
+ internal static class Win32Native
+ {
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ internal static extern uint GetCurrentProcessId();
+ }
+}
+#endif
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs
new file mode 100644
index 0000000000..5771354f67
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ internal sealed class ArrayTypeInfo : TraceLoggingTypeInfo
+ {
+ private readonly TraceLoggingTypeInfo elementInfo;
+
+ public ArrayTypeInfo(Type type, TraceLoggingTypeInfo elementInfo)
+ : base(type)
+ {
+ this.elementInfo = elementInfo;
+ }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ collector.BeginBufferedArray();
+ this.elementInfo.WriteMetadata(collector, name, format);
+ collector.EndBufferedArray();
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ var bookmark = collector.BeginBufferedArray();
+
+ var count = 0;
+ Array array = (Array)value.ReferenceValue;
+ if (array != null)
+ {
+ count = array.Length;
+ for (int i = 0; i < array.Length; i++)
+ {
+ this.elementInfo.WriteData(collector, elementInfo.PropertyValueFactory(array.GetValue(i)));
+ }
+ }
+
+ collector.EndBufferedArray(bookmark, count);
+ }
+
+ public override object GetData(object value)
+ {
+ var array = (Array)value;
+ var serializedArray = new object[array.Length];
+ for (int i = 0; i < array.Length; i++)
+ {
+ serializedArray[i] = this.elementInfo.GetData(array.GetValue(i));
+ }
+ return serializedArray;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs
new file mode 100644
index 0000000000..76c01c6c06
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Interlocked = System.Threading.Interlocked;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: A very simple lock-free add-only dictionary.
+ /// Warning: this is a copy-by-value type. Copying performs a snapshot.
+ /// Accessing a readonly field always makes a copy of the field, so the
+ /// GetOrAdd method will not work as expected if called on a readonly field.
+ /// </summary>
+ /// <typeparam name="KeyType">
+ /// The type of the key, used for TryGet.
+ /// </typeparam>
+ /// <typeparam name="ItemType">
+ /// The type of the item, used for GetOrAdd.
+ /// </typeparam>
+ internal struct ConcurrentSet<KeyType, ItemType>
+ where ItemType : ConcurrentSetItem<KeyType, ItemType>
+ {
+ private ItemType[] items;
+
+ public ItemType TryGet(KeyType key)
+ {
+ ItemType item;
+ var oldItems = this.items;
+
+ if (oldItems != null)
+ {
+ var lo = 0;
+ var hi = oldItems.Length;
+ do
+ {
+ int i = (lo + hi) / 2;
+ item = oldItems[i];
+
+ int cmp = item.Compare(key);
+ if (cmp == 0)
+ {
+ goto Done;
+ }
+ else if (cmp < 0)
+ {
+ lo = i + 1;
+ }
+ else
+ {
+ hi = i;
+ }
+ }
+ while (lo != hi);
+ }
+
+ item = null;
+
+ Done:
+
+ return item;
+ }
+
+ public ItemType GetOrAdd(ItemType newItem)
+ {
+ ItemType item;
+ var oldItems = this.items;
+ ItemType[] newItems;
+
+ Retry:
+
+ if (oldItems == null)
+ {
+ newItems = new ItemType[] { newItem };
+ }
+ else
+ {
+ var lo = 0;
+ var hi = oldItems.Length;
+ do
+ {
+ int i = (lo + hi) / 2;
+ item = oldItems[i];
+
+ int cmp = item.Compare(newItem);
+ if (cmp == 0)
+ {
+ goto Done;
+ }
+ else if (cmp < 0)
+ {
+ lo = i + 1;
+ }
+ else
+ {
+ hi = i;
+ }
+ }
+ while (lo != hi);
+
+ int oldLength = oldItems.Length;
+ newItems = new ItemType[oldLength + 1];
+ Array.Copy(oldItems, 0, newItems, 0, lo);
+ newItems[lo] = newItem;
+ Array.Copy(oldItems, lo, newItems, lo + 1, oldLength - lo);
+ }
+
+ newItems = Interlocked.CompareExchange(ref this.items, newItems, oldItems);
+ if (oldItems != newItems)
+ {
+ oldItems = newItems;
+ goto Retry;
+ }
+
+ item = newItem;
+
+ Done:
+
+ return item;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs
new file mode 100644
index 0000000000..558dbf670b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Abstract base class that must be inherited by items in a
+ /// ConcurrentSet.
+ /// </summary>
+ /// <typeparam name="KeyType">Type of the set's key.</typeparam>
+ /// <typeparam name="ItemType">Type of the derived class.</typeparam>
+ internal abstract class ConcurrentSetItem<KeyType, ItemType>
+ where ItemType : ConcurrentSetItem<KeyType, ItemType>
+ {
+ public abstract int Compare(ItemType other);
+ public abstract int Compare(KeyType key);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs
new file mode 100644
index 0000000000..11c18a260f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs
@@ -0,0 +1,358 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Resources;
+using System.Runtime.InteropServices;
+using System.Security;
+
+#if ES_BUILD_STANDALONE
+using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment;
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: This is the implementation of the DataCollector
+ /// functionality. To enable safe access to the DataCollector from
+ /// untrusted code, there is one thread-local instance of this structure
+ /// per thread. The instance must be Enabled before any data is written to
+ /// it. The instance must be Finished before the data is passed to
+ /// EventWrite. The instance must be Disabled before the arrays referenced
+ /// by the pointers are freed or unpinned.
+ /// </summary>
+ internal unsafe struct DataCollector
+ {
+ [ThreadStatic]
+ internal static DataCollector ThreadInstance;
+
+ private byte* scratchEnd;
+ private EventSource.EventData* datasEnd;
+ private GCHandle* pinsEnd;
+ private EventSource.EventData* datasStart;
+ private byte* scratch;
+ private EventSource.EventData* datas;
+ private GCHandle* pins;
+ private byte[] buffer;
+ private int bufferPos;
+ private int bufferNesting; // We may merge many fields int a single blob. If we are doing this we increment this.
+ private bool writingScalars;
+
+ internal void Enable(
+ byte* scratch,
+ int scratchSize,
+ EventSource.EventData* datas,
+ int dataCount,
+ GCHandle* pins,
+ int pinCount)
+ {
+ this.datasStart = datas;
+ this.scratchEnd = scratch + scratchSize;
+ this.datasEnd = datas + dataCount;
+ this.pinsEnd = pins + pinCount;
+ this.scratch = scratch;
+ this.datas = datas;
+ this.pins = pins;
+ this.writingScalars = false;
+ }
+
+ internal void Disable()
+ {
+ this = new DataCollector();
+ }
+
+ /// <summary>
+ /// Completes the list of scalars. Finish must be called before the data
+ /// descriptor array is passed to EventWrite.
+ /// </summary>
+ /// <returns>
+ /// A pointer to the next unused data descriptor, or datasEnd if they were
+ /// all used. (Descriptors may be unused if a string or array was null.)
+ /// </returns>
+ internal EventSource.EventData* Finish()
+ {
+ this.ScalarsEnd();
+ return this.datas;
+ }
+
+ internal void AddScalar(void* value, int size)
+ {
+ var pb = (byte*)value;
+ if (this.bufferNesting == 0)
+ {
+ var scratchOld = this.scratch;
+ var scratchNew = scratchOld + size;
+ if (this.scratchEnd < scratchNew)
+ {
+ throw new IndexOutOfRangeException(SR.EventSource_AddScalarOutOfRange);
+ }
+
+ this.ScalarsBegin();
+ this.scratch = scratchNew;
+
+ for (int i = 0; i != size; i++)
+ {
+ scratchOld[i] = pb[i];
+ }
+ }
+ else
+ {
+ var oldPos = this.bufferPos;
+ this.bufferPos = checked(this.bufferPos + size);
+ this.EnsureBuffer();
+ for (int i = 0; i != size; i++, oldPos++)
+ {
+ this.buffer[oldPos] = pb[i];
+ }
+ }
+ }
+
+ internal void AddBinary(string value, int size)
+ {
+ if (size > ushort.MaxValue)
+ {
+ size = ushort.MaxValue - 1;
+ }
+
+ if (this.bufferNesting != 0)
+ {
+ this.EnsureBuffer(size + 2);
+ }
+
+ this.AddScalar(&size, 2);
+
+ if (size != 0)
+ {
+ if (this.bufferNesting == 0)
+ {
+ this.ScalarsEnd();
+ this.PinArray(value, size);
+ }
+ else
+ {
+ var oldPos = this.bufferPos;
+ this.bufferPos = checked(this.bufferPos + size);
+ this.EnsureBuffer();
+ fixed (void* p = value)
+ {
+ Marshal.Copy((IntPtr)p, buffer, oldPos, size);
+ }
+ }
+ }
+ }
+
+ internal unsafe void AddNullTerminatedString(string value)
+ {
+ // Treat null strings as empty strings.
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+
+ // Calculate the size of the string including the trailing NULL char.
+ // Don't use value.Length here because string allows for embedded NULL characters.
+ int nullCharIndex = value.IndexOf((char)0);
+ if (nullCharIndex < 0)
+ {
+ nullCharIndex = value.Length;
+ }
+ int size = (nullCharIndex + 1) * 2;
+
+ if (this.bufferNesting != 0)
+ {
+ this.EnsureBuffer(size);
+ }
+
+ if (this.bufferNesting == 0)
+ {
+ this.ScalarsEnd();
+ this.PinArray(value, size);
+ }
+ else
+ {
+ var oldPos = this.bufferPos;
+ this.bufferPos = checked(this.bufferPos + size);
+ this.EnsureBuffer();
+ fixed (void* p = value)
+ {
+ Marshal.Copy((IntPtr)p, buffer, oldPos, size);
+ }
+ }
+ }
+
+ internal void AddBinary(Array value, int size)
+ {
+ this.AddArray(value, size, 1);
+ }
+
+ internal void AddArray(Array value, int length, int itemSize)
+ {
+ if (length > ushort.MaxValue)
+ {
+ length = ushort.MaxValue;
+ }
+
+ var size = length * itemSize;
+ if (this.bufferNesting != 0)
+ {
+ this.EnsureBuffer(size + 2);
+ }
+
+ this.AddScalar(&length, 2);
+
+ if (length != 0)
+ {
+ if (this.bufferNesting == 0)
+ {
+ this.ScalarsEnd();
+ this.PinArray(value, size);
+ }
+ else
+ {
+ var oldPos = this.bufferPos;
+ this.bufferPos = checked(this.bufferPos + size);
+ this.EnsureBuffer();
+ Buffer.BlockCopy(value, 0, this.buffer, oldPos, size);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Marks the start of a non-blittable array or enumerable.
+ /// </summary>
+ /// <returns>Bookmark to be passed to EndBufferedArray.</returns>
+ internal int BeginBufferedArray()
+ {
+ this.BeginBuffered();
+ this.bufferPos += 2; // Reserve space for the array length (filled in by EndEnumerable)
+ return this.bufferPos;
+ }
+
+ /// <summary>
+ /// Marks the end of a non-blittable array or enumerable.
+ /// </summary>
+ /// <param name="bookmark">The value returned by BeginBufferedArray.</param>
+ /// <param name="count">The number of items in the array.</param>
+ internal void EndBufferedArray(int bookmark, int count)
+ {
+ this.EnsureBuffer();
+ this.buffer[bookmark - 2] = unchecked((byte)count);
+ this.buffer[bookmark - 1] = unchecked((byte)(count >> 8));
+ this.EndBuffered();
+ }
+
+ /// <summary>
+ /// Marks the start of dynamically-buffered data.
+ /// </summary>
+ internal void BeginBuffered()
+ {
+ this.ScalarsEnd();
+ this.bufferNesting += 1;
+ }
+
+ /// <summary>
+ /// Marks the end of dynamically-buffered data.
+ /// </summary>
+ internal void EndBuffered()
+ {
+ this.bufferNesting -= 1;
+
+ if (this.bufferNesting == 0)
+ {
+ /*
+ TODO (perf): consider coalescing adjacent buffered regions into a
+ single buffer, similar to what we're already doing for adjacent
+ scalars. In addition, if a type contains a buffered region adjacent
+ to a blittable array, and the blittable array is small, it would be
+ more efficient to buffer the array instead of pinning it.
+ */
+
+ this.EnsureBuffer();
+ this.PinArray(this.buffer, this.bufferPos);
+ this.buffer = null;
+ this.bufferPos = 0;
+ }
+ }
+
+ private void EnsureBuffer()
+ {
+ var required = this.bufferPos;
+ if (this.buffer == null || this.buffer.Length < required)
+ {
+ this.GrowBuffer(required);
+ }
+ }
+
+ private void EnsureBuffer(int additionalSize)
+ {
+ var required = this.bufferPos + additionalSize;
+ if (this.buffer == null || this.buffer.Length < required)
+ {
+ this.GrowBuffer(required);
+ }
+ }
+
+ private void GrowBuffer(int required)
+ {
+ var newSize = this.buffer == null ? 64 : this.buffer.Length;
+
+ do
+ {
+ newSize *= 2;
+ }
+ while (newSize < required);
+
+ Array.Resize(ref this.buffer, newSize);
+ }
+
+ private void PinArray(object value, int size)
+ {
+ var pinsTemp = this.pins;
+ if (this.pinsEnd <= pinsTemp)
+ {
+ throw new IndexOutOfRangeException(SR.EventSource_PinArrayOutOfRange);
+ }
+
+ var datasTemp = this.datas;
+ if (this.datasEnd <= datasTemp)
+ {
+ throw new IndexOutOfRangeException(SR.EventSource_DataDescriptorsOutOfRange);
+ }
+
+ this.pins = pinsTemp + 1;
+ this.datas = datasTemp + 1;
+
+ *pinsTemp = GCHandle.Alloc(value, GCHandleType.Pinned);
+ datasTemp->DataPointer = pinsTemp->AddrOfPinnedObject();
+ datasTemp->m_Size = size;
+ }
+
+ private void ScalarsBegin()
+ {
+ if (!this.writingScalars)
+ {
+ var datasTemp = this.datas;
+ if (this.datasEnd <= datasTemp)
+ {
+ throw new IndexOutOfRangeException(SR.EventSource_DataDescriptorsOutOfRange);
+ }
+
+ datasTemp->DataPointer = (IntPtr) this.scratch;
+ this.writingScalars = true;
+ }
+ }
+
+ private void ScalarsEnd()
+ {
+ if (this.writingScalars)
+ {
+ var datasTemp = this.datas;
+ datasTemp->m_Size = checked((int)(this.scratch - (byte*)datasTemp->m_Ptr));
+ this.datas = datasTemp + 1;
+ this.writingScalars = false;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs
new file mode 100644
index 0000000000..bc7fb8c346
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Empty struct indicating no payload data.
+ /// </summary>
+ internal struct EmptyStruct
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs
new file mode 100644
index 0000000000..7a23378bb1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#if EVENTSOURCE_GENERICS
+?using System;
+using System.Reflection;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Provides support for casting enums to their underlying type
+ /// from within generic context.
+ /// </summary>
+ /// <typeparam name="UnderlyingType">
+ /// The underlying type of the enum.
+ /// </typeparam>
+ internal static class EnumHelper<UnderlyingType>
+ {
+ public static UnderlyingType Cast<ValueType>(ValueType value)
+ {
+ return (UnderlyingType)(object)value;
+ }
+ }
+
+}
+#endif
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs
new file mode 100644
index 0000000000..74a3fa27b2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ internal sealed class EnumerableTypeInfo : TraceLoggingTypeInfo
+ {
+ private readonly TraceLoggingTypeInfo elementInfo;
+
+ public EnumerableTypeInfo(Type type, TraceLoggingTypeInfo elementInfo)
+ : base(type)
+ {
+ this.elementInfo = elementInfo;
+ }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ collector.BeginBufferedArray();
+ this.elementInfo.WriteMetadata(collector, name, format);
+ collector.EndBufferedArray();
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ var bookmark = collector.BeginBufferedArray();
+
+ var count = 0;
+ IEnumerable enumerable = (IEnumerable)value.ReferenceValue;
+ if (enumerable != null)
+ {
+ foreach (var element in enumerable)
+ {
+ this.elementInfo.WriteData(collector, elementInfo.PropertyValueFactory(element));
+ count++;
+ }
+ }
+
+ collector.EndBufferedArray(bookmark, count);
+ }
+
+ public override object GetData(object value)
+ {
+ var iterType = (IEnumerable)value;
+ List<object> serializedEnumerable = new List<object>();
+ foreach (var element in iterType)
+ {
+ serializedEnumerable.Add(elementInfo.GetData(element));
+ }
+ return serializedEnumerable.ToArray();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs
new file mode 100644
index 0000000000..cdedf13c64
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs
@@ -0,0 +1,146 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Used when authoring types that will be passed to EventSource.Write.
+ /// EventSource.Write&lt;T> only works when T is either an anonymous type
+ /// or a type with an [EventData] attribute. In addition, the properties
+ /// of T must be supported property types. Supported property types include
+ /// simple built-in types (int, string, Guid, DateTime, DateTimeOffset,
+ /// KeyValuePair, etc.), anonymous types that only contain supported types,
+ /// types with an [EventData] attribute, arrays of the above, and IEnumerable
+ /// of the above.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
+ public class EventDataAttribute
+ : Attribute
+ {
+ private EventLevel level = (EventLevel)(-1);
+ private EventOpcode opcode = (EventOpcode)(-1);
+
+ /// <summary>
+ /// Gets or sets the name to use if this type is used for an
+ /// implicitly-named event or an implicitly-named property.
+ ///
+ /// Example 1:
+ ///
+ /// EventSource.Write(null, new T()); // implicitly-named event
+ ///
+ /// The name of the event will be determined as follows:
+ ///
+ /// if (T has an EventData attribute and attribute.Name != null)
+ /// eventName = attribute.Name;
+ /// else
+ /// eventName = typeof(T).Name;
+ ///
+ /// Example 2:
+ ///
+ /// EventSource.Write(name, new { _1 = new T() }); // implicitly-named field
+ ///
+ /// The name of the field will be determined as follows:
+ ///
+ /// if (T has an EventData attribute and attribute.Name != null)
+ /// fieldName = attribute.Name;
+ /// else
+ /// fieldName = typeof(T).Name;
+ /// </summary>
+ public string Name
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Gets or sets the level to use for the event.
+ /// Invalid levels (outside the range 0..255) are treated as unset.
+ /// Note that the Level attribute can bubble-up, i.e. if a type contains
+ /// a sub-object (a field or property), and the sub-object's type has a
+ /// TraceLoggingEvent attribute, the Level from the sub-object's attribute
+ /// can affect the event's level.
+ ///
+ /// Example: for EventSource.Write(name, options, data), the level of the
+ /// event will be determined as follows:
+ ///
+ /// if (options.Level has been set)
+ /// eventLevel = options.Level;
+ /// else if (data.GetType() has a TraceLoggingEvent attribute and attribute.Level has been set)
+ /// eventLevel = attribute.Level;
+ /// else if (a field/property contained in data has a TraceLoggingEvent attribute and attribute.Level has been set)
+ /// eventLevel = attribute.Level;
+ /// else
+ /// eventLevel = EventLevel.LogAlways;
+ /// </summary>
+ internal EventLevel Level
+ {
+ get { return this.level; }
+ set { this.level = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the opcode to use for the event.
+ /// Invalid opcodes (outside the range 0..255) are treated as unset.
+ /// Note that the Opcode attribute can bubble-up, i.e. if a type contains
+ /// a sub-object (a field or property), and the sub-object's type has a
+ /// TraceLoggingEvent attribute, the Opcode from the sub-object's attribute
+ /// can affect the event's opcode.
+ ///
+ /// Example: for EventSource.Write(name, options, data), the opcode of the
+ /// event will be determined as follows:
+ ///
+ /// if (options.Opcode has been set)
+ /// eventOpcode = options.Opcode;
+ /// else if (data.GetType() has a TraceLoggingEvent attribute and attribute.Opcode has been set)
+ /// eventOpcode = attribute.Opcode;
+ /// else if (a field/property contained in data has a TraceLoggingEvent attribute and attribute.Opcode has been set)
+ /// eventOpcode = attribute.Opcode;
+ /// else
+ /// eventOpcode = EventOpcode.Info;
+ /// </summary>
+ internal EventOpcode Opcode
+ {
+ get { return this.opcode; }
+ set { this.opcode = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the keywords to use for the event.
+ /// Note that the Keywords attribute can bubble-up, i.e. if a type contains
+ /// a sub-object (a field or property), and the sub-object's type has a
+ /// TraceLoggingEvent attribute, the Keywords from the sub-object's attribute
+ /// can affect the event's keywords.
+ ///
+ /// Example: for EventSource.Write(name, options, data), the keywords of the
+ /// event will be determined as follows:
+ ///
+ /// eventKeywords = options.Keywords;
+ /// if (data.GetType() has a TraceLoggingEvent attribute)
+ /// eventKeywords |= attribute.Keywords;
+ /// if (a field/property contained in data has a TraceLoggingEvent attribute)
+ /// eventKeywords |= attribute.Keywords;
+ /// </summary>
+ internal EventKeywords Keywords
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Gets or sets the flags for an event. These flags are ignored by ETW,
+ /// but can have meaning to the event consumer.
+ /// </summary>
+ internal EventTags Tags
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs
new file mode 100644
index 0000000000..1a298c2851
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Tags are flags that are not interpreted by EventSource but are passed along
+ /// to the EventListener. The EventListener determines the semantics of the flags.
+ /// </summary>
+ [Flags]
+ public enum EventFieldTags
+ {
+ /// <summary>
+ /// No special traits are added to the field.
+ /// </summary>
+ None = 0,
+
+ /* Bits below 0x10000 are available for any use by the provider. */
+ /* Bits at or above 0x10000 are reserved for definition by Microsoft. */
+ }
+
+ /// <summary>
+ /// TraceLogging: used when authoring types that will be passed to EventSource.Write.
+ /// Controls how a field or property is handled when it is written as a
+ /// field in a TraceLogging event. Apply this attribute to a field or
+ /// property if the default handling is not correct. (Apply the
+ /// TraceLoggingIgnore attribute if the property should not be
+ /// included as a field in the event.)
+ /// The default for Name is null, which means that the name of the
+ /// underlying field or property will be used as the event field's name.
+ /// The default for PiiTag is 0, which means that the event field does not
+ /// contain personally-identifiable information.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Property)]
+ public class EventFieldAttribute
+ : Attribute
+ {
+ /// <summary>
+ /// User defined options for the field. These are not interpreted by the EventSource
+ /// but are available to the Listener. See EventFieldSettings for details
+ /// </summary>
+ public EventFieldTags Tags
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Gets or sets the name to use for the field. This defaults to null.
+ /// If null, the name of the corresponding property will be used
+ /// as the event field's name.
+ /// TODO REMOVE
+ /// </summary>
+ internal string Name
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Gets or sets a field formatting hint.
+ /// </summary>
+ public EventFieldFormat Format
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs
new file mode 100644
index 0000000000..fd77b07965
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs
@@ -0,0 +1,130 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Provides a hint that may be used by an event listener when formatting
+ /// an event field for display. Note that the event listener may ignore the
+ /// hint if it does not recognize a particular combination of type and format.
+ /// Similar to TDH_OUTTYPE.
+ /// </summary>
+ public enum EventFieldFormat
+ {
+ /// <summary>
+ /// Field receives default formatting based on the field's underlying type.
+ /// </summary>
+ Default = 0,
+#if false
+ /// <summary>
+ /// Field should not be displayed.
+ /// </summary>
+ NoPrint = 1,
+#endif
+ /// <summary>
+ /// Field should be formatted as character or string data.
+ /// Typically applied to 8-bit or 16-bit integers.
+ /// This is the default format for String and Char types.
+ /// </summary>
+ String = 2,
+
+ /// <summary>
+ /// Field should be formatted as boolean data. Typically applied to 8-bit
+ /// or 32-bit integers. This is the default format for the Boolean type.
+ /// </summary>
+ Boolean = 3,
+
+ /// <summary>
+ /// Field should be formatted as hexadecimal data. Typically applied to
+ /// integer types.
+ /// </summary>
+ Hexadecimal = 4,
+
+#if false
+ /// <summary>
+ /// Field should be formatted as a process identifier. Typically applied to
+ /// 32-bit integer types.
+ /// </summary>
+ ProcessId = 5,
+
+ /// <summary>
+ /// Field should be formatted as a thread identifier. Typically applied to
+ /// 32-bit integer types.
+ /// </summary>
+ ThreadId = 6,
+
+ /// <summary>
+ /// Field should be formatted as an Internet port. Typically applied to 16-bit integer
+ /// types.
+ /// </summary>
+ Port = 7,
+ /// <summary>
+ /// Field should be formatted as an Internet Protocol v4 address. Typically applied to
+ /// 32-bit integer types.
+ /// </summary>
+ Ipv4Address = 8,
+
+ /// <summary>
+ /// Field should be formatted as an Internet Protocol v6 address. Typically applied to
+ /// byte[] types.
+ /// </summary>
+ Ipv6Address = 9,
+ /// <summary>
+ /// Field should be formatted as a SOCKADDR. Typically applied to byte[] types.
+ /// </summary>
+ SocketAddress = 10,
+#endif
+ /// <summary>
+ /// Field should be formatted as XML string data. Typically applied to
+ /// strings or arrays of 8-bit or 16-bit integers.
+ /// </summary>
+ Xml = 11,
+
+ /// <summary>
+ /// Field should be formatted as JSON string data. Typically applied to
+ /// strings or arrays of 8-bit or 16-bit integers.
+ /// </summary>
+ Json = 12,
+#if false
+ /// <summary>
+ /// Field should be formatted as a Win32 error code. Typically applied to
+ /// 32-bit integer types.
+ /// </summary>
+ Win32Error = 13,
+
+ /// <summary>
+ /// Field should be formatted as an NTSTATUS code. Typically applied to
+ /// 32-bit integer types.
+ /// </summary>
+ NTStatus = 14,
+#endif
+ /// <summary>
+ /// Field should be formatted as an HRESULT code. Typically applied to
+ /// 32-bit integer types.
+ /// </summary>
+ HResult = 15,
+#if false
+ /// <summary>
+ /// Field should be formatted as a FILETIME. Typically applied to 64-bit
+ /// integer types. This is the default format for DateTime types.
+ /// </summary>
+ FileTime = 16,
+ /// <summary>
+ /// When applied to a numeric type, indicates that the type should be formatted
+ /// as a signed integer. This is the default format for signed integer types.
+ /// </summary>
+ Signed = 17,
+
+ /// <summary>
+ /// When applied to a numeric type, indicates that the type should be formatted
+ /// as an unsigned integer. This is the default format for unsigned integer types.
+ /// </summary>
+ Unsigned = 18,
+#endif
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs
new file mode 100644
index 0000000000..769345f78e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Used when authoring types that will be passed to EventSource.Write.
+ /// By default, EventSource.Write will write all of an object's public
+ /// properties to the event payload. Apply [EventIgnore] to a public
+ /// property to prevent EventSource.Write from including the property in
+ /// the event.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Property)]
+ public class EventIgnoreAttribute
+ : Attribute
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs
new file mode 100644
index 0000000000..30a941195f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using System.Diagnostics;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// EventPayload class holds the list of parameters and their corresponding values for user defined types passed to
+ /// EventSource APIs.
+ /// Preserving the order of the elements as they were found inside user defined types is the most important characteristic of this class.
+ /// </summary>
+ internal class EventPayload : IDictionary<string, object>
+ {
+ internal EventPayload(List<string> payloadNames, List<object> payloadValues)
+ {
+ Debug.Assert(payloadNames.Count == payloadValues.Count);
+
+ m_names = payloadNames;
+ m_values = payloadValues;
+ }
+
+ public ICollection<string> Keys { get { return m_names; } }
+ public ICollection<object> Values { get { return m_values; } }
+
+ public object this[string key]
+ {
+ get
+ {
+ if (key == null)
+ throw new System.ArgumentNullException(nameof(key));
+
+ int position = 0;
+ foreach(var name in m_names)
+ {
+ if (name == key)
+ {
+ return m_values[position];
+ }
+ position++;
+ }
+
+ throw new System.Collections.Generic.KeyNotFoundException(SR.Format(SR.Arg_KeyNotFoundWithKey, key.ToString()));
+ }
+ set
+ {
+ throw new System.NotSupportedException();
+ }
+ }
+
+ public void Add(string key, object value)
+ {
+ throw new System.NotSupportedException();
+ }
+
+ public void Add(KeyValuePair<string, object> payloadEntry)
+ {
+ throw new System.NotSupportedException();
+ }
+
+ public void Clear()
+ {
+ throw new System.NotSupportedException();
+ }
+
+ public bool Contains(KeyValuePair<string, object> entry)
+ {
+ return ContainsKey(entry.Key);
+ }
+
+ public bool ContainsKey(string key)
+ {
+ if (key == null)
+ throw new System.ArgumentNullException(nameof(key));
+
+ foreach (var item in m_names)
+ {
+ if (item == key)
+ return true;
+ }
+ return false;
+ }
+
+ public int Count { get { return m_names.Count; } }
+
+ public bool IsReadOnly { get { return true; } }
+
+ public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+ {
+ for (int i = 0; i < Keys.Count; i++)
+ {
+ yield return new KeyValuePair<string, object>(this.m_names[i], this.m_values[i]);
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ var instance = this as IEnumerable<KeyValuePair<string, object>>;
+ return instance.GetEnumerator();
+ }
+
+ public void CopyTo(KeyValuePair<string, object>[] payloadEntries, int count)
+ {
+ throw new System.NotSupportedException();
+ }
+
+ public bool Remove(string key)
+ {
+ throw new System.NotSupportedException();
+ }
+
+ public bool Remove(KeyValuePair<string, object> entry)
+ {
+ throw new System.NotSupportedException();
+ }
+
+ public bool TryGetValue(string key, out object value)
+ {
+ if (key == null)
+ throw new System.ArgumentNullException(nameof(key));
+
+ int position = 0;
+ foreach (var name in m_names)
+ {
+ if (name == key)
+ {
+ value = m_values[position];
+ return true;
+ }
+ position++;
+ }
+
+ value = default(object);
+ return false;
+ }
+
+ #region private
+ private List<string> m_names;
+ private List<object> m_values;
+ #endregion
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs
new file mode 100644
index 0000000000..2d71550803
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs
@@ -0,0 +1,320 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Provides support for EventSource activities by marking the start and
+ /// end of a particular operation.
+ /// </summary>
+ internal sealed class EventSourceActivity
+ : IDisposable
+ {
+ /// <summary>
+ /// Initializes a new instance of the EventSourceActivity class that
+ /// is attached to the specified event source. The new activity will
+ /// not be attached to any related (parent) activity.
+ /// The activity is created in the Initialized state.
+ /// </summary>
+ /// <param name="eventSource">
+ /// The event source to which the activity information is written.
+ /// </param>
+ public EventSourceActivity(EventSource eventSource)
+ {
+ if (eventSource == null)
+ throw new ArgumentNullException(nameof(eventSource));
+
+ this.eventSource = eventSource;
+ }
+
+ /// <summary>
+ /// You can make an activity out of just an EventSource.
+ /// </summary>
+ public static implicit operator EventSourceActivity(EventSource eventSource) { return new EventSourceActivity(eventSource); }
+
+ /* Properties */
+ /// <summary>
+ /// Gets the event source to which this activity writes events.
+ /// </summary>
+ public EventSource EventSource
+ {
+ get { return this.eventSource; }
+ }
+
+ /// <summary>
+ /// Gets this activity's unique identifier, or the default Guid if the
+ /// event source was disabled when the activity was initialized.
+ /// </summary>
+ public Guid Id
+ {
+ get { return this.activityId; }
+ }
+
+#if false // don't expose RelatedActivityId unless there is a need.
+ /// <summary>
+ /// Gets the unique identifier of this activity's related (parent)
+ /// activity.
+ /// </summary>
+ public Guid RelatedId
+ {
+ get { return this.relatedActivityId; }
+ }
+#endif
+
+ /// <summary>
+ /// Writes a Start event with the specified name and data. If the start event is not active (because the provider
+ /// is not on or keyword-level indicates the event is off, then the returned activity is simply the 'this' pointer
+ /// and it is effectively like start did not get called.
+ ///
+ /// A new activityID GUID is generated and the returned
+ /// EventSourceActivity remembers this activity and will mark every event (including the start stop and any writes)
+ /// with this activityID. In addition the Start activity will log a 'relatedActivityID' that was the activity
+ /// ID before the start event. This way event processors can form a linked list of all the activities that
+ /// caused this one (directly or indirectly).
+ /// </summary>
+ /// <param name="eventName">
+ /// The name to use for the event. It is strongly suggested that this name end in 'Start' (e.g. DownloadStart).
+ /// If you do this, then the Stop() method will automatically replace the 'Start' suffix with a 'Stop' suffix.
+ /// </param>
+ /// <param name="options">Allow options (keywords, level) to be set for the write associated with this start
+ /// These will also be used for the stop event.</param>
+ /// <param name="data">The data to include in the event.</param>
+ public EventSourceActivity Start<T>(string eventName, EventSourceOptions options, T data)
+ {
+ return this.Start(eventName, ref options, ref data);
+ }
+ /// <summary>
+ /// Shortcut version see Start(string eventName, EventSourceOptions options, T data) Options is empty (no keywords
+ /// and level==Info) Data payload is empty.
+ /// </summary>
+ public EventSourceActivity Start(string eventName)
+ {
+ var options = new EventSourceOptions();
+ var data = new EmptyStruct();
+ return this.Start(eventName, ref options, ref data);
+ }
+ /// <summary>
+ /// Shortcut version see Start(string eventName, EventSourceOptions options, T data). Data payload is empty.
+ /// </summary>
+ public EventSourceActivity Start(string eventName, EventSourceOptions options)
+ {
+ var data = new EmptyStruct();
+ return this.Start(eventName, ref options, ref data);
+ }
+ /// <summary>
+ /// Shortcut version see Start(string eventName, EventSourceOptions options, T data) Options is empty (no keywords
+ /// and level==Info)
+ /// </summary>
+ public EventSourceActivity Start<T>(string eventName, T data)
+ {
+ var options = new EventSourceOptions();
+ return this.Start(eventName, ref options, ref data);
+ }
+
+ /// <summary>
+ /// Writes a Stop event with the specified data, and sets the activity
+ /// to the Stopped state. The name is determined by the eventName used in Start.
+ /// If that Start event name is suffixed with 'Start' that is removed, and regardless
+ /// 'Stop' is appended to the result to form the Stop event name.
+ /// May only be called when the activity is in the Started state.
+ /// </summary>
+ /// <param name="data">The data to include in the event.</param>
+ public void Stop<T>(T data)
+ {
+ this.Stop(null, ref data);
+ }
+ /// <summary>
+ /// Used if you wish to use the non-default stop name (which is the start name with Start replace with 'Stop')
+ /// This can be useful to indicate unusual ways of stopping (but it is still STRONGLY recommended that
+ /// you start with the same prefix used for the start event and you end with the 'Stop' suffix.
+ /// </summary>
+ public void Stop<T>(string eventName)
+ {
+ var data = new EmptyStruct();
+ this.Stop(eventName, ref data);
+ }
+ /// <summary>
+ /// Used if you wish to use the non-default stop name (which is the start name with Start replace with 'Stop')
+ /// This can be useful to indicate unusual ways of stopping (but it is still STRONGLY recommended that
+ /// you start with the same prefix used for the start event and you end with the 'Stop' suffix.
+ /// </summary>
+ public void Stop<T>(string eventName, T data)
+ {
+ this.Stop(eventName, ref data);
+ }
+
+ /// <summary>
+ /// Writes an event associated with this activity to the eventSource associated with this activity.
+ /// May only be called when the activity is in the Started state.
+ /// </summary>
+ /// <param name="eventName">
+ /// The name to use for the event. If null, the name is determined from
+ /// data's type.
+ /// </param>
+ /// <param name="options">
+ /// The options to use for the event.
+ /// </param>
+ /// <param name="data">The data to include in the event.</param>
+ public void Write<T>(string eventName, EventSourceOptions options, T data)
+ {
+ this.Write(this.eventSource, eventName, ref options, ref data);
+ }
+ /// <summary>
+ /// Writes an event associated with this activity.
+ /// May only be called when the activity is in the Started state.
+ /// </summary>
+ /// <param name="eventName">
+ /// The name to use for the event. If null, the name is determined from
+ /// data's type.
+ /// </param>
+ /// <param name="data">The data to include in the event.</param>
+ public void Write<T>(string eventName, T data)
+ {
+ var options = new EventSourceOptions();
+ this.Write(this.eventSource, eventName, ref options, ref data);
+ }
+ /// <summary>
+ /// Writes a trivial event associated with this activity.
+ /// May only be called when the activity is in the Started state.
+ /// </summary>
+ /// <param name="eventName">
+ /// The name to use for the event. Must not be null.
+ /// </param>
+ /// <param name="options">
+ /// The options to use for the event.
+ /// </param>
+ public void Write(string eventName, EventSourceOptions options)
+ {
+ var data = new EmptyStruct();
+ this.Write(this.eventSource, eventName, ref options, ref data);
+ }
+ /// <summary>
+ /// Writes a trivial event associated with this activity.
+ /// May only be called when the activity is in the Started state.
+ /// </summary>
+ /// <param name="eventName">
+ /// The name to use for the event. Must not be null.
+ /// </param>
+ public void Write(string eventName)
+ {
+ var options = new EventSourceOptions();
+ var data = new EmptyStruct();
+ this.Write(this.eventSource, eventName, ref options, ref data);
+ }
+ /// <summary>
+ /// Writes an event to a arbitrary eventSource stamped with the activity ID of this activity.
+ /// </summary>
+ public void Write<T>(EventSource source, string eventName, EventSourceOptions options, T data)
+ {
+ this.Write(source, eventName, ref options, ref data);
+ }
+
+ /// <summary>
+ /// Releases any unmanaged resources associated with this object.
+ /// If the activity is in the Started state, calls Stop().
+ /// </summary>
+ public void Dispose()
+ {
+ if (this.state == State.Started)
+ {
+ var data = new EmptyStruct();
+ this.Stop(null, ref data);
+ }
+ }
+
+ #region private
+ private EventSourceActivity Start<T>(string eventName, ref EventSourceOptions options, ref T data)
+ {
+ if (this.state != State.Started)
+ throw new InvalidOperationException();
+
+ // If the source is not on at all, then we don't need to do anything and we can simply return ourselves.
+ if (!this.eventSource.IsEnabled())
+ return this;
+
+ var newActivity = new EventSourceActivity(eventSource);
+ if (!this.eventSource.IsEnabled(options.Level, options.Keywords))
+ {
+ // newActivity.relatedActivityId = this.Id;
+ Guid relatedActivityId = this.Id;
+ newActivity.activityId = Guid.NewGuid();
+ newActivity.startStopOptions = options;
+ newActivity.eventName = eventName;
+ newActivity.startStopOptions.Opcode = EventOpcode.Start;
+ this.eventSource.Write(eventName, ref newActivity.startStopOptions, ref newActivity.activityId, ref relatedActivityId, ref data);
+ }
+ else
+ {
+ // If we are not active, we don't set the eventName, which basically also turns off the Stop event as well.
+ newActivity.activityId = this.Id;
+ }
+
+ return newActivity;
+ }
+
+ private void Write<T>(EventSource eventSource, string eventName, ref EventSourceOptions options, ref T data)
+ {
+ if (this.state != State.Started)
+ throw new InvalidOperationException(); // Write after stop.
+ if (eventName == null)
+ throw new ArgumentNullException();
+
+ eventSource.Write(eventName, ref options, ref this.activityId, ref s_empty, ref data);
+ }
+
+ private void Stop<T>(string eventName, ref T data)
+ {
+ if (this.state != State.Started)
+ throw new InvalidOperationException();
+
+ // If start was not fired, then stop isn't as well.
+ if (!StartEventWasFired)
+ return;
+
+ this.state = State.Stopped;
+ if (eventName == null)
+ {
+ eventName = this.eventName;
+ if (eventName.EndsWith("Start"))
+ eventName = eventName.Substring(0, eventName.Length - 5);
+ eventName = eventName + "Stop";
+ }
+ this.startStopOptions.Opcode = EventOpcode.Stop;
+ this.eventSource.Write(eventName, ref this.startStopOptions, ref this.activityId, ref s_empty, ref data);
+ }
+
+ private enum State
+ {
+ Started,
+ Stopped
+ }
+
+ /// <summary>
+ /// If eventName is non-null then we logged a start event
+ /// </summary>
+ private bool StartEventWasFired { get { return eventName != null; } }
+
+ private readonly EventSource eventSource;
+ private EventSourceOptions startStopOptions;
+ internal Guid activityId;
+ // internal Guid relatedActivityId;
+ private State state;
+ private string eventName;
+
+ internal static Guid s_empty;
+ #endregion
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs
new file mode 100644
index 0000000000..26305a5708
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs
@@ -0,0 +1,130 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Used when calling EventSource.Write.
+ /// Optional overrides for event settings such as Level, Keywords, or Opcode.
+ /// If overrides are not provided for a setting, default values will be used.
+ /// </summary>
+ public struct EventSourceOptions
+ {
+ internal EventKeywords keywords;
+ internal EventTags tags;
+ internal EventActivityOptions activityOptions;
+ internal byte level;
+ internal byte opcode;
+ internal byte valuesSet;
+
+ internal const byte keywordsSet = 0x1;
+ internal const byte tagsSet = 0x2;
+ internal const byte levelSet = 0x4;
+ internal const byte opcodeSet = 0x8;
+ internal const byte activityOptionsSet = 0x10;
+
+ /// <summary>
+ /// Gets or sets the level to use for the specified event. If this property
+ /// is unset, the event's level will be 5 (Verbose).
+ /// </summary>
+ public EventLevel Level
+ {
+ get
+ {
+ return (EventLevel)this.level;
+ }
+
+ set
+ {
+ this.level = checked((byte)value);
+ this.valuesSet |= levelSet;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the opcode to use for the specified event. If this property
+ /// is unset, the event's opcode will 0 (Info).
+ /// </summary>
+ public EventOpcode Opcode
+ {
+ get
+ {
+ return (EventOpcode)this.opcode;
+ }
+
+ set
+ {
+ this.opcode = checked((byte)value);
+ this.valuesSet |= opcodeSet;
+ }
+ }
+
+ internal bool IsOpcodeSet
+ {
+ get
+ {
+ return (this.valuesSet & opcodeSet) != 0;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the keywords to use for the specified event. If this
+ /// property is unset, the event's keywords will be 0.
+ /// </summary>
+ public EventKeywords Keywords
+ {
+ get
+ {
+ return this.keywords;
+ }
+
+ set
+ {
+ this.keywords = value;
+ this.valuesSet |= keywordsSet;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the tags to use for the specified event. If this property is
+ /// unset, the event's tags will be 0.
+ /// </summary>
+ public EventTags Tags
+ {
+ get
+ {
+ return this.tags;
+ }
+
+ set
+ {
+ this.tags = value;
+ this.valuesSet |= tagsSet;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the activity options for this specified events. If this property is
+ /// unset, the event's activity options will be 0.
+ /// </summary>
+ public EventActivityOptions ActivityOptions
+ {
+ get
+ {
+ return this.activityOptions;
+ }
+ set
+ {
+ this.activityOptions = value;
+ this.valuesSet |= activityOptionsSet;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs
new file mode 100644
index 0000000000..f153734752
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs
@@ -0,0 +1,230 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Resources;
+using Encoding = System.Text.Encoding;
+
+#if ES_BUILD_STANDALONE
+using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment;
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Contains the information needed to generate tracelogging
+ /// metadata for an event field.
+ /// </summary>
+ internal class FieldMetadata
+ {
+ /// <summary>
+ /// Name of the field
+ /// </summary>
+ private readonly string name;
+
+ /// <summary>
+ /// The number of bytes in the UTF8 Encoding of 'name' INCLUDING a null terminator.
+ /// </summary>
+ private readonly int nameSize;
+ private readonly EventFieldTags tags;
+ private readonly byte[] custom;
+
+ /// <summary>
+ /// ETW supports fixed sized arrays. If inType has the InTypeFixedCountFlag then this is the
+ /// statically known count for the array. It is also used to encode the number of bytes of
+ /// custom meta-data if InTypeCustomCountFlag set.
+ /// </summary>
+ private readonly ushort fixedCount;
+
+ private byte inType;
+ private byte outType;
+
+ /// <summary>
+ /// Scalar or variable-length array.
+ /// </summary>
+ public FieldMetadata(
+ string name,
+ TraceLoggingDataType type,
+ EventFieldTags tags,
+ bool variableCount)
+ : this(
+ name,
+ type,
+ tags,
+ variableCount ? Statics.InTypeVariableCountFlag : (byte)0,
+ 0,
+ null)
+ {
+ return;
+ }
+
+ /// <summary>
+ /// Fixed-length array.
+ /// </summary>
+ public FieldMetadata(
+ string name,
+ TraceLoggingDataType type,
+ EventFieldTags tags,
+ ushort fixedCount)
+ : this(
+ name,
+ type,
+ tags,
+ Statics.InTypeFixedCountFlag,
+ fixedCount,
+ null)
+ {
+ return;
+ }
+
+ /// <summary>
+ /// Custom serializer
+ /// </summary>
+ public FieldMetadata(
+ string name,
+ TraceLoggingDataType type,
+ EventFieldTags tags,
+ byte[] custom)
+ : this(
+ name,
+ type,
+ tags,
+ Statics.InTypeCustomCountFlag,
+ checked((ushort)(custom == null ? 0 : custom.Length)),
+ custom)
+ {
+ return;
+ }
+
+ private FieldMetadata(
+ string name,
+ TraceLoggingDataType dataType,
+ EventFieldTags tags,
+ byte countFlags,
+ ushort fixedCount = 0,
+ byte[] custom = null)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(
+ nameof(name),
+ "This usually means that the object passed to Write is of a type that"
+ + " does not support being used as the top-level object in an event,"
+ + " e.g. a primitive or built-in type.");
+ }
+
+ Statics.CheckName(name);
+ var coreType = (int)dataType & Statics.InTypeMask;
+ this.name = name;
+ this.nameSize = Encoding.UTF8.GetByteCount(this.name) + 1;
+ this.inType = (byte)(coreType | countFlags);
+ this.outType = (byte)(((int)dataType >> 8) & Statics.OutTypeMask);
+ this.tags = tags;
+ this.fixedCount = fixedCount;
+ this.custom = custom;
+
+ if (countFlags != 0)
+ {
+ if (coreType == (int)TraceLoggingDataType.Nil)
+ {
+ throw new NotSupportedException(SR.EventSource_NotSupportedArrayOfNil);
+ }
+ if (coreType == (int)TraceLoggingDataType.Binary)
+ {
+ throw new NotSupportedException(SR.EventSource_NotSupportedArrayOfBinary);
+ }
+ if (coreType == (int)TraceLoggingDataType.Utf16String ||
+ coreType == (int)TraceLoggingDataType.MbcsString)
+ {
+ throw new NotSupportedException(SR.EventSource_NotSupportedArrayOfNullTerminatedString);
+ }
+ }
+
+ if (((int)this.tags & 0xfffffff) != 0)
+ {
+ this.outType |= Statics.OutTypeChainFlag;
+ }
+
+ if (this.outType != 0)
+ {
+ this.inType |= Statics.InTypeChainFlag;
+ }
+ }
+
+ public void IncrementStructFieldCount()
+ {
+ this.inType |= Statics.InTypeChainFlag;
+ this.outType++;
+ if ((this.outType & Statics.OutTypeMask) == 0)
+ {
+ throw new NotSupportedException(SR.EventSource_TooManyFields);
+ }
+ }
+
+ /// <summary>
+ /// This is the main routine for FieldMetaData. Basically it will serialize the data in
+ /// this structure as TraceLogging style meta-data into the array 'metaArray' starting at
+ /// 'pos' (pos is updated to reflect the bytes written).
+ ///
+ /// Note that 'metaData' can be null, in which case it only updates 'pos'. This is useful
+ /// for a 'two pass' approach where you figure out how big to make the array, and then you
+ /// fill it in.
+ /// </summary>
+ public void Encode(ref int pos, byte[] metadata)
+ {
+ // Write out the null terminated UTF8 encoded name
+ if (metadata != null)
+ {
+ Encoding.UTF8.GetBytes(this.name, 0, this.name.Length, metadata, pos);
+ }
+ pos += this.nameSize;
+
+ // Write 1 byte for inType
+ if (metadata != null)
+ {
+ metadata[pos] = this.inType;
+ }
+ pos += 1;
+
+ // If InTypeChainFlag set, then write out the outType
+ if (0 != (this.inType & Statics.InTypeChainFlag))
+ {
+ if (metadata != null)
+ {
+ metadata[pos] = this.outType;
+ }
+ pos += 1;
+
+ // If OutTypeChainFlag set, then write out tags
+ if (0 != (this.outType & Statics.OutTypeChainFlag))
+ {
+ Statics.EncodeTags((int)this.tags, ref pos, metadata);
+ }
+ }
+
+ // If InTypeFixedCountFlag set, write out the fixedCount (2 bytes little endian)
+ if (0 != (this.inType & Statics.InTypeFixedCountFlag))
+ {
+ if (metadata != null)
+ {
+ metadata[pos + 0] = unchecked((byte)this.fixedCount);
+ metadata[pos + 1] = (byte)(this.fixedCount >> 8);
+ }
+ pos += 2;
+
+ // If InTypeCustomCountFlag set, write out the blob of custom meta-data.
+ if (Statics.InTypeCustomCountFlag == (this.inType & Statics.InTypeCountMask) &&
+ this.fixedCount != 0)
+ {
+ if (metadata != null)
+ {
+ Buffer.BlockCopy(this.custom, 0, metadata, pos, this.fixedCount);
+ }
+ pos += this.fixedCount;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs
new file mode 100644
index 0000000000..2a7113e5d7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs
@@ -0,0 +1,96 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: An implementation of TraceLoggingTypeInfo that works
+ /// for arbitrary types. It writes all public instance properties of
+ /// the type.
+ /// </summary>
+ /// <typeparam name="ContainerType">
+ /// Type from which to read values.
+ /// </typeparam>
+ internal sealed class InvokeTypeInfo : TraceLoggingTypeInfo
+ {
+ internal readonly PropertyAnalysis[] properties;
+
+ public InvokeTypeInfo(
+ Type type,
+ TypeAnalysis typeAnalysis)
+ : base(
+ type,
+ typeAnalysis.name,
+ typeAnalysis.level,
+ typeAnalysis.opcode,
+ typeAnalysis.keywords,
+ typeAnalysis.tags)
+ {
+ if (typeAnalysis.properties.Length != 0)
+ this.properties = typeAnalysis.properties;
+ }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ var groupCollector = collector.AddGroup(name);
+ if (this.properties != null)
+ {
+ foreach (var property in this.properties)
+ {
+ var propertyFormat = EventFieldFormat.Default;
+ var propertyAttribute = property.fieldAttribute;
+ if (propertyAttribute != null)
+ {
+ groupCollector.Tags = propertyAttribute.Tags;
+ propertyFormat = propertyAttribute.Format;
+ }
+
+ property.typeInfo.WriteMetadata(
+ groupCollector,
+ property.name,
+ propertyFormat);
+ }
+ }
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ if (this.properties != null)
+ {
+ foreach (var property in this.properties)
+ {
+ property.typeInfo.WriteData(collector, property.getter(value));
+ }
+ }
+ }
+
+ public override object GetData(object value)
+ {
+ if (this.properties != null)
+ {
+ var membersNames = new List<string>();
+ var memebersValues = new List<object>();
+ for (int i = 0; i < this.properties.Length; i++)
+ {
+ var propertyValue = properties[i].propertyInfo.GetValue(value);
+ membersNames.Add(properties[i].name);
+ memebersValues.Add(properties[i].typeInfo.GetData(propertyValue));
+ }
+ return new EventPayload(membersNames, memebersValues);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs
new file mode 100644
index 0000000000..b1c7327c18
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs
@@ -0,0 +1,126 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using Interlocked = System.Threading.Interlocked;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Stores the metadata and event identifier corresponding
+ /// to a tracelogging event type+name+tags combination.
+ /// </summary>
+ internal sealed class NameInfo
+ : ConcurrentSetItem<KeyValuePair<string, EventTags>, NameInfo>
+ {
+ /// <summary>
+ /// Insure that eventIds strictly less than 'eventId' will not be
+ /// used by the SelfDescribing events.
+ /// </summary>
+ internal static void ReserveEventIDsBelow(int eventId)
+ {
+ for(;;)
+ {
+ int snapshot = lastIdentity;
+ int newIdentity = (lastIdentity & ~0xFFFFFF) + eventId;
+ newIdentity = Math.Max(newIdentity, snapshot); // Should be redundant. as we only create descriptors once.
+ if (Interlocked.CompareExchange(ref lastIdentity, newIdentity, snapshot) == snapshot)
+ break;
+ }
+ }
+
+ private static int lastIdentity = Statics.TraceLoggingChannel << 24;
+ internal readonly string name;
+ internal readonly EventTags tags;
+ internal readonly int identity;
+ internal readonly byte[] nameMetadata;
+
+#if FEATURE_PERFTRACING
+ private readonly object eventHandleCreationLock = new object();
+#endif
+
+ public NameInfo(string name, EventTags tags, int typeMetadataSize)
+ {
+ this.name = name;
+ this.tags = tags & Statics.EventTagsMask;
+ this.identity = Interlocked.Increment(ref lastIdentity);
+
+ int tagsPos = 0;
+ Statics.EncodeTags((int)this.tags, ref tagsPos, null);
+
+ this.nameMetadata = Statics.MetadataForString(name, tagsPos, 0, typeMetadataSize);
+
+ tagsPos = 2;
+ Statics.EncodeTags((int)this.tags, ref tagsPos, this.nameMetadata);
+ }
+
+ public override int Compare(NameInfo other)
+ {
+ return this.Compare(other.name, other.tags);
+ }
+
+ public override int Compare(KeyValuePair<string, EventTags> key)
+ {
+ return this.Compare(key.Key, key.Value & Statics.EventTagsMask);
+ }
+
+ private int Compare(string otherName, EventTags otherTags)
+ {
+ int result = StringComparer.Ordinal.Compare(this.name, otherName);
+ if (result == 0 && this.tags != otherTags)
+ {
+ result = this.tags < otherTags ? -1 : 1;
+ }
+ return result;
+ }
+
+#if FEATURE_PERFTRACING
+ public IntPtr GetOrCreateEventHandle(EventProvider provider, ConcurrentDictionary<int, IntPtr> eventHandleMap, EventDescriptor descriptor, TraceLoggingEventTypes eventTypes)
+ {
+ IntPtr eventHandle = IntPtr.Zero;
+ if(!eventHandleMap.TryGetValue(descriptor.EventId, out eventHandle))
+ {
+ lock (eventHandleCreationLock)
+ {
+ if (!eventHandleMap.TryGetValue(descriptor.EventId, out eventHandle))
+ {
+ byte[] metadataBlob = EventPipeMetadataGenerator.Instance.GenerateEventMetadata(
+ descriptor.EventId,
+ name,
+ (EventKeywords)descriptor.Keywords,
+ (EventLevel)descriptor.Level,
+ descriptor.Version,
+ eventTypes);
+ uint metadataLength = (metadataBlob != null) ? (uint)metadataBlob.Length : 0;
+
+ unsafe
+ {
+ fixed (byte* pMetadataBlob = metadataBlob)
+ {
+ // Define the event.
+ eventHandle = provider.m_eventProvider.DefineEventHandle(
+ (uint)descriptor.EventId,
+ name,
+ descriptor.Keywords,
+ descriptor.Version,
+ descriptor.Level,
+ pMetadataBlob,
+ metadataLength);
+ }
+ }
+ }
+ }
+ }
+
+ return eventHandle;
+ }
+#endif
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs
new file mode 100644
index 0000000000..1f07539b52
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Reflection;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: stores the per-property information obtained by
+ /// reflecting over a type.
+ /// </summary>
+ internal sealed class PropertyAnalysis
+ {
+ internal readonly string name;
+ internal readonly PropertyInfo propertyInfo;
+ internal readonly Func<PropertyValue, PropertyValue> getter;
+ internal readonly TraceLoggingTypeInfo typeInfo;
+ internal readonly EventFieldAttribute fieldAttribute;
+
+ public PropertyAnalysis(
+ string name,
+ PropertyInfo propertyInfo,
+ TraceLoggingTypeInfo typeInfo,
+ EventFieldAttribute fieldAttribute)
+ {
+ this.name = name;
+ this.propertyInfo = propertyInfo;
+ this.getter = PropertyValue.GetPropertyGetter(propertyInfo);
+ this.typeInfo = typeInfo;
+ this.fieldAttribute = fieldAttribute;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs
new file mode 100644
index 0000000000..ae60888493
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs
@@ -0,0 +1,277 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Holds property values of any type. For common value types, we have inline storage so that we don't need
+ /// to box the values. For all other types, we store the value in a single object reference field.
+ ///
+ /// To get the value of a property quickly, use a delegate produced by <see cref="PropertyValue.GetPropertyGetter(PropertyInfo)"/>.
+ /// </summary>
+#if ES_BUILD_PN
+ [CLSCompliant(false)]
+ public
+#else
+ internal
+#endif
+ unsafe readonly struct PropertyValue
+ {
+ /// <summary>
+ /// Union of well-known value types, to avoid boxing those types.
+ /// </summary>
+ [StructLayout(LayoutKind.Explicit)]
+ public struct Scalar
+ {
+ [FieldOffset(0)]
+ public Boolean AsBoolean;
+ [FieldOffset(0)]
+ public Byte AsByte;
+ [FieldOffset(0)]
+ public SByte AsSByte;
+ [FieldOffset(0)]
+ public Char AsChar;
+ [FieldOffset(0)]
+ public Int16 AsInt16;
+ [FieldOffset(0)]
+ public UInt16 AsUInt16;
+ [FieldOffset(0)]
+ public Int32 AsInt32;
+ [FieldOffset(0)]
+ public UInt32 AsUInt32;
+ [FieldOffset(0)]
+ public Int64 AsInt64;
+ [FieldOffset(0)]
+ public UInt64 AsUInt64;
+ [FieldOffset(0)]
+ public IntPtr AsIntPtr;
+ [FieldOffset(0)]
+ public UIntPtr AsUIntPtr;
+ [FieldOffset(0)]
+ public Single AsSingle;
+ [FieldOffset(0)]
+ public Double AsDouble;
+ [FieldOffset(0)]
+ public Guid AsGuid;
+ [FieldOffset(0)]
+ public DateTime AsDateTime;
+ [FieldOffset(0)]
+ public DateTimeOffset AsDateTimeOffset;
+ [FieldOffset(0)]
+ public TimeSpan AsTimeSpan;
+ [FieldOffset(0)]
+ public Decimal AsDecimal;
+ }
+
+ // Anything not covered by the Scalar union gets stored in this reference.
+ readonly object _reference;
+ readonly Scalar _scalar;
+ readonly int _scalarLength;
+
+ private PropertyValue(object value)
+ {
+ _reference = value;
+ _scalar = default(Scalar);
+ _scalarLength = 0;
+ }
+
+ private PropertyValue(Scalar scalar, int scalarLength)
+ {
+ _reference = null;
+ _scalar = scalar;
+ _scalarLength = scalarLength;
+ }
+
+ private PropertyValue(Boolean value) : this(new Scalar() { AsBoolean = value }, sizeof(Boolean)) { }
+ private PropertyValue(Byte value) : this(new Scalar() { AsByte = value }, sizeof(Byte)) { }
+ private PropertyValue(SByte value) : this(new Scalar() { AsSByte = value }, sizeof(SByte)) { }
+ private PropertyValue(Char value) : this(new Scalar() { AsChar = value }, sizeof(Char)) { }
+ private PropertyValue(Int16 value) : this(new Scalar() { AsInt16 = value }, sizeof(Int16)) { }
+ private PropertyValue(UInt16 value) : this(new Scalar() { AsUInt16 = value }, sizeof(UInt16)) { }
+ private PropertyValue(Int32 value) : this(new Scalar() { AsInt32 = value }, sizeof(Int32)) { }
+ private PropertyValue(UInt32 value) : this(new Scalar() { AsUInt32 = value }, sizeof(UInt32)) { }
+ private PropertyValue(Int64 value) : this(new Scalar() { AsInt64 = value }, sizeof(Int64)) { }
+ private PropertyValue(UInt64 value) : this(new Scalar() { AsUInt64 = value }, sizeof(UInt64)) { }
+ private PropertyValue(IntPtr value) : this(new Scalar() { AsIntPtr = value }, sizeof(IntPtr)) { }
+ private PropertyValue(UIntPtr value) : this(new Scalar() { AsUIntPtr = value }, sizeof(UIntPtr)) { }
+ private PropertyValue(Single value) : this(new Scalar() { AsSingle = value }, sizeof(Single)) { }
+ private PropertyValue(Double value) : this(new Scalar() { AsDouble = value }, sizeof(Double)) { }
+ private PropertyValue(Guid value) : this(new Scalar() { AsGuid = value }, sizeof(Guid)) { }
+ private PropertyValue(DateTime value) : this(new Scalar() { AsDateTime = value }, sizeof(DateTime)) { }
+ private PropertyValue(DateTimeOffset value) : this(new Scalar() { AsDateTimeOffset = value }, sizeof(DateTimeOffset)) { }
+ private PropertyValue(TimeSpan value) : this(new Scalar() { AsTimeSpan = value }, sizeof(TimeSpan)) { }
+ private PropertyValue(Decimal value) : this(new Scalar() { AsDecimal = value }, sizeof(Decimal)) { }
+
+ public static Func<object, PropertyValue> GetFactory(Type type)
+ {
+ if (type == typeof(Boolean)) return value => new PropertyValue((Boolean)value);
+ if (type == typeof(Byte)) return value => new PropertyValue((Byte)value);
+ if (type == typeof(SByte)) return value => new PropertyValue((SByte)value);
+ if (type == typeof(Char)) return value => new PropertyValue((Char)value);
+ if (type == typeof(Int16)) return value => new PropertyValue((Int16)value);
+ if (type == typeof(UInt16)) return value => new PropertyValue((UInt16)value);
+ if (type == typeof(Int32)) return value => new PropertyValue((Int32)value);
+ if (type == typeof(UInt32)) return value => new PropertyValue((UInt32)value);
+ if (type == typeof(Int64)) return value => new PropertyValue((Int64)value);
+ if (type == typeof(UInt64)) return value => new PropertyValue((UInt64)value);
+ if (type == typeof(IntPtr)) return value => new PropertyValue((IntPtr)value);
+ if (type == typeof(UIntPtr)) return value => new PropertyValue((UIntPtr)value);
+ if (type == typeof(Single)) return value => new PropertyValue((Single)value);
+ if (type == typeof(Double)) return value => new PropertyValue((Double)value);
+ if (type == typeof(Guid)) return value => new PropertyValue((Guid)value);
+ if (type == typeof(DateTime)) return value => new PropertyValue((DateTime)value);
+ if (type == typeof(DateTimeOffset)) return value => new PropertyValue((DateTimeOffset)value);
+ if (type == typeof(TimeSpan)) return value => new PropertyValue((TimeSpan)value);
+ if (type == typeof(Decimal)) return value => new PropertyValue((Decimal)value);
+
+ return value => new PropertyValue(value);
+ }
+
+
+ public object ReferenceValue
+ {
+ get
+ {
+ Debug.Assert(_scalarLength == 0, "This ReflectedValue refers to an unboxed value type, not a reference type or boxed value type.");
+ return _reference;
+ }
+ }
+
+ public Scalar ScalarValue
+ {
+ get
+ {
+ Debug.Assert(_scalarLength > 0, "This ReflectedValue refers to a reference type or boxed value type, not an unboxed value type");
+ return _scalar;
+ }
+ }
+
+ public int ScalarLength
+ {
+ get
+ {
+ Debug.Assert(_scalarLength > 0, "This ReflectedValue refers to a reference type or boxed value type, not an unboxed value type");
+ return _scalarLength;
+ }
+ }
+
+ /// <summary>
+ /// Gets a delegate that gets the value of a given property.
+ /// </summary>
+ public static Func<PropertyValue, PropertyValue> GetPropertyGetter(PropertyInfo property)
+ {
+ if (property.DeclaringType.GetTypeInfo().IsValueType)
+ return GetBoxedValueTypePropertyGetter(property);
+ else
+ return GetReferenceTypePropertyGetter(property);
+ }
+
+ /// <summary>
+ /// Gets a delegate that gets the value of a property of a value type. We unfortunately cannot avoid boxing the value type,
+ /// without making this generic over the value type. That would result in a large number of generic instantiations, and furthermore
+ /// does not work correctly on .Net Native (we cannot express the needed instantiations in an rd.xml file). We expect that user-defined
+ /// value types will be rare, and in any case the boxing only happens for events that are actually enabled.
+ /// </summary>
+ private static Func<PropertyValue, PropertyValue> GetBoxedValueTypePropertyGetter(PropertyInfo property)
+ {
+ var type = property.PropertyType;
+
+ if (type.GetTypeInfo().IsEnum)
+ type = Enum.GetUnderlyingType(type);
+
+ var factory = GetFactory(type);
+
+ return container => factory(property.GetValue(container.ReferenceValue));
+ }
+
+ /// <summary>
+ /// For properties of reference types, we use a generic helper class to get the value. This enables us to use MethodInfo.CreateDelegate
+ /// to build a fast getter. We can get away with this on .Net Native, because we really only need one runtime instantiation of the
+ /// generic type, since it's only instantiated over reference types (and thus all instances are shared).
+ /// </summary>
+ /// <param name="property"></param>
+ /// <returns></returns>
+ private static Func<PropertyValue, PropertyValue> GetReferenceTypePropertyGetter(PropertyInfo property)
+ {
+ var helper = (TypeHelper)Activator.CreateInstance(typeof(ReferenceTypeHelper<>).MakeGenericType(property.DeclaringType));
+ return helper.GetPropertyGetter(property);
+ }
+
+#if ES_BUILD_PN
+ public
+#else
+ private
+#endif
+ abstract class TypeHelper
+ {
+ public abstract Func<PropertyValue, PropertyValue> GetPropertyGetter(PropertyInfo property);
+
+ protected Delegate GetGetMethod(PropertyInfo property, Type propertyType)
+ {
+ return property.GetMethod.CreateDelegate(typeof(Func<,>).MakeGenericType(property.DeclaringType, propertyType));
+ }
+ }
+
+#if ES_BUILD_PN
+ public
+#else
+ private
+#endif
+ sealed class ReferenceTypeHelper<TContainer> : TypeHelper where TContainer : class
+ {
+ public override Func<PropertyValue, PropertyValue> GetPropertyGetter(PropertyInfo property)
+ {
+ var type = property.PropertyType;
+
+ if (!Statics.IsValueType(type))
+ {
+ var getter = (Func<TContainer, object>)GetGetMethod(property, type);
+ return container => new PropertyValue(getter((TContainer)container.ReferenceValue));
+ }
+ else
+ {
+ if (type.GetTypeInfo().IsEnum)
+ type = Enum.GetUnderlyingType(type);
+
+ if (type == typeof(Boolean)) { var f = (Func<TContainer, Boolean>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Byte)) { var f = (Func<TContainer, Byte>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(SByte)) { var f = (Func<TContainer, SByte>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Char)) { var f = (Func<TContainer, Char>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Int16)) { var f = (Func<TContainer, Int16>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(UInt16)) { var f = (Func<TContainer, UInt16>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Int32)) { var f = (Func<TContainer, Int32>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(UInt32)) { var f = (Func<TContainer, UInt32>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Int64)) { var f = (Func<TContainer, Int64>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(UInt64)) { var f = (Func<TContainer, UInt64>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(IntPtr)) { var f = (Func<TContainer, IntPtr>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(UIntPtr)) { var f = (Func<TContainer, UIntPtr>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Single)) { var f = (Func<TContainer, Single>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Double)) { var f = (Func<TContainer, Double>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Guid)) { var f = (Func<TContainer, Guid>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(DateTime)) { var f = (Func<TContainer, DateTime>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(DateTimeOffset)) { var f = (Func<TContainer, DateTimeOffset>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(TimeSpan)) { var f = (Func<TContainer, TimeSpan>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+ if (type == typeof(Decimal)) { var f = (Func<TContainer, Decimal>)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); }
+
+ return container => new PropertyValue(property.GetValue(container.ReferenceValue));
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs
new file mode 100644
index 0000000000..cdced968f0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Interlocked = System.Threading.Interlocked;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Contains the metadata needed to emit an event, optimized
+ /// for events with one top-level compile-time-typed payload object.
+ /// </summary>
+ /// <typeparam name="T">
+ /// Type of the top-level payload object. Should be EmptyStruct if the
+ /// event has no payload.
+ /// </typeparam>
+ internal static class SimpleEventTypes<T>
+ {
+ private static TraceLoggingEventTypes instance;
+
+ public static TraceLoggingEventTypes Instance
+ {
+ get { return instance ?? InitInstance(); }
+ }
+
+ private static TraceLoggingEventTypes InitInstance()
+ {
+ var info = TraceLoggingTypeInfo.GetInstance(typeof(T), null);
+ var newInstance = new TraceLoggingEventTypes(info.Name, info.Tags, new TraceLoggingTypeInfo[] { info });
+ Interlocked.CompareExchange(ref instance, newInstance, null);
+ return instance;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs
new file mode 100644
index 0000000000..001a8e8f05
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs
@@ -0,0 +1,303 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Diagnostics;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Type handler for empty or unsupported types.
+ /// </summary>
+ internal sealed class NullTypeInfo : TraceLoggingTypeInfo
+ {
+ public NullTypeInfo() : base(typeof(EmptyStruct)) { }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ collector.AddGroup(name);
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ return;
+ }
+
+ public override object GetData(object value)
+ {
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Type handler for simple scalar types.
+ /// </summary>
+ sealed class ScalarTypeInfo : TraceLoggingTypeInfo
+ {
+ Func<EventFieldFormat, TraceLoggingDataType, TraceLoggingDataType> formatFunc;
+ TraceLoggingDataType nativeFormat;
+
+ private ScalarTypeInfo(
+ Type type,
+ Func<EventFieldFormat, TraceLoggingDataType, TraceLoggingDataType> formatFunc,
+ TraceLoggingDataType nativeFormat)
+ : base(type)
+ {
+ this.formatFunc = formatFunc;
+ this.nativeFormat = nativeFormat;
+ }
+
+ public override void WriteMetadata(TraceLoggingMetadataCollector collector, string name, EventFieldFormat format)
+ {
+ collector.AddScalar(name, formatFunc(format, nativeFormat));
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ collector.AddScalar(value);
+ }
+
+ public static TraceLoggingTypeInfo Boolean() { return new ScalarTypeInfo(typeof(Boolean), Statics.Format8, TraceLoggingDataType.Boolean8); }
+ public static TraceLoggingTypeInfo Byte() { return new ScalarTypeInfo(typeof(Byte), Statics.Format8, TraceLoggingDataType.UInt8); }
+ public static TraceLoggingTypeInfo SByte() { return new ScalarTypeInfo(typeof(SByte), Statics.Format8, TraceLoggingDataType.Int8); }
+ public static TraceLoggingTypeInfo Char() { return new ScalarTypeInfo(typeof(Char), Statics.Format16, TraceLoggingDataType.Char16); }
+ public static TraceLoggingTypeInfo Int16() { return new ScalarTypeInfo(typeof(Int16), Statics.Format16, TraceLoggingDataType.Int16); }
+ public static TraceLoggingTypeInfo UInt16() { return new ScalarTypeInfo(typeof(UInt16), Statics.Format16, TraceLoggingDataType.UInt16); }
+ public static TraceLoggingTypeInfo Int32() { return new ScalarTypeInfo(typeof(Int32), Statics.Format32, TraceLoggingDataType.Int32); }
+ public static TraceLoggingTypeInfo UInt32() { return new ScalarTypeInfo(typeof(UInt32), Statics.Format32, TraceLoggingDataType.UInt32); }
+ public static TraceLoggingTypeInfo Int64() { return new ScalarTypeInfo(typeof(Int64), Statics.Format64, TraceLoggingDataType.Int64); }
+ public static TraceLoggingTypeInfo UInt64() { return new ScalarTypeInfo(typeof(UInt64), Statics.Format64, TraceLoggingDataType.UInt64); }
+ public static TraceLoggingTypeInfo IntPtr() { return new ScalarTypeInfo(typeof(IntPtr), Statics.FormatPtr, Statics.IntPtrType); }
+ public static TraceLoggingTypeInfo UIntPtr() { return new ScalarTypeInfo(typeof(UIntPtr), Statics.FormatPtr, Statics.UIntPtrType); }
+ public static TraceLoggingTypeInfo Single() { return new ScalarTypeInfo(typeof(Single), Statics.Format32, TraceLoggingDataType.Float); }
+ public static TraceLoggingTypeInfo Double() { return new ScalarTypeInfo(typeof(Double), Statics.Format64, TraceLoggingDataType.Double); }
+ public static TraceLoggingTypeInfo Guid() { return new ScalarTypeInfo(typeof(Guid), (f, t) => Statics.MakeDataType(TraceLoggingDataType.Guid, f), TraceLoggingDataType.Guid); }
+ }
+
+
+ /// <summary>
+ /// Type handler for arrays of scalars
+ /// </summary>
+ internal sealed class ScalarArrayTypeInfo : TraceLoggingTypeInfo
+ {
+ Func<EventFieldFormat, TraceLoggingDataType, TraceLoggingDataType> formatFunc;
+ TraceLoggingDataType nativeFormat;
+ int elementSize;
+
+ private ScalarArrayTypeInfo(
+ Type type,
+ Func<EventFieldFormat, TraceLoggingDataType, TraceLoggingDataType> formatFunc,
+ TraceLoggingDataType nativeFormat,
+ int elementSize)
+ : base(type)
+ {
+ this.formatFunc = formatFunc;
+ this.nativeFormat = nativeFormat;
+ this.elementSize = elementSize;
+ }
+
+ public override void WriteMetadata(TraceLoggingMetadataCollector collector, string name, EventFieldFormat format)
+ {
+ collector.AddArray(name, formatFunc(format, nativeFormat));
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ collector.AddArray(value, elementSize);
+ }
+
+ public static TraceLoggingTypeInfo Boolean() { return new ScalarArrayTypeInfo(typeof(Boolean[]), Statics.Format8, TraceLoggingDataType.Boolean8, sizeof(Boolean)); }
+ public static TraceLoggingTypeInfo Byte() { return new ScalarArrayTypeInfo(typeof(Byte[]), Statics.Format8, TraceLoggingDataType.UInt8, sizeof(Byte)); }
+ public static TraceLoggingTypeInfo SByte() { return new ScalarArrayTypeInfo(typeof(SByte[]), Statics.Format8, TraceLoggingDataType.Int8, sizeof(SByte)); }
+ public static TraceLoggingTypeInfo Char() { return new ScalarArrayTypeInfo(typeof(Char[]), Statics.Format16, TraceLoggingDataType.Char16, sizeof(Char)); }
+ public static TraceLoggingTypeInfo Int16() { return new ScalarArrayTypeInfo(typeof(Int16[]), Statics.Format16, TraceLoggingDataType.Int16, sizeof(Int16)); }
+ public static TraceLoggingTypeInfo UInt16() { return new ScalarArrayTypeInfo(typeof(UInt16[]), Statics.Format16, TraceLoggingDataType.UInt16, sizeof(UInt16)); }
+ public static TraceLoggingTypeInfo Int32() { return new ScalarArrayTypeInfo(typeof(Int32[]), Statics.Format32, TraceLoggingDataType.Int32, sizeof(Int32)); }
+ public static TraceLoggingTypeInfo UInt32() { return new ScalarArrayTypeInfo(typeof(UInt32[]), Statics.Format32, TraceLoggingDataType.UInt32, sizeof(UInt32)); }
+ public static TraceLoggingTypeInfo Int64() { return new ScalarArrayTypeInfo(typeof(Int64[]), Statics.Format64, TraceLoggingDataType.Int64, sizeof(Int64)); }
+ public static TraceLoggingTypeInfo UInt64() { return new ScalarArrayTypeInfo(typeof(UInt64[]), Statics.Format64, TraceLoggingDataType.UInt64, sizeof(UInt64)); }
+ public static TraceLoggingTypeInfo IntPtr() { return new ScalarArrayTypeInfo(typeof(IntPtr[]), Statics.FormatPtr, Statics.IntPtrType, System.IntPtr.Size); }
+ public static TraceLoggingTypeInfo UIntPtr() { return new ScalarArrayTypeInfo(typeof(UIntPtr[]), Statics.FormatPtr, Statics.UIntPtrType, System.IntPtr.Size); }
+ public static TraceLoggingTypeInfo Single() { return new ScalarArrayTypeInfo(typeof(Single[]), Statics.Format32, TraceLoggingDataType.Float, sizeof(Single)); }
+ public static TraceLoggingTypeInfo Double() { return new ScalarArrayTypeInfo(typeof(Double[]), Statics.Format64, TraceLoggingDataType.Double, sizeof(Double)); }
+ public static unsafe TraceLoggingTypeInfo Guid() { return new ScalarArrayTypeInfo(typeof(Guid), (f, t) => Statics.MakeDataType(TraceLoggingDataType.Guid, f), TraceLoggingDataType.Guid, sizeof(Guid)); }
+ }
+
+ /// <summary>
+ /// TraceLogging: Type handler for String.
+ /// </summary>
+ internal sealed class StringTypeInfo : TraceLoggingTypeInfo
+ {
+ public StringTypeInfo() : base(typeof(string)) { }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ collector.AddNullTerminatedString(name, Statics.MakeDataType(TraceLoggingDataType.Utf16String, format));
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ collector.AddNullTerminatedString((string)value.ReferenceValue);
+ }
+
+ public override object GetData(object value)
+ {
+ if(value == null)
+ {
+ return "";
+ }
+
+ return value;
+ }
+ }
+
+ /// <summary>
+ /// TraceLogging: Type handler for DateTime.
+ /// </summary>
+ internal sealed class DateTimeTypeInfo : TraceLoggingTypeInfo
+ {
+ public DateTimeTypeInfo() : base(typeof(DateTime)) { }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ collector.AddScalar(name, Statics.MakeDataType(TraceLoggingDataType.FileTime, format));
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ DateTime dateTime = value.ScalarValue.AsDateTime;
+ const long UTCMinTicks = 504911232000000000;
+ long dateTimeTicks = 0;
+ // We cannot translate dates sooner than 1/1/1601 in UTC.
+ // To avoid getting an ArgumentOutOfRangeException we compare with 1/1/1601 DateTime ticks
+ if (dateTime.Ticks > UTCMinTicks)
+ dateTimeTicks = dateTime.ToFileTimeUtc();
+ collector.AddScalar(dateTimeTicks);
+ }
+ }
+
+ /// <summary>
+ /// TraceLogging: Type handler for DateTimeOffset.
+ /// </summary>
+ internal sealed class DateTimeOffsetTypeInfo : TraceLoggingTypeInfo
+ {
+ public DateTimeOffsetTypeInfo() : base(typeof(DateTimeOffset)) { }
+
+ public override void WriteMetadata(TraceLoggingMetadataCollector collector, string name, EventFieldFormat format)
+ {
+ var group = collector.AddGroup(name);
+ group.AddScalar("Ticks", Statics.MakeDataType(TraceLoggingDataType.FileTime, format));
+ group.AddScalar("Offset", TraceLoggingDataType.Int64);
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ var dateTimeOffset = value.ScalarValue.AsDateTimeOffset;
+ var ticks = dateTimeOffset.Ticks;
+ collector.AddScalar(ticks < 504911232000000000 ? 0 : ticks - 504911232000000000);
+ collector.AddScalar(dateTimeOffset.Offset.Ticks);
+ }
+ }
+
+ /// <summary>
+ /// TraceLogging: Type handler for TimeSpan.
+ /// </summary>
+ internal sealed class TimeSpanTypeInfo : TraceLoggingTypeInfo
+ {
+ public TimeSpanTypeInfo() : base(typeof(TimeSpan)) { }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ collector.AddScalar(name, Statics.MakeDataType(TraceLoggingDataType.Int64, format));
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ collector.AddScalar(value.ScalarValue.AsTimeSpan.Ticks);
+ }
+ }
+
+ /// <summary>
+ /// TraceLogging: Type handler for Decimal. (Note: not full-fidelity, exposed as Double.)
+ /// </summary>
+ internal sealed class DecimalTypeInfo : TraceLoggingTypeInfo
+ {
+ public DecimalTypeInfo() : base(typeof(Decimal)) { }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ collector.AddScalar(name, Statics.MakeDataType(TraceLoggingDataType.Double, format));
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ collector.AddScalar((double)value.ScalarValue.AsDecimal);
+ }
+ }
+
+ /// <summary>
+ /// TraceLogging: Type handler for Nullable.
+ /// </summary>
+ internal sealed class NullableTypeInfo : TraceLoggingTypeInfo
+ {
+ private readonly TraceLoggingTypeInfo valueInfo;
+ private readonly Func<PropertyValue, PropertyValue> hasValueGetter;
+ private readonly Func<PropertyValue, PropertyValue> valueGetter;
+
+ public NullableTypeInfo(Type type, List<Type> recursionCheck)
+ : base(type)
+ {
+ var typeArgs = type.GenericTypeArguments;
+ Debug.Assert(typeArgs.Length == 1);
+ this.valueInfo = TraceLoggingTypeInfo.GetInstance(typeArgs[0], recursionCheck);
+ this.hasValueGetter = PropertyValue.GetPropertyGetter(type.GetTypeInfo().GetDeclaredProperty("HasValue"));
+ this.valueGetter = PropertyValue.GetPropertyGetter(type.GetTypeInfo().GetDeclaredProperty("Value"));
+ }
+
+ public override void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format)
+ {
+ var group = collector.AddGroup(name);
+ group.AddScalar("HasValue", TraceLoggingDataType.Boolean8);
+ this.valueInfo.WriteMetadata(group, "Value", format);
+ }
+
+ public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value)
+ {
+ var hasValue = hasValueGetter(value);
+ collector.AddScalar(hasValue);
+ var val = hasValue.ScalarValue.AsBoolean ? valueGetter(value) : valueInfo.PropertyValueFactory(Activator.CreateInstance(valueInfo.DataType));
+ this.valueInfo.WriteData(collector, val);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs
new file mode 100644
index 0000000000..05539ab4fd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs
@@ -0,0 +1,728 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using Encoding = System.Text.Encoding;
+
+using Microsoft.Reflection;
+
+#if ES_BUILD_STANDALONE
+using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment;
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Constants and utility functions.
+ /// </summary>
+ internal static class Statics
+ {
+ #region Constants
+
+ public const byte DefaultLevel = 5;
+ public const byte TraceLoggingChannel = 0xb;
+ public const byte InTypeMask = 31;
+ public const byte InTypeFixedCountFlag = 32;
+ public const byte InTypeVariableCountFlag = 64;
+ public const byte InTypeCustomCountFlag = 96;
+ public const byte InTypeCountMask = 96;
+ public const byte InTypeChainFlag = 128;
+ public const byte OutTypeMask = 127;
+ public const byte OutTypeChainFlag = 128;
+ public const EventTags EventTagsMask = (EventTags)0xfffffff;
+
+ public static readonly TraceLoggingDataType IntPtrType = IntPtr.Size == 8
+ ? TraceLoggingDataType.Int64
+ : TraceLoggingDataType.Int32;
+ public static readonly TraceLoggingDataType UIntPtrType = IntPtr.Size == 8
+ ? TraceLoggingDataType.UInt64
+ : TraceLoggingDataType.UInt32;
+ public static readonly TraceLoggingDataType HexIntPtrType = IntPtr.Size == 8
+ ? TraceLoggingDataType.HexInt64
+ : TraceLoggingDataType.HexInt32;
+
+ #endregion
+
+ #region Metadata helpers
+
+ /// <summary>
+ /// A complete metadata chunk can be expressed as:
+ /// length16 + prefix + null-terminated-utf8-name + suffix + additionalData.
+ /// We assume that excludedData will be provided by some other means,
+ /// but that its size is known. This function returns a blob containing
+ /// length16 + prefix + name + suffix, with prefix and suffix initialized
+ /// to 0's. The length16 value is initialized to the length of the returned
+ /// blob plus additionalSize, so that the concatenation of the returned blob
+ /// plus a blob of size additionalSize constitutes a valid metadata blob.
+ /// </summary>
+ /// <param name="name">
+ /// The name to include in the blob.
+ /// </param>
+ /// <param name="prefixSize">
+ /// Amount of space to reserve before name. For provider or field blobs, this
+ /// should be 0. For event blobs, this is used for the tags field and will vary
+ /// from 1 to 4, depending on how large the tags field needs to be.
+ /// </param>
+ /// <param name="suffixSize">
+ /// Amount of space to reserve after name. For example, a provider blob with no
+ /// traits would reserve 0 extra bytes, but a provider blob with a single GroupId
+ /// field would reserve 19 extra bytes.
+ /// </param>
+ /// <param name="additionalSize">
+ /// Amount of additional data in another blob. This value will be counted in the
+ /// blob's length field, but will not be included in the returned byte[] object.
+ /// The complete blob would then be the concatenation of the returned byte[] object
+ /// with another byte[] object of length additionalSize.
+ /// </param>
+ /// <returns>
+ /// A byte[] object with the length and name fields set, with room reserved for
+ /// prefix and suffix. If additionalSize was 0, the byte[] object is a complete
+ /// blob. Otherwise, another byte[] of size additionalSize must be concatenated
+ /// with this one to form a complete blob.
+ /// </returns>
+ public static byte[] MetadataForString(
+ string name,
+ int prefixSize,
+ int suffixSize,
+ int additionalSize)
+ {
+ Statics.CheckName(name);
+ int metadataSize = Encoding.UTF8.GetByteCount(name) + 3 + prefixSize + suffixSize;
+ var metadata = new byte[metadataSize];
+ ushort totalSize = checked((ushort)(metadataSize + additionalSize));
+ metadata[0] = unchecked((byte)totalSize);
+ metadata[1] = unchecked((byte)(totalSize >> 8));
+ Encoding.UTF8.GetBytes(name, 0, name.Length, metadata, 2 + prefixSize);
+ return metadata;
+ }
+
+ /// <summary>
+ /// Serialize the low 28 bits of the tags value into the metadata stream,
+ /// starting at the index given by pos. Updates pos. Writes 1 to 4 bytes,
+ /// depending on the value of the tags variable. Usable for event tags and
+ /// field tags.
+ ///
+ /// Note that 'metadata' can be null, in which case it only updates 'pos'.
+ /// This is useful for a two pass approach where you figure out how big to
+ /// make the array, and then you fill it in.
+ /// </summary>
+ public static void EncodeTags(int tags, ref int pos, byte[] metadata)
+ {
+ // We transmit the low 28 bits of tags, high bits first, 7 bits at a time.
+ var tagsLeft = tags & 0xfffffff;
+ bool more;
+ do
+ {
+ byte current = (byte)((tagsLeft >> 21) & 0x7f);
+ more = (tagsLeft & 0x1fffff) != 0;
+ current |= (byte)(more ? 0x80 : 0x00);
+ tagsLeft = tagsLeft << 7;
+
+ if (metadata != null)
+ {
+ metadata[pos] = current;
+ }
+ pos += 1;
+ }
+ while (more);
+ }
+
+ public static byte Combine(
+ int settingValue,
+ byte defaultValue)
+ {
+ unchecked
+ {
+ return (byte)settingValue == settingValue
+ ? (byte)settingValue
+ : defaultValue;
+ }
+ }
+
+ public static byte Combine(
+ int settingValue1,
+ int settingValue2,
+ byte defaultValue)
+ {
+ unchecked
+ {
+ return (byte)settingValue1 == settingValue1
+ ? (byte)settingValue1
+ : (byte)settingValue2 == settingValue2
+ ? (byte)settingValue2
+ : defaultValue;
+ }
+ }
+
+ public static int Combine(
+ int settingValue1,
+ int settingValue2)
+ {
+ unchecked
+ {
+ return (byte)settingValue1 == settingValue1
+ ? settingValue1
+ : settingValue2;
+ }
+ }
+
+ public static void CheckName(string name)
+ {
+ if (name != null && 0 <= name.IndexOf('\0'))
+ {
+ throw new ArgumentOutOfRangeException(nameof(name));
+ }
+ }
+
+ public static bool ShouldOverrideFieldName(string fieldName)
+ {
+ return (fieldName.Length <= 2 && fieldName[0] == '_');
+ }
+
+ public static TraceLoggingDataType MakeDataType(
+ TraceLoggingDataType baseType,
+ EventFieldFormat format)
+ {
+ return (TraceLoggingDataType)(((int)baseType & 0x1f) | ((int)format << 8));
+ }
+
+ /// <summary>
+ /// Adjusts the native type based on format.
+ /// - If format is default, return native.
+ /// - If format is recognized, return the canonical type for that format.
+ /// - Otherwise remove existing format from native and apply the requested format.
+ /// </summary>
+ public static TraceLoggingDataType Format8(
+ EventFieldFormat format,
+ TraceLoggingDataType native)
+ {
+ switch (format)
+ {
+ case EventFieldFormat.Default:
+ return native;
+ case EventFieldFormat.String:
+ return TraceLoggingDataType.Char8;
+ case EventFieldFormat.Boolean:
+ return TraceLoggingDataType.Boolean8;
+ case EventFieldFormat.Hexadecimal:
+ return TraceLoggingDataType.HexInt8;
+#if false
+ case EventSourceFieldFormat.Signed:
+ return TraceLoggingDataType.Int8;
+ case EventSourceFieldFormat.Unsigned:
+ return TraceLoggingDataType.UInt8;
+#endif
+ default:
+ return MakeDataType(native, format);
+ }
+ }
+
+ /// <summary>
+ /// Adjusts the native type based on format.
+ /// - If format is default, return native.
+ /// - If format is recognized, return the canonical type for that format.
+ /// - Otherwise remove existing format from native and apply the requested format.
+ /// </summary>
+ public static TraceLoggingDataType Format16(
+ EventFieldFormat format,
+ TraceLoggingDataType native)
+ {
+ switch (format)
+ {
+ case EventFieldFormat.Default:
+ return native;
+ case EventFieldFormat.String:
+ return TraceLoggingDataType.Char16;
+ case EventFieldFormat.Hexadecimal:
+ return TraceLoggingDataType.HexInt16;
+#if false
+ case EventSourceFieldFormat.Port:
+ return TraceLoggingDataType.Port;
+ case EventSourceFieldFormat.Signed:
+ return TraceLoggingDataType.Int16;
+ case EventSourceFieldFormat.Unsigned:
+ return TraceLoggingDataType.UInt16;
+#endif
+ default:
+ return MakeDataType(native, format);
+ }
+ }
+
+ /// <summary>
+ /// Adjusts the native type based on format.
+ /// - If format is default, return native.
+ /// - If format is recognized, return the canonical type for that format.
+ /// - Otherwise remove existing format from native and apply the requested format.
+ /// </summary>
+ public static TraceLoggingDataType Format32(
+ EventFieldFormat format,
+ TraceLoggingDataType native)
+ {
+ switch (format)
+ {
+ case EventFieldFormat.Default:
+ return native;
+ case EventFieldFormat.Boolean:
+ return TraceLoggingDataType.Boolean32;
+ case EventFieldFormat.Hexadecimal:
+ return TraceLoggingDataType.HexInt32;
+#if false
+ case EventSourceFieldFormat.Ipv4Address:
+ return TraceLoggingDataType.Ipv4Address;
+ case EventSourceFieldFormat.ProcessId:
+ return TraceLoggingDataType.ProcessId;
+ case EventSourceFieldFormat.ThreadId:
+ return TraceLoggingDataType.ThreadId;
+ case EventSourceFieldFormat.Win32Error:
+ return TraceLoggingDataType.Win32Error;
+ case EventSourceFieldFormat.NTStatus:
+ return TraceLoggingDataType.NTStatus;
+#endif
+ case EventFieldFormat.HResult:
+ return TraceLoggingDataType.HResult;
+#if false
+ case EventSourceFieldFormat.Signed:
+ return TraceLoggingDataType.Int32;
+ case EventSourceFieldFormat.Unsigned:
+ return TraceLoggingDataType.UInt32;
+#endif
+ default:
+ return MakeDataType(native, format);
+ }
+ }
+
+ /// <summary>
+ /// Adjusts the native type based on format.
+ /// - If format is default, return native.
+ /// - If format is recognized, return the canonical type for that format.
+ /// - Otherwise remove existing format from native and apply the requested format.
+ /// </summary>
+ public static TraceLoggingDataType Format64(
+ EventFieldFormat format,
+ TraceLoggingDataType native)
+ {
+ switch (format)
+ {
+ case EventFieldFormat.Default:
+ return native;
+ case EventFieldFormat.Hexadecimal:
+ return TraceLoggingDataType.HexInt64;
+#if false
+ case EventSourceFieldFormat.FileTime:
+ return TraceLoggingDataType.FileTime;
+ case EventSourceFieldFormat.Signed:
+ return TraceLoggingDataType.Int64;
+ case EventSourceFieldFormat.Unsigned:
+ return TraceLoggingDataType.UInt64;
+#endif
+ default:
+ return MakeDataType(native, format);
+ }
+ }
+
+ /// <summary>
+ /// Adjusts the native type based on format.
+ /// - If format is default, return native.
+ /// - If format is recognized, return the canonical type for that format.
+ /// - Otherwise remove existing format from native and apply the requested format.
+ /// </summary>
+ public static TraceLoggingDataType FormatPtr(
+ EventFieldFormat format,
+ TraceLoggingDataType native)
+ {
+ switch (format)
+ {
+ case EventFieldFormat.Default:
+ return native;
+ case EventFieldFormat.Hexadecimal:
+ return HexIntPtrType;
+#if false
+ case EventSourceFieldFormat.Signed:
+ return IntPtrType;
+ case EventSourceFieldFormat.Unsigned:
+ return UIntPtrType;
+#endif
+ default:
+ return MakeDataType(native, format);
+ }
+ }
+
+ #endregion
+
+ #region Reflection helpers
+
+ /*
+ All TraceLogging use of reflection APIs should go through wrappers here.
+ This helps with portability, and it also makes it easier to audit what
+ kinds of reflection operations are being done.
+ */
+
+ public static object CreateInstance(Type type, params object[] parameters)
+ {
+ return Activator.CreateInstance(type, parameters);
+ }
+
+ public static bool IsValueType(Type type)
+ {
+ bool result = type.IsValueType();
+ return result;
+ }
+
+ public static bool IsEnum(Type type)
+ {
+ bool result = type.IsEnum();
+ return result;
+ }
+
+ public static IEnumerable<PropertyInfo> GetProperties(Type type)
+ {
+ IEnumerable<PropertyInfo> result = type.GetProperties();
+ return result;
+ }
+
+ public static MethodInfo GetGetMethod(PropertyInfo propInfo)
+ {
+ MethodInfo result = propInfo.GetGetMethod();
+ return result;
+ }
+
+ public static MethodInfo GetDeclaredStaticMethod(Type declaringType, string name)
+ {
+ MethodInfo result;
+#if (ES_BUILD_PCL || ES_BUILD_PN)
+ result = declaringType.GetTypeInfo().GetDeclaredMethod(name);
+#else
+ result = declaringType.GetMethod(
+ name,
+ BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.NonPublic);
+#endif
+ return result;
+ }
+
+ public static bool HasCustomAttribute(
+ PropertyInfo propInfo,
+ Type attributeType)
+ {
+ bool result;
+#if (ES_BUILD_PCL || ES_BUILD_PN)
+ result = propInfo.IsDefined(attributeType);
+#else
+ var attributes = propInfo.GetCustomAttributes(
+ attributeType,
+ false);
+ result = attributes.Length != 0;
+#endif
+ return result;
+ }
+
+ public static AttributeType GetCustomAttribute<AttributeType>(PropertyInfo propInfo)
+ where AttributeType : Attribute
+ {
+ AttributeType result = null;
+#if (ES_BUILD_PCL || ES_BUILD_PN)
+ foreach (var attrib in propInfo.GetCustomAttributes<AttributeType>(false))
+ {
+ result = attrib;
+ break;
+ }
+#else
+ var attributes = propInfo.GetCustomAttributes(typeof(AttributeType), false);
+ if (attributes.Length != 0)
+ {
+ result = (AttributeType)attributes[0];
+ }
+#endif
+ return result;
+ }
+
+ public static AttributeType GetCustomAttribute<AttributeType>(Type type)
+ where AttributeType : Attribute
+ {
+ AttributeType result = null;
+#if (ES_BUILD_PCL || ES_BUILD_PN)
+ foreach (var attrib in type.GetTypeInfo().GetCustomAttributes<AttributeType>(false))
+ {
+ result = attrib;
+ break;
+ }
+#else
+ var attributes = type.GetCustomAttributes(typeof(AttributeType), false);
+ if (attributes.Length != 0)
+ {
+ result = (AttributeType)attributes[0];
+ }
+#endif
+ return result;
+ }
+
+ public static Type[] GetGenericArguments(Type type)
+ {
+ return type.GetGenericArguments();
+ }
+
+ public static Type FindEnumerableElementType(Type type)
+ {
+ Type elementType = null;
+
+ if (IsGenericMatch(type, typeof(IEnumerable<>)))
+ {
+ elementType = GetGenericArguments(type)[0];
+ }
+ else
+ {
+#if (ES_BUILD_PCL || ES_BUILD_PN)
+ var ifaceTypes = type.GetTypeInfo().ImplementedInterfaces;
+#else
+ var ifaceTypes = type.FindInterfaces(IsGenericMatch, typeof(IEnumerable<>));
+#endif
+
+ foreach (var ifaceType in ifaceTypes)
+ {
+#if (ES_BUILD_PCL || ES_BUILD_PN)
+ if (!IsGenericMatch(ifaceType, typeof(IEnumerable<>)))
+ {
+ continue;
+ }
+#endif
+
+ if (elementType != null)
+ {
+ // ambiguous match. report no match at all.
+ elementType = null;
+ break;
+ }
+
+ elementType = GetGenericArguments(ifaceType)[0];
+ }
+ }
+
+ return elementType;
+ }
+
+ public static bool IsGenericMatch(Type type, object openType)
+ {
+ return type.IsGenericType() && type.GetGenericTypeDefinition() == (Type)openType;
+ }
+
+ public static Delegate CreateDelegate(Type delegateType, MethodInfo methodInfo)
+ {
+ Delegate result;
+#if (ES_BUILD_PCL || ES_BUILD_PN)
+ result = methodInfo.CreateDelegate(
+ delegateType);
+#else
+ result = Delegate.CreateDelegate(
+ delegateType,
+ methodInfo);
+#endif
+ return result;
+ }
+
+ public static TraceLoggingTypeInfo CreateDefaultTypeInfo(
+ Type dataType,
+ List<Type> recursionCheck)
+ {
+ TraceLoggingTypeInfo result;
+
+ if (recursionCheck.Contains(dataType))
+ {
+ throw new NotSupportedException(SR.EventSource_RecursiveTypeDefinition);
+ }
+
+ recursionCheck.Add(dataType);
+
+ var eventAttrib = Statics.GetCustomAttribute<EventDataAttribute>(dataType);
+ if (eventAttrib != null ||
+ Statics.GetCustomAttribute<CompilerGeneratedAttribute>(dataType) != null ||
+ IsGenericMatch(dataType, typeof(KeyValuePair<,>)))
+ {
+ var analysis = new TypeAnalysis(dataType, eventAttrib, recursionCheck);
+ result = new InvokeTypeInfo(dataType, analysis);
+ }
+ else if (dataType.IsArray)
+ {
+ var elementType = dataType.GetElementType();
+ if (elementType == typeof(Boolean))
+ {
+ result = ScalarArrayTypeInfo.Boolean();
+ }
+ else if (elementType == typeof(Byte))
+ {
+ result = ScalarArrayTypeInfo.Byte();
+ }
+ else if (elementType == typeof(SByte))
+ {
+ result = ScalarArrayTypeInfo.SByte();
+ }
+ else if (elementType == typeof(Int16))
+ {
+ result = ScalarArrayTypeInfo.Int16();
+ }
+ else if (elementType == typeof(UInt16))
+ {
+ result = ScalarArrayTypeInfo.UInt16();
+ }
+ else if (elementType == typeof(Int32))
+ {
+ result = ScalarArrayTypeInfo.Int32();
+ }
+ else if (elementType == typeof(UInt32))
+ {
+ result = ScalarArrayTypeInfo.UInt32();
+ }
+ else if (elementType == typeof(Int64))
+ {
+ result = ScalarArrayTypeInfo.Int64();
+ }
+ else if (elementType == typeof(UInt64))
+ {
+ result = ScalarArrayTypeInfo.UInt64();
+ }
+ else if (elementType == typeof(Char))
+ {
+ result = ScalarArrayTypeInfo.Char();
+ }
+ else if (elementType == typeof(Double))
+ {
+ result = ScalarArrayTypeInfo.Double();
+ }
+ else if (elementType == typeof(Single))
+ {
+ result = ScalarArrayTypeInfo.Single();
+ }
+ else if (elementType == typeof(IntPtr))
+ {
+ result = ScalarArrayTypeInfo.IntPtr();
+ }
+ else if (elementType == typeof(UIntPtr))
+ {
+ result = ScalarArrayTypeInfo.UIntPtr();
+ }
+ else if (elementType == typeof(Guid))
+ {
+ result = ScalarArrayTypeInfo.Guid();
+ }
+ else
+ {
+ result = new ArrayTypeInfo(dataType, TraceLoggingTypeInfo.GetInstance(elementType, recursionCheck));
+ }
+ }
+ else
+ {
+ if (Statics.IsEnum(dataType))
+ dataType = Enum.GetUnderlyingType(dataType);
+
+ if (dataType == typeof(String))
+ {
+ result = new StringTypeInfo();
+ }
+ else if (dataType == typeof(Boolean))
+ {
+ result = ScalarTypeInfo.Boolean();
+ }
+ else if (dataType == typeof(Byte))
+ {
+ result = ScalarTypeInfo.Byte();
+ }
+ else if (dataType == typeof(SByte))
+ {
+ result = ScalarTypeInfo.SByte();
+ }
+ else if (dataType == typeof(Int16))
+ {
+ result = ScalarTypeInfo.Int16();
+ }
+ else if (dataType == typeof(UInt16))
+ {
+ result = ScalarTypeInfo.UInt16();
+ }
+ else if (dataType == typeof(Int32))
+ {
+ result = ScalarTypeInfo.Int32();
+ }
+ else if (dataType == typeof(UInt32))
+ {
+ result = ScalarTypeInfo.UInt32();
+ }
+ else if (dataType == typeof(Int64))
+ {
+ result = ScalarTypeInfo.Int64();
+ }
+ else if (dataType == typeof(UInt64))
+ {
+ result = ScalarTypeInfo.UInt64();
+ }
+ else if (dataType == typeof(Char))
+ {
+ result = ScalarTypeInfo.Char();
+ }
+ else if (dataType == typeof(Double))
+ {
+ result = ScalarTypeInfo.Double();
+ }
+ else if (dataType == typeof(Single))
+ {
+ result = ScalarTypeInfo.Single();
+ }
+ else if (dataType == typeof(DateTime))
+ {
+ result = new DateTimeTypeInfo();
+ }
+ else if (dataType == typeof(Decimal))
+ {
+ result = new DecimalTypeInfo();
+ }
+ else if (dataType == typeof(IntPtr))
+ {
+ result = ScalarTypeInfo.IntPtr();
+ }
+ else if (dataType == typeof(UIntPtr))
+ {
+ result = ScalarTypeInfo.UIntPtr();
+ }
+ else if (dataType == typeof(Guid))
+ {
+ result = ScalarTypeInfo.Guid();
+ }
+ else if (dataType == typeof(TimeSpan))
+ {
+ result = new TimeSpanTypeInfo();
+ }
+ else if (dataType == typeof(DateTimeOffset))
+ {
+ result = new DateTimeOffsetTypeInfo();
+ }
+ else if (dataType == typeof(EmptyStruct))
+ {
+ result = new NullTypeInfo();
+ }
+ else if (IsGenericMatch(dataType, typeof(Nullable<>)))
+ {
+ result = new NullableTypeInfo(dataType, recursionCheck);
+ }
+ else
+ {
+ var elementType = FindEnumerableElementType(dataType);
+ if (elementType != null)
+ {
+ result = new EnumerableTypeInfo(dataType, TraceLoggingTypeInfo.GetInstance(elementType, recursionCheck));
+ }
+ else
+ {
+ throw new ArgumentException(SR.Format(SR.EventSource_NonCompliantTypeError, dataType.Name));
+ }
+ }
+ }
+
+ return result;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs
new file mode 100644
index 0000000000..f6d0a59aa6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs
@@ -0,0 +1,115 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Security;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Used when implementing a custom TraceLoggingTypeInfo.
+ /// The instance of this type is provided to the TypeInfo.WriteData method.
+ /// All operations are forwarded to the current thread's DataCollector.
+ /// Note that this abstraction would allow us to expose the custom
+ /// serialization system to partially-trusted code. If we end up not
+ /// making custom serialization public, or if we only expose it to
+ /// full-trust code, this abstraction is unnecessary (though it probably
+ /// doesn't hurt anything).
+ /// </summary>
+ internal unsafe class TraceLoggingDataCollector
+ {
+ internal static readonly TraceLoggingDataCollector Instance = new TraceLoggingDataCollector();
+
+ private TraceLoggingDataCollector()
+ {
+ return;
+ }
+
+ /// <summary>
+ /// Marks the start of a non-blittable array or enumerable.
+ /// </summary>
+ /// <returns>Bookmark to be passed to EndBufferedArray.</returns>
+ public int BeginBufferedArray()
+ {
+ return DataCollector.ThreadInstance.BeginBufferedArray();
+ }
+
+ /// <summary>
+ /// Marks the end of a non-blittable array or enumerable.
+ /// </summary>
+ /// <param name="bookmark">The value returned by BeginBufferedArray.</param>
+ /// <param name="count">The number of items in the array.</param>
+ public void EndBufferedArray(int bookmark, int count)
+ {
+ DataCollector.ThreadInstance.EndBufferedArray(bookmark, count);
+ }
+
+ /// <summary>
+ /// Adds the start of a group to the event.
+ /// This has no effect on the event payload, but is provided to allow
+ /// WriteMetadata and WriteData implementations to have similar
+ /// sequences of calls, allowing for easier verification of correctness.
+ /// </summary>
+ public TraceLoggingDataCollector AddGroup()
+ {
+ return this;
+ }
+
+ public void AddScalar(PropertyValue value)
+ {
+ var scalar = value.ScalarValue;
+ DataCollector.ThreadInstance.AddScalar(&scalar, value.ScalarLength);
+ }
+
+ /// <summary>
+ /// Adds an Int64 value to the event payload.
+ /// </summary>
+ /// <param name="value">Value to be added.</param>
+ public void AddScalar(long value)
+ {
+ DataCollector.ThreadInstance.AddScalar(&value, sizeof(long));
+ }
+
+ /// <summary>
+ /// Adds a Double value to the event payload.
+ /// </summary>
+ /// <param name="value">Value to be added.</param>
+ public void AddScalar(double value)
+ {
+ DataCollector.ThreadInstance.AddScalar(&value, sizeof(double));
+ }
+
+ /// <summary>
+ /// Adds a null-terminated String value to the event payload.
+ /// </summary>
+ /// <param name="value">
+ /// Value to be added. A null value is treated as a zero-length string.
+ /// </param>
+ public void AddNullTerminatedString(string value)
+ {
+ DataCollector.ThreadInstance.AddNullTerminatedString(value);
+ }
+
+ /// <summary>
+ /// Adds a counted String value to the event payload.
+ /// </summary>
+ /// <param name="value">
+ /// Value to be added. A null value is treated as a zero-length string.
+ /// </param>
+ public void AddBinary(string value)
+ {
+ DataCollector.ThreadInstance.AddBinary(value, value == null ? 0 : value.Length * 2);
+ }
+
+ public void AddArray(PropertyValue value, int elementSize)
+ {
+ Array array = (Array)value.ReferenceValue;
+ DataCollector.ThreadInstance.AddArray(array, array == null ? 0 : array.Length, elementSize);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs
new file mode 100644
index 0000000000..cc416a96d9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs
@@ -0,0 +1,349 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Used when implementing a custom TraceLoggingTypeInfo.
+ /// These are passed to metadataCollector.Add to specify the low-level
+ /// type of a field in the event payload. Note that a "formatted"
+ /// TraceLoggingDataType consists of a core TraceLoggingDataType value
+ /// (a TraceLoggingDataType with a value less than 32) plus an OutType.
+ /// Any combination of TraceLoggingDataType + OutType is valid, but not
+ /// all are useful. In particular, combinations not explicitly listed
+ /// below are unlikely to be recognized by decoders, and will typically
+ /// be decoded as the corresponding core type (i.e. the decoder will
+ /// mask off any unrecognized OutType value).
+ /// </summary>
+ internal enum TraceLoggingDataType
+ {
+ /// <summary>
+ /// Core type.
+ /// Data type with no value (0-length payload).
+ /// NOTE: arrays of Nil are illegal.
+ /// NOTE: a fixed-length array of Nil is interpreted by the decoder as
+ /// a struct (obsolete but retained for backwards-compatibility).
+ /// </summary>
+ Nil = 0,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes null-terminated Char16 string.
+ /// Decoding treats as UTF-16LE string.
+ /// </summary>
+ Utf16String = 1,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes null-terminated Char8 string.
+ /// Decoding treats as MBCS string.
+ /// </summary>
+ MbcsString = 2,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 8-bit value.
+ /// Decoding treats as signed integer.
+ /// </summary>
+ Int8 = 3,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 8-bit value.
+ /// Decoding treats as unsigned integer.
+ /// </summary>
+ UInt8 = 4,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 16-bit value.
+ /// Decoding treats as signed integer.
+ /// </summary>
+ Int16 = 5,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 16-bit value.
+ /// Decoding treats as unsigned integer.
+ /// </summary>
+ UInt16 = 6,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as signed integer.
+ /// </summary>
+ Int32 = 7,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as unsigned integer.
+ /// </summary>
+ UInt32 = 8,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 64-bit value.
+ /// Decoding treats as signed integer.
+ /// </summary>
+ Int64 = 9,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 64-bit value.
+ /// Decoding treats as unsigned integer.
+ /// </summary>
+ UInt64 = 10,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as Float.
+ /// </summary>
+ Float = 11,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 64-bit value.
+ /// Decoding treats as Double.
+ /// </summary>
+ Double = 12,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as Boolean.
+ /// </summary>
+ Boolean32 = 13,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 16-bit bytecount followed by binary data.
+ /// Decoding treats as binary data.
+ /// </summary>
+ Binary = 14,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 16-byte value.
+ /// Decoding treats as GUID.
+ /// </summary>
+ Guid = 15,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 64-bit value.
+ /// Decoding treats as FILETIME.
+ /// </summary>
+ FileTime = 17,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 16-byte value.
+ /// Decoding treats as SYSTEMTIME.
+ /// </summary>
+ SystemTime = 18,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as hexadecimal unsigned integer.
+ /// </summary>
+ HexInt32 = 20,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 64-bit value.
+ /// Decoding treats as hexadecimal unsigned integer.
+ /// </summary>
+ HexInt64 = 21,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 16-bit bytecount followed by Char16 data.
+ /// Decoding treats as UTF-16LE string.
+ /// </summary>
+ CountedUtf16String = 22,
+
+ /// <summary>
+ /// Core type.
+ /// Encoding assumes 16-bit bytecount followed by Char8 data.
+ /// Decoding treats as MBCS string.
+ /// </summary>
+ CountedMbcsString = 23,
+
+ /// <summary>
+ /// Core type.
+ /// Special case: Struct indicates that this field plus the
+ /// subsequent N logical fields are to be considered as one logical
+ /// field (i.e. a nested structure). The OutType is used to encode N.
+ /// The maximum value for N is 127. This field has no payload by
+ /// itself, but logically contains the payload of the following N
+ /// fields. It is legal to have an array of Struct.
+ /// </summary>
+ Struct = 24,
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit value.
+ /// Decoding treats as UTF-16LE character.
+ /// </summary>
+ Char16 = UInt16 + (EventFieldFormat.String << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 8-bit value.
+ /// Decoding treats as character.
+ /// </summary>
+ Char8 = UInt8 + (EventFieldFormat.String << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 8-bit value.
+ /// Decoding treats as Boolean.
+ /// </summary>
+ Boolean8 = UInt8 + (EventFieldFormat.Boolean << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 8-bit value.
+ /// Decoding treats as hexadecimal unsigned integer.
+ /// </summary>
+ HexInt8 = UInt8 + (EventFieldFormat.Hexadecimal << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit value.
+ /// Decoding treats as hexadecimal unsigned integer.
+ /// </summary>
+ HexInt16 = UInt16 + (EventFieldFormat.Hexadecimal << 8),
+
+#if false
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as process identifier.
+ /// </summary>
+ ProcessId = UInt32 + (EventSourceFieldFormat.ProcessId << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as thread identifier.
+ /// </summary>
+ ThreadId = UInt32 + (EventSourceFieldFormat.ThreadId << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit value.
+ /// Decoding treats as IP port.
+ /// </summary>
+ Port = UInt16 + (EventSourceFieldFormat.Port << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as IPv4 address.
+ /// </summary>
+ Ipv4Address = UInt32 + (EventSourceFieldFormat.Ipv4Address << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit bytecount followed by binary data.
+ /// Decoding treats as IPv6 address.
+ /// </summary>
+ Ipv6Address = Binary + (EventSourceFieldFormat.Ipv6Address << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit bytecount followed by binary data.
+ /// Decoding treats as SOCKADDR.
+ /// </summary>
+ SocketAddress = Binary + (EventSourceFieldFormat.SocketAddress << 8),
+#endif
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes null-terminated Char16 string.
+ /// Decoding treats as UTF-16LE XML string.
+ /// </summary>
+ Utf16Xml = Utf16String + (EventFieldFormat.Xml << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes null-terminated Char8 string.
+ /// Decoding treats as MBCS XML string.
+ /// </summary>
+ MbcsXml = MbcsString + (EventFieldFormat.Xml << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit bytecount followed by Char16 data.
+ /// Decoding treats as UTF-16LE XML.
+ /// </summary>
+ CountedUtf16Xml = CountedUtf16String + (EventFieldFormat.Xml << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit bytecount followed by Char8 data.
+ /// Decoding treats as MBCS XML.
+ /// </summary>
+ CountedMbcsXml = CountedMbcsString + (EventFieldFormat.Xml << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes null-terminated Char16 string.
+ /// Decoding treats as UTF-16LE JSON string.
+ /// </summary>
+ Utf16Json = Utf16String + (EventFieldFormat.Json << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes null-terminated Char8 string.
+ /// Decoding treats as MBCS JSON string.
+ /// </summary>
+ MbcsJson = MbcsString + (EventFieldFormat.Json << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit bytecount followed by Char16 data.
+ /// Decoding treats as UTF-16LE JSON.
+ /// </summary>
+ CountedUtf16Json = CountedUtf16String + (EventFieldFormat.Json << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 16-bit bytecount followed by Char8 data.
+ /// Decoding treats as MBCS JSON.
+ /// </summary>
+ CountedMbcsJson = CountedMbcsString + (EventFieldFormat.Json << 8),
+#if false
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as Win32 error.
+ /// </summary>
+ Win32Error = UInt32 + (EventSourceFieldFormat.Win32Error << 8),
+
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as NTSTATUS.
+ /// </summary>
+ NTStatus = UInt32 + (EventSourceFieldFormat.NTStatus << 8),
+#endif
+ /// <summary>
+ /// Formatted type.
+ /// Encoding assumes 32-bit value.
+ /// Decoding treats as HRESULT.
+ /// </summary>
+ HResult = Int32 + (EventFieldFormat.HResult << 8)
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs
new file mode 100644
index 0000000000..ccdc8bf7c4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs
@@ -0,0 +1,910 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in.
+// It is available from http://www.codeplex.com/hyperAddin
+
+#if PLATFORM_WINDOWS
+#define FEATURE_MANAGED_ETW
+#endif // PLATFORM_WINDOWS
+
+#if ES_BUILD_STANDALONE
+#define FEATURE_MANAGED_ETW_CHANNELS
+// #define FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
+#endif
+
+#if ES_BUILD_STANDALONE
+using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment;
+using EventDescriptor = Microsoft.Diagnostics.Tracing.EventDescriptor;
+#endif
+
+using System;
+using System.Resources;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Collections.ObjectModel;
+using System.Collections.Concurrent;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+using System.Collections.Generic;
+using System.Text;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+using System.Collections.Generic;
+using System.Text;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ public partial class EventSource
+ {
+#if FEATURE_MANAGED_ETW
+ private byte[] providerMetadata;
+#endif
+
+#if FEATURE_PERFTRACING
+ private ConcurrentDictionary<int, IntPtr> m_eventHandleMap = new ConcurrentDictionary<int, IntPtr>();
+#endif
+
+ /// <summary>
+ /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API).
+ /// </summary>
+ /// <param name="eventSourceName">
+ /// The name of the event source. Must not be null.
+ /// </param>
+ public EventSource(
+ string eventSourceName)
+ : this(eventSourceName, EventSourceSettings.EtwSelfDescribingEventFormat)
+ { }
+
+ /// <summary>
+ /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API).
+ /// </summary>
+ /// <param name="eventSourceName">
+ /// The name of the event source. Must not be null.
+ /// </param>
+ /// <param name="config">
+ /// Configuration options for the EventSource as a whole.
+ /// </param>
+ public EventSource(
+ string eventSourceName,
+ EventSourceSettings config)
+ : this(eventSourceName, config, null) { }
+
+ /// <summary>
+ /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API).
+ ///
+ /// Also specify a list of key-value pairs called traits (you must pass an even number of strings).
+ /// The first string is the key and the second is the value. These are not interpreted by EventSource
+ /// itself but may be interpreted the listeners. Can be fetched with GetTrait(string).
+ /// </summary>
+ /// <param name="eventSourceName">
+ /// The name of the event source. Must not be null.
+ /// </param>
+ /// <param name="config">
+ /// Configuration options for the EventSource as a whole.
+ /// </param>
+ /// <param name="traits">A collection of key-value strings (must be an even number).</param>
+ public EventSource(
+ string eventSourceName,
+ EventSourceSettings config,
+ params string[] traits)
+ : this(
+ eventSourceName == null ? new Guid() : GenerateGuidFromName(eventSourceName.ToUpperInvariant()),
+ eventSourceName,
+ config, traits)
+ {
+ if (eventSourceName == null)
+ {
+ throw new ArgumentNullException(nameof(eventSourceName));
+ }
+ }
+
+ /// <summary>
+ /// Writes an event with no fields and default options.
+ /// (Native API: EventWriteTransfer)
+ /// </summary>
+ /// <param name="eventName">The name of the event. Must not be null.</param>
+ public unsafe void Write(string eventName)
+ {
+ if (eventName == null)
+ {
+ throw new ArgumentNullException(nameof(eventName));
+ }
+
+ if (!this.IsEnabled())
+ {
+ return;
+ }
+
+ var options = new EventSourceOptions();
+ this.WriteImpl(eventName, ref options, null, null, null, SimpleEventTypes<EmptyStruct>.Instance);
+ }
+
+ /// <summary>
+ /// Writes an event with no fields.
+ /// (Native API: EventWriteTransfer)
+ /// </summary>
+ /// <param name="eventName">The name of the event. Must not be null.</param>
+ /// <param name="options">
+ /// Options for the event, such as the level, keywords, and opcode. Unset
+ /// options will be set to default values.
+ /// </param>
+ public unsafe void Write(string eventName, EventSourceOptions options)
+ {
+ if (eventName == null)
+ {
+ throw new ArgumentNullException(nameof(eventName));
+ }
+
+ if (!this.IsEnabled())
+ {
+ return;
+ }
+
+ this.WriteImpl(eventName, ref options, null, null, null, SimpleEventTypes<EmptyStruct>.Instance);
+ }
+
+ /// <summary>
+ /// Writes an event.
+ /// (Native API: EventWriteTransfer)
+ /// </summary>
+ /// <typeparam name="T">
+ /// The type that defines the event and its payload. This must be an
+ /// anonymous type or a type with an [EventData] attribute.
+ /// </typeparam>
+ /// <param name="eventName">
+ /// The name for the event. If null, the event name is automatically
+ /// determined based on T, either from the Name property of T's EventData
+ /// attribute or from typeof(T).Name.
+ /// </param>
+ /// <param name="data">
+ /// The object containing the event payload data. The type T must be
+ /// an anonymous type or a type with an [EventData] attribute. The
+ /// public instance properties of data will be written recursively to
+ /// create the fields of the event.
+ /// </param>
+ public unsafe void Write<T>(
+ string eventName,
+ T data)
+ {
+ if (!this.IsEnabled())
+ {
+ return;
+ }
+
+ var options = new EventSourceOptions();
+ this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes<T>.Instance);
+ }
+
+ /// <summary>
+ /// Writes an event.
+ /// (Native API: EventWriteTransfer)
+ /// </summary>
+ /// <typeparam name="T">
+ /// The type that defines the event and its payload. This must be an
+ /// anonymous type or a type with an [EventData] attribute.
+ /// </typeparam>
+ /// <param name="eventName">
+ /// The name for the event. If null, the event name is automatically
+ /// determined based on T, either from the Name property of T's EventData
+ /// attribute or from typeof(T).Name.
+ /// </param>
+ /// <param name="options">
+ /// Options for the event, such as the level, keywords, and opcode. Unset
+ /// options will be set to default values.
+ /// </param>
+ /// <param name="data">
+ /// The object containing the event payload data. The type T must be
+ /// an anonymous type or a type with an [EventData] attribute. The
+ /// public instance properties of data will be written recursively to
+ /// create the fields of the event.
+ /// </param>
+ public unsafe void Write<T>(
+ string eventName,
+ EventSourceOptions options,
+ T data)
+ {
+ if (!this.IsEnabled())
+ {
+ return;
+ }
+
+ this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes<T>.Instance);
+ }
+
+ /// <summary>
+ /// Writes an event.
+ /// This overload is for use with extension methods that wish to efficiently
+ /// forward the options or data parameter without performing an extra copy.
+ /// (Native API: EventWriteTransfer)
+ /// </summary>
+ /// <typeparam name="T">
+ /// The type that defines the event and its payload. This must be an
+ /// anonymous type or a type with an [EventData] attribute.
+ /// </typeparam>
+ /// <param name="eventName">
+ /// The name for the event. If null, the event name is automatically
+ /// determined based on T, either from the Name property of T's EventData
+ /// attribute or from typeof(T).Name.
+ /// </param>
+ /// <param name="options">
+ /// Options for the event, such as the level, keywords, and opcode. Unset
+ /// options will be set to default values.
+ /// </param>
+ /// <param name="data">
+ /// The object containing the event payload data. The type T must be
+ /// an anonymous type or a type with an [EventData] attribute. The
+ /// public instance properties of data will be written recursively to
+ /// create the fields of the event.
+ /// </param>
+ public unsafe void Write<T>(
+ string eventName,
+ ref EventSourceOptions options,
+ ref T data)
+ {
+ if (!this.IsEnabled())
+ {
+ return;
+ }
+
+ this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes<T>.Instance);
+ }
+
+ /// <summary>
+ /// Writes an event.
+ /// This overload is meant for clients that need to manipuate the activityId
+ /// and related ActivityId for the event.
+ /// </summary>
+ /// <typeparam name="T">
+ /// The type that defines the event and its payload. This must be an
+ /// anonymous type or a type with an [EventData] attribute.
+ /// </typeparam>
+ /// <param name="eventName">
+ /// The name for the event. If null, the event name is automatically
+ /// determined based on T, either from the Name property of T's EventData
+ /// attribute or from typeof(T).Name.
+ /// </param>
+ /// <param name="options">
+ /// Options for the event, such as the level, keywords, and opcode. Unset
+ /// options will be set to default values.
+ /// </param>
+ /// <param name="activityId">
+ /// The GUID of the activity associated with this event.
+ /// </param>
+ /// <param name="relatedActivityId">
+ /// The GUID of another activity that is related to this activity, or Guid.Empty
+ /// if there is no related activity. Most commonly, the Start operation of a
+ /// new activity specifies a parent activity as its related activity.
+ /// </param>
+ /// <param name="data">
+ /// The object containing the event payload data. The type T must be
+ /// an anonymous type or a type with an [EventData] attribute. The
+ /// public instance properties of data will be written recursively to
+ /// create the fields of the event.
+ /// </param>
+ public unsafe void Write<T>(
+ string eventName,
+ ref EventSourceOptions options,
+ ref Guid activityId,
+ ref Guid relatedActivityId,
+ ref T data)
+ {
+ if (!this.IsEnabled())
+ {
+ return;
+ }
+
+ fixed (Guid* pActivity = &activityId, pRelated = &relatedActivityId)
+ {
+ this.WriteImpl(
+ eventName,
+ ref options,
+ data,
+ pActivity,
+ relatedActivityId == Guid.Empty ? null : pRelated,
+ SimpleEventTypes<T>.Instance);
+ }
+ }
+
+ /// <summary>
+ /// Writes an extended event, where the values of the event are the
+ /// combined properties of any number of values. This method is
+ /// intended for use in advanced logging scenarios that support a
+ /// dynamic set of event context providers.
+ /// This method does a quick check on whether this event is enabled.
+ /// </summary>
+ /// <param name="eventName">
+ /// The name for the event. If null, the name from eventTypes is used.
+ /// (Note that providing the event name via the name parameter is slightly
+ /// less efficient than using the name from eventTypes.)
+ /// </param>
+ /// <param name="options">
+ /// Optional overrides for the event, such as the level, keyword, opcode,
+ /// activityId, and relatedActivityId. Any settings not specified by options
+ /// are obtained from eventTypes.
+ /// </param>
+ /// <param name="eventTypes">
+ /// Information about the event and the types of the values in the event.
+ /// Must not be null. Note that the eventTypes object should be created once and
+ /// saved. It should not be recreated for each event.
+ /// </param>
+ /// <param name="activityID">
+ /// A pointer to the activity ID GUID to log
+ /// </param>
+ /// <param name="childActivityID">
+ /// A pointer to the child activity ID to log (can be null) </param>
+ /// <param name="values">
+ /// The values to include in the event. Must not be null. The number and types of
+ /// the values must match the number and types of the fields described by the
+ /// eventTypes parameter.
+ /// </param>
+ private unsafe void WriteMultiMerge(
+ string eventName,
+ ref EventSourceOptions options,
+ TraceLoggingEventTypes eventTypes,
+ Guid* activityID,
+ Guid* childActivityID,
+ params object[] values)
+ {
+ if (!this.IsEnabled())
+ {
+ return;
+ }
+ byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0
+ ? options.level
+ : eventTypes.level;
+ EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0
+ ? options.keywords
+ : eventTypes.keywords;
+
+ if (this.IsEnabled((EventLevel)level, keywords))
+ {
+ WriteMultiMergeInner(eventName, ref options, eventTypes, activityID, childActivityID, values);
+ }
+ }
+
+ /// <summary>
+ /// Writes an extended event, where the values of the event are the
+ /// combined properties of any number of values. This method is
+ /// intended for use in advanced logging scenarios that support a
+ /// dynamic set of event context providers.
+ /// Attention: This API does not check whether the event is enabled or not.
+ /// Please use WriteMultiMerge to avoid spending CPU cycles for events that are
+ /// not enabled.
+ /// </summary>
+ /// <param name="eventName">
+ /// The name for the event. If null, the name from eventTypes is used.
+ /// (Note that providing the event name via the name parameter is slightly
+ /// less efficient than using the name from eventTypes.)
+ /// </param>
+ /// <param name="options">
+ /// Optional overrides for the event, such as the level, keyword, opcode,
+ /// activityId, and relatedActivityId. Any settings not specified by options
+ /// are obtained from eventTypes.
+ /// </param>
+ /// <param name="eventTypes">
+ /// Information about the event and the types of the values in the event.
+ /// Must not be null. Note that the eventTypes object should be created once and
+ /// saved. It should not be recreated for each event.
+ /// </param>
+ /// <param name="activityID">
+ /// A pointer to the activity ID GUID to log
+ /// </param>
+ /// <param name="childActivityID">
+ /// A pointer to the child activity ID to log (can be null)
+ /// </param>
+ /// <param name="values">
+ /// The values to include in the event. Must not be null. The number and types of
+ /// the values must match the number and types of the fields described by the
+ /// eventTypes parameter.
+ /// </param>
+ private unsafe void WriteMultiMergeInner(
+ string eventName,
+ ref EventSourceOptions options,
+ TraceLoggingEventTypes eventTypes,
+ Guid* activityID,
+ Guid* childActivityID,
+ params object[] values)
+ {
+#if FEATURE_MANAGED_ETW
+ int identity = 0;
+ byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0
+ ? options.level
+ : eventTypes.level;
+ byte opcode = (options.valuesSet & EventSourceOptions.opcodeSet) != 0
+ ? options.opcode
+ : eventTypes.opcode;
+ EventTags tags = (options.valuesSet & EventSourceOptions.tagsSet) != 0
+ ? options.tags
+ : eventTypes.Tags;
+ EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0
+ ? options.keywords
+ : eventTypes.keywords;
+
+ var nameInfo = eventTypes.GetNameInfo(eventName ?? eventTypes.Name, tags);
+ if (nameInfo == null)
+ {
+ return;
+ }
+ identity = nameInfo.identity;
+ EventDescriptor descriptor = new EventDescriptor(identity, level, opcode, (long)keywords);
+
+#if FEATURE_PERFTRACING
+ IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_provider, m_eventHandleMap, descriptor, eventTypes);
+ Debug.Assert(eventHandle != IntPtr.Zero);
+#else
+ IntPtr eventHandle = IntPtr.Zero;
+#endif
+
+ var pinCount = eventTypes.pinCount;
+ var scratch = stackalloc byte[eventTypes.scratchSize];
+ var descriptors = stackalloc EventData[eventTypes.dataCount + 3];
+ for(int i = 0; i < eventTypes.dataCount + 3; i++)
+ descriptors[i] = default(EventData);
+
+ var pins = stackalloc GCHandle[pinCount];
+ for (int i = 0; i < pinCount; i++)
+ pins[i] = default(GCHandle);
+
+ fixed (byte*
+ pMetadata0 = this.providerMetadata,
+ pMetadata1 = nameInfo.nameMetadata,
+ pMetadata2 = eventTypes.typeMetadata)
+ {
+ descriptors[0].SetMetadata(pMetadata0, this.providerMetadata.Length, 2);
+ descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1);
+ descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1);
+
+#if (!ES_BUILD_PCL && !ES_BUILD_PN)
+ System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
+#endif
+ try
+ {
+ DataCollector.ThreadInstance.Enable(
+ scratch,
+ eventTypes.scratchSize,
+ descriptors + 3,
+ eventTypes.dataCount,
+ pins,
+ pinCount);
+
+ for (int i = 0; i < eventTypes.typeInfos.Length; i++)
+ {
+ var info = eventTypes.typeInfos[i];
+ info.WriteData(TraceLoggingDataCollector.Instance, info.PropertyValueFactory(values[i]));
+ }
+
+ this.WriteEventRaw(
+ eventName,
+ ref descriptor,
+ eventHandle,
+ activityID,
+ childActivityID,
+ (int)(DataCollector.ThreadInstance.Finish() - descriptors),
+ (IntPtr)descriptors);
+ }
+ finally
+ {
+ this.WriteCleanup(pins, pinCount);
+ }
+ }
+#endif // FEATURE_MANAGED_ETW
+ }
+
+ /// <summary>
+ /// Writes an extended event, where the values of the event have already
+ /// been serialized in "data".
+ /// </summary>
+ /// <param name="eventName">
+ /// The name for the event. If null, the name from eventTypes is used.
+ /// (Note that providing the event name via the name parameter is slightly
+ /// less efficient than using the name from eventTypes.)
+ /// </param>
+ /// <param name="options">
+ /// Optional overrides for the event, such as the level, keyword, opcode,
+ /// activityId, and relatedActivityId. Any settings not specified by options
+ /// are obtained from eventTypes.
+ /// </param>
+ /// <param name="eventTypes">
+ /// Information about the event and the types of the values in the event.
+ /// Must not be null. Note that the eventTypes object should be created once and
+ /// saved. It should not be recreated for each event.
+ /// </param>
+ /// <param name="activityID">
+ /// A pointer to the activity ID GUID to log
+ /// </param>
+ /// <param name="childActivityID">
+ /// A pointer to the child activity ID to log (can be null)
+ /// </param>
+ /// <param name="data">
+ /// The previously serialized values to include in the event. Must not be null.
+ /// The number and types of the values must match the number and types of the
+ /// fields described by the eventTypes parameter.
+ /// </param>
+ internal unsafe void WriteMultiMerge(
+ string eventName,
+ ref EventSourceOptions options,
+ TraceLoggingEventTypes eventTypes,
+ Guid* activityID,
+ Guid* childActivityID,
+ EventData* data)
+ {
+#if FEATURE_MANAGED_ETW
+ if (!this.IsEnabled())
+ {
+ return;
+ }
+
+ fixed (EventSourceOptions* pOptions = &options)
+ {
+ EventDescriptor descriptor;
+ var nameInfo = this.UpdateDescriptor(eventName, eventTypes, ref options, out descriptor);
+ if (nameInfo == null)
+ {
+ return;
+ }
+
+#if FEATURE_PERFTRACING
+ IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_provider, m_eventHandleMap, descriptor, eventTypes);
+ Debug.Assert(eventHandle != IntPtr.Zero);
+#else
+ IntPtr eventHandle = IntPtr.Zero;
+#endif
+
+ // We make a descriptor for each EventData, and because we morph strings to counted strings
+ // we may have 2 for each arg, so we allocate enough for this.
+ var descriptorsLength = eventTypes.dataCount + eventTypes.typeInfos.Length * 2 + 3;
+ var descriptors = stackalloc EventData[descriptorsLength];
+ for(int i = 0; i < descriptorsLength; i++)
+ descriptors[i] = default(EventData);
+
+ fixed (byte*
+ pMetadata0 = this.providerMetadata,
+ pMetadata1 = nameInfo.nameMetadata,
+ pMetadata2 = eventTypes.typeMetadata)
+ {
+ descriptors[0].SetMetadata(pMetadata0, this.providerMetadata.Length, 2);
+ descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1);
+ descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1);
+ int numDescrs = 3;
+
+ for (int i = 0; i < eventTypes.typeInfos.Length; i++)
+ {
+ descriptors[numDescrs].m_Ptr = data[i].m_Ptr;
+ descriptors[numDescrs].m_Size = data[i].m_Size;
+
+ // old conventions for bool is 4 bytes, but meta-data assumes 1.
+ if (data[i].m_Size == 4 && eventTypes.typeInfos[i].DataType == typeof(bool))
+ descriptors[numDescrs].m_Size = 1;
+
+ numDescrs++;
+ }
+
+ this.WriteEventRaw(
+ eventName,
+ ref descriptor,
+ eventHandle,
+ activityID,
+ childActivityID,
+ numDescrs,
+ (IntPtr)descriptors);
+ }
+ }
+#endif // FEATURE_MANAGED_ETW
+ }
+
+ private unsafe void WriteImpl(
+ string eventName,
+ ref EventSourceOptions options,
+ object data,
+ Guid* pActivityId,
+ Guid* pRelatedActivityId,
+ TraceLoggingEventTypes eventTypes)
+ {
+ try
+ {
+ fixed (EventSourceOptions* pOptions = &options)
+ {
+ EventDescriptor descriptor;
+ options.Opcode = options.IsOpcodeSet ? options.Opcode : GetOpcodeWithDefault(options.Opcode, eventName);
+ var nameInfo = this.UpdateDescriptor(eventName, eventTypes, ref options, out descriptor);
+ if (nameInfo == null)
+ {
+ return;
+ }
+
+#if FEATURE_PERFTRACING
+ IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_provider, m_eventHandleMap, descriptor, eventTypes);
+ Debug.Assert(eventHandle != IntPtr.Zero);
+#else
+ IntPtr eventHandle = IntPtr.Zero;
+#endif
+
+#if FEATURE_MANAGED_ETW
+ var pinCount = eventTypes.pinCount;
+ var scratch = stackalloc byte[eventTypes.scratchSize];
+ var descriptors = stackalloc EventData[eventTypes.dataCount + 3];
+ for(int i=0; i<eventTypes.dataCount + 3; i++)
+ descriptors[i] = default(EventData);
+
+ var pins = stackalloc GCHandle[pinCount];
+ for (int i = 0; i < pinCount; i++)
+ pins[i] = default(GCHandle);
+
+ fixed (byte*
+ pMetadata0 = this.providerMetadata,
+ pMetadata1 = nameInfo.nameMetadata,
+ pMetadata2 = eventTypes.typeMetadata)
+ {
+ descriptors[0].SetMetadata(pMetadata0, this.providerMetadata.Length, 2);
+ descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1);
+ descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1);
+#endif // FEATURE_MANAGED_ETW
+
+#if (!ES_BUILD_PCL && !ES_BUILD_PN)
+ System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
+#endif
+ EventOpcode opcode = (EventOpcode)descriptor.Opcode;
+
+ Guid activityId = Guid.Empty;
+ Guid relatedActivityId = Guid.Empty;
+ if (pActivityId == null && pRelatedActivityId == null &&
+ ((options.ActivityOptions & EventActivityOptions.Disable) == 0))
+ {
+ if (opcode == EventOpcode.Start)
+ {
+ m_activityTracker.OnStart(m_name, eventName, 0, ref activityId, ref relatedActivityId, options.ActivityOptions);
+ }
+ else if (opcode == EventOpcode.Stop)
+ {
+ m_activityTracker.OnStop(m_name, eventName, 0, ref activityId);
+ }
+ if (activityId != Guid.Empty)
+ pActivityId = &activityId;
+ if (relatedActivityId != Guid.Empty)
+ pRelatedActivityId = &relatedActivityId;
+ }
+
+ try
+ {
+#if FEATURE_MANAGED_ETW
+ DataCollector.ThreadInstance.Enable(
+ scratch,
+ eventTypes.scratchSize,
+ descriptors + 3,
+ eventTypes.dataCount,
+ pins,
+ pinCount);
+
+ var info = eventTypes.typeInfos[0];
+ info.WriteData(TraceLoggingDataCollector.Instance, info.PropertyValueFactory(data));
+
+ this.WriteEventRaw(
+ eventName,
+ ref descriptor,
+ eventHandle,
+ pActivityId,
+ pRelatedActivityId,
+ (int)(DataCollector.ThreadInstance.Finish() - descriptors),
+ (IntPtr)descriptors);
+#endif // FEATURE_MANAGED_ETW
+
+ // TODO enable filtering for listeners.
+ if (m_Dispatchers != null)
+ {
+ var eventData = (EventPayload)(eventTypes.typeInfos[0].GetData(data));
+ WriteToAllListeners(eventName, ref descriptor, nameInfo.tags, pActivityId, pRelatedActivityId, eventData);
+ }
+
+ }
+ catch (Exception ex)
+ {
+ if (ex is EventSourceException)
+ throw;
+ else
+ ThrowEventSourceException(eventName, ex);
+ }
+#if FEATURE_MANAGED_ETW
+ finally
+ {
+ this.WriteCleanup(pins, pinCount);
+ }
+ }
+#endif // FEATURE_MANAGED_ETW
+ }
+ }
+ catch (Exception ex)
+ {
+ if (ex is EventSourceException)
+ throw;
+ else
+ ThrowEventSourceException(eventName, ex);
+ }
+ }
+
+ private unsafe void WriteToAllListeners(string eventName, ref EventDescriptor eventDescriptor, EventTags tags, Guid* pActivityId, Guid* pChildActivityId, EventPayload payload)
+ {
+ EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this);
+ eventCallbackArgs.EventName = eventName;
+ eventCallbackArgs.m_level = (EventLevel) eventDescriptor.Level;
+ eventCallbackArgs.m_keywords = (EventKeywords) eventDescriptor.Keywords;
+ eventCallbackArgs.m_opcode = (EventOpcode) eventDescriptor.Opcode;
+ eventCallbackArgs.m_tags = tags;
+
+ // Self described events do not have an id attached. We mark it internally with -1.
+ eventCallbackArgs.EventId = -1;
+ if (pActivityId != null)
+ eventCallbackArgs.ActivityId = *pActivityId;
+ if (pChildActivityId != null)
+ eventCallbackArgs.RelatedActivityId = *pChildActivityId;
+
+ if (payload != null)
+ {
+ eventCallbackArgs.Payload = new ReadOnlyCollection<object>((IList<object>)payload.Values);
+ eventCallbackArgs.PayloadNames = new ReadOnlyCollection<string>((IList<string>)payload.Keys);
+ }
+
+ DispatchToAllListeners(-1, pActivityId, eventCallbackArgs);
+ }
+
+#if (!ES_BUILD_PCL && !ES_BUILD_PN)
+ [System.Runtime.ConstrainedExecution.ReliabilityContract(
+ System.Runtime.ConstrainedExecution.Consistency.WillNotCorruptState,
+ System.Runtime.ConstrainedExecution.Cer.Success)]
+#endif
+ [NonEvent]
+ private unsafe void WriteCleanup(GCHandle* pPins, int cPins)
+ {
+ DataCollector.ThreadInstance.Disable();
+
+ for (int i = 0; i < cPins; i++)
+ {
+ if (pPins[i].IsAllocated)
+ {
+ pPins[i].Free();
+ }
+ }
+ }
+
+ private void InitializeProviderMetadata()
+ {
+#if FEATURE_MANAGED_ETW
+ if (m_traits != null)
+ {
+ List<byte> traitMetaData = new List<byte>(100);
+ for (int i = 0; i < m_traits.Length - 1; i += 2)
+ {
+ if (m_traits[i].StartsWith("ETW_", StringComparison.Ordinal))
+ {
+ string etwTrait = m_traits[i].Substring(4);
+ byte traitNum;
+ if (!byte.TryParse(etwTrait, out traitNum))
+ {
+ if (etwTrait == "GROUP")
+ {
+ traitNum = 1;
+ }
+ else
+ {
+ throw new ArgumentException(SR.Format(SR.EventSource_UnknownEtwTrait, etwTrait), "traits");
+ }
+ }
+ string value = m_traits[i + 1];
+ int lenPos = traitMetaData.Count;
+ traitMetaData.Add(0); // Emit size (to be filled in later)
+ traitMetaData.Add(0);
+ traitMetaData.Add(traitNum); // Emit Trait number
+ var valueLen = AddValueToMetaData(traitMetaData, value) + 3; // Emit the value bytes +3 accounts for 3 bytes we emited above.
+ traitMetaData[lenPos] = unchecked((byte)valueLen); // Fill in size
+ traitMetaData[lenPos + 1] = unchecked((byte)(valueLen >> 8));
+ }
+ }
+ providerMetadata = Statics.MetadataForString(this.Name, 0, traitMetaData.Count, 0);
+ int startPos = providerMetadata.Length - traitMetaData.Count;
+ foreach (var b in traitMetaData)
+ providerMetadata[startPos++] = b;
+ }
+ else
+ providerMetadata = Statics.MetadataForString(this.Name, 0, 0, 0);
+#endif //FEATURE_MANAGED_ETW
+ }
+
+ private static int AddValueToMetaData(List<byte> metaData, string value)
+ {
+ if (value.Length == 0)
+ return 0;
+
+ int startPos = metaData.Count;
+ char firstChar = value[0];
+
+ if (firstChar == '@')
+ metaData.AddRange(Encoding.UTF8.GetBytes(value.Substring(1)));
+ else if (firstChar == '{')
+ metaData.AddRange(new Guid(value).ToByteArray());
+ else if (firstChar == '#')
+ {
+ for (int i = 1; i < value.Length; i++)
+ {
+ if (value[i] != ' ') // Skip spaces between bytes.
+ {
+ if (!(i + 1 < value.Length))
+ {
+ throw new ArgumentException(SR.EventSource_EvenHexDigits, "traits");
+ }
+ metaData.Add((byte)(HexDigit(value[i]) * 16 + HexDigit(value[i + 1])));
+ i++;
+ }
+ }
+ }
+ else if ('A' <= firstChar || ' ' == firstChar) // Is it alphabetic or space (excludes digits and most punctuation).
+ {
+ metaData.AddRange(Encoding.UTF8.GetBytes(value));
+ }
+ else
+ {
+ throw new ArgumentException(SR.Format(SR.EventSource_IllegalValue, value), "traits");
+ }
+
+ return metaData.Count - startPos;
+ }
+
+ /// <summary>
+ /// Returns a value 0-15 if 'c' is a hexadecimal digit. If it throws an argument exception.
+ /// </summary>
+ private static int HexDigit(char c)
+ {
+ if ('0' <= c && c <= '9')
+ {
+ return (c - '0');
+ }
+ if ('a' <= c)
+ {
+ c = unchecked((char)(c - ('a' - 'A'))); // Convert to lower case
+ }
+ if ('A' <= c && c <= 'F')
+ {
+ return (c - 'A' + 10);
+ }
+
+ throw new ArgumentException(SR.Format(SR.EventSource_BadHexDigit, c), "traits");
+ }
+
+ private NameInfo UpdateDescriptor(
+ string name,
+ TraceLoggingEventTypes eventInfo,
+ ref EventSourceOptions options,
+ out EventDescriptor descriptor)
+ {
+ NameInfo nameInfo = null;
+ int identity = 0;
+ byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0
+ ? options.level
+ : eventInfo.level;
+ byte opcode = (options.valuesSet & EventSourceOptions.opcodeSet) != 0
+ ? options.opcode
+ : eventInfo.opcode;
+ EventTags tags = (options.valuesSet & EventSourceOptions.tagsSet) != 0
+ ? options.tags
+ : eventInfo.Tags;
+ EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0
+ ? options.keywords
+ : eventInfo.keywords;
+
+ if (this.IsEnabled((EventLevel)level, keywords))
+ {
+ nameInfo = eventInfo.GetNameInfo(name ?? eventInfo.Name, tags);
+ identity = nameInfo.identity;
+ }
+
+ descriptor = new EventDescriptor(identity, level, opcode, (long)keywords);
+ return nameInfo;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs
new file mode 100644
index 0000000000..e808a8823c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// Tags are flags that are not interpreted by EventSource but are passed along
+ /// to the EventListener. The EventListener determines the semantics of the flags.
+ /// </summary>
+ [Flags]
+ public enum EventTags
+ {
+ /// <summary>
+ /// No special traits are added to the event.
+ /// </summary>
+ None = 0,
+
+ /* Bits below 0x10000 are available for any use by the provider. */
+ /* Bits at or above 0x10000 are reserved for definition by Microsoft. */
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs
new file mode 100644
index 0000000000..8887714fdb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs
@@ -0,0 +1,272 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using Interlocked = System.Threading.Interlocked;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: Used when calling EventSource.WriteMultiMerge.
+ /// Stores the type information to use when writing the event fields.
+ /// </summary>
+ public class TraceLoggingEventTypes
+ {
+ internal readonly TraceLoggingTypeInfo[] typeInfos;
+#if FEATURE_PERFTRACING
+ internal readonly string[] paramNames;
+#endif
+ internal readonly string name;
+ internal readonly EventTags tags;
+ internal readonly byte level;
+ internal readonly byte opcode;
+ internal readonly EventKeywords keywords;
+ internal readonly byte[] typeMetadata;
+ internal readonly int scratchSize;
+ internal readonly int dataCount;
+ internal readonly int pinCount;
+ private ConcurrentSet<KeyValuePair<string, EventTags>, NameInfo> nameInfos;
+
+ /// <summary>
+ /// Initializes a new instance of TraceLoggingEventTypes corresponding
+ /// to the name, flags, and types provided. Always uses the default
+ /// TypeInfo for each Type.
+ /// </summary>
+ /// <param name="name">
+ /// The name to use when the name parameter passed to
+ /// EventSource.Write is null. This value must not be null.
+ /// </param>
+ /// <param name="tags">
+ /// Tags to add to the event if the tags are not set via options.
+ /// </param>
+ /// <param name="types">
+ /// The types of the fields in the event. This value must not be null.
+ /// </param>
+ internal TraceLoggingEventTypes(
+ string name,
+ EventTags tags,
+ params Type[] types)
+ : this(tags, name, MakeArray(types))
+ {
+ return;
+ }
+
+ /// <summary>
+ /// Returns a new instance of TraceLoggingEventInfo corresponding to the name,
+ /// flags, and typeInfos provided.
+ /// </summary>
+ /// <param name="name">
+ /// The name to use when the name parameter passed to
+ /// EventSource.Write is null. This value must not be null.
+ /// </param>
+ /// <param name="tags">
+ /// Tags to add to the event if the tags are not set via options.
+ /// </param>
+ /// <param name="typeInfos">
+ /// The types of the fields in the event. This value must not be null.
+ /// </param>
+ /// <returns>
+ /// An instance of TraceLoggingEventInfo with DefaultName set to the specified name
+ /// and with the specified typeInfos.
+ /// </returns>
+ internal TraceLoggingEventTypes(
+ string name,
+ EventTags tags,
+ params TraceLoggingTypeInfo[] typeInfos)
+ : this(tags, name, MakeArray(typeInfos))
+ {
+ return;
+ }
+
+ internal TraceLoggingEventTypes(
+ string name,
+ EventTags tags,
+ System.Reflection.ParameterInfo[] paramInfos)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ this.typeInfos = MakeArray(paramInfos);
+#if FEATURE_PERFTRACING
+ this.paramNames = MakeParamNameArray(paramInfos);
+#endif
+ this.name = name;
+ this.tags = tags;
+ this.level = Statics.DefaultLevel;
+
+ var collector = new TraceLoggingMetadataCollector();
+ for (int i = 0; i < typeInfos.Length; ++i)
+ {
+ var typeInfo = typeInfos[i];
+ this.level = Statics.Combine((int)typeInfo.Level, this.level);
+ this.opcode = Statics.Combine((int)typeInfo.Opcode, this.opcode);
+ this.keywords |= typeInfo.Keywords;
+ var paramName = paramInfos[i].Name;
+ if (Statics.ShouldOverrideFieldName(paramName))
+ {
+ paramName = typeInfo.Name;
+ }
+ typeInfo.WriteMetadata(collector, paramName, EventFieldFormat.Default);
+ }
+
+ this.typeMetadata = collector.GetMetadata();
+ this.scratchSize = collector.ScratchSize;
+ this.dataCount = collector.DataCount;
+ this.pinCount = collector.PinCount;
+ }
+
+ private TraceLoggingEventTypes(
+ EventTags tags,
+ string defaultName,
+ TraceLoggingTypeInfo[] typeInfos)
+ {
+ if (defaultName == null)
+ {
+ throw new ArgumentNullException(nameof(defaultName));
+ }
+
+ this.typeInfos = typeInfos;
+ this.name = defaultName;
+ this.tags = tags;
+ this.level = Statics.DefaultLevel;
+
+ var collector = new TraceLoggingMetadataCollector();
+ foreach (var typeInfo in typeInfos)
+ {
+ this.level = Statics.Combine((int)typeInfo.Level, this.level);
+ this.opcode = Statics.Combine((int)typeInfo.Opcode, this.opcode);
+ this.keywords |= typeInfo.Keywords;
+ typeInfo.WriteMetadata(collector, null, EventFieldFormat.Default);
+ }
+
+ this.typeMetadata = collector.GetMetadata();
+ this.scratchSize = collector.ScratchSize;
+ this.dataCount = collector.DataCount;
+ this.pinCount = collector.PinCount;
+ }
+
+ /// <summary>
+ /// Gets the default name that will be used for events with this descriptor.
+ /// </summary>
+ internal string Name
+ {
+ get { return this.name; }
+ }
+
+ /// <summary>
+ /// Gets the default level that will be used for events with this descriptor.
+ /// </summary>
+ internal EventLevel Level
+ {
+ get { return (EventLevel)this.level; }
+ }
+
+ /// <summary>
+ /// Gets the default opcode that will be used for events with this descriptor.
+ /// </summary>
+ internal EventOpcode Opcode
+ {
+ get { return (EventOpcode)this.opcode; }
+ }
+
+ /// <summary>
+ /// Gets the default set of keywords that will added to events with this descriptor.
+ /// </summary>
+ internal EventKeywords Keywords
+ {
+ get { return (EventKeywords)this.keywords; }
+ }
+
+ /// <summary>
+ /// Gets the default tags that will be added events with this descriptor.
+ /// </summary>
+ internal EventTags Tags
+ {
+ get { return this.tags; }
+ }
+
+ internal NameInfo GetNameInfo(string name, EventTags tags)
+ {
+ var ret = this.nameInfos.TryGet(new KeyValuePair<string, EventTags>(name, tags));
+ if (ret == null)
+ {
+ ret = this.nameInfos.GetOrAdd(new NameInfo(name, tags, this.typeMetadata.Length));
+ }
+
+ return ret;
+ }
+
+ private TraceLoggingTypeInfo[] MakeArray(System.Reflection.ParameterInfo[] paramInfos)
+ {
+ if (paramInfos == null)
+ {
+ throw new ArgumentNullException(nameof(paramInfos));
+ }
+
+ var recursionCheck = new List<Type>(paramInfos.Length);
+ var result = new TraceLoggingTypeInfo[paramInfos.Length];
+ for (int i = 0; i < paramInfos.Length; ++i)
+ {
+ result[i] = TraceLoggingTypeInfo.GetInstance(paramInfos[i].ParameterType, recursionCheck);
+ }
+
+ return result;
+ }
+
+ private static TraceLoggingTypeInfo[] MakeArray(Type[] types)
+ {
+ if (types == null)
+ {
+ throw new ArgumentNullException(nameof(types));
+ }
+
+ var recursionCheck = new List<Type>(types.Length);
+ var result = new TraceLoggingTypeInfo[types.Length];
+ for (int i = 0; i < types.Length; i++)
+ {
+ result[i] = TraceLoggingTypeInfo.GetInstance(types[i], recursionCheck);
+ }
+
+ return result;
+ }
+
+ private static TraceLoggingTypeInfo[] MakeArray(
+ TraceLoggingTypeInfo[] typeInfos)
+ {
+ if (typeInfos == null)
+ {
+ throw new ArgumentNullException(nameof(typeInfos));
+ }
+
+ return (TraceLoggingTypeInfo[])typeInfos.Clone(); ;
+ }
+
+#if FEATURE_PERFTRACING
+ private static string[] MakeParamNameArray(
+ System.Reflection.ParameterInfo[] paramInfos)
+ {
+ string[] paramNames = new string[paramInfos.Length];
+ for (int i = 0; i < paramNames.Length; i++)
+ {
+ paramNames[i] = paramInfos[i].Name;
+ }
+
+ return paramNames;
+ }
+#endif
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs
new file mode 100644
index 0000000000..b5b199dbca
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs
@@ -0,0 +1,391 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+#if ES_BUILD_STANDALONE
+using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment;
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: used when implementing a custom TraceLoggingTypeInfo.
+ /// An instance of this type is provided to the TypeInfo.WriteMetadata method.
+ /// </summary>
+ internal class TraceLoggingMetadataCollector
+ {
+ private readonly Impl impl;
+ private readonly FieldMetadata currentGroup;
+ private int bufferedArrayFieldCount = int.MinValue;
+
+ /// <summary>
+ /// Creates a root-level collector.
+ /// </summary>
+ internal TraceLoggingMetadataCollector()
+ {
+ this.impl = new Impl();
+ }
+
+ /// <summary>
+ /// Creates a collector for a group.
+ /// </summary>
+ /// <param name="other">Parent collector</param>
+ /// <param name="group">The field that starts the group</param>
+ private TraceLoggingMetadataCollector(
+ TraceLoggingMetadataCollector other,
+ FieldMetadata group)
+ {
+ this.impl = other.impl;
+ this.currentGroup = group;
+ }
+
+ /// <summary>
+ /// The field tags to be used for the next field.
+ /// This will be reset to None each time a field is written.
+ /// </summary>
+ internal EventFieldTags Tags
+ {
+ get;
+ set;
+ }
+
+ internal int ScratchSize
+ {
+ get { return this.impl.scratchSize; }
+ }
+
+ internal int DataCount
+ {
+ get { return this.impl.dataCount; }
+ }
+
+ internal int PinCount
+ {
+ get { return this.impl.pinCount; }
+ }
+
+ private bool BeginningBufferedArray
+ {
+ get { return this.bufferedArrayFieldCount == 0; }
+ }
+
+ /// <summary>
+ /// Call this method to add a group to the event and to return
+ /// a new metadata collector that can be used to add fields to the
+ /// group. After all of the fields in the group have been written,
+ /// switch back to the original metadata collector to add fields
+ /// outside of the group.
+ /// Special-case: if name is null, no group is created, and AddGroup
+ /// returns the original metadata collector. This is useful when
+ /// adding the top-level group for an event.
+ /// Note: do not use the original metadata collector while the group's
+ /// metadata collector is in use, and do not use the group's metadata
+ /// collector after switching back to the original.
+ /// </summary>
+ /// <param name="name">
+ /// The name of the group. If name is null, the call to AddGroup is a
+ /// no-op (collector.AddGroup(null) returns collector).
+ /// </param>
+ /// <returns>
+ /// A new metadata collector that can be used to add fields to the group.
+ /// </returns>
+ public TraceLoggingMetadataCollector AddGroup(string name)
+ {
+ TraceLoggingMetadataCollector result = this;
+
+ if (name != null || // Normal.
+ this.BeginningBufferedArray) // Error, FieldMetadata's constructor will throw the appropriate exception.
+ {
+ var newGroup = new FieldMetadata(
+ name,
+ TraceLoggingDataType.Struct,
+ this.Tags,
+ this.BeginningBufferedArray);
+ this.AddField(newGroup);
+ result = new TraceLoggingMetadataCollector(this, newGroup);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Adds a scalar field to an event.
+ /// </summary>
+ /// <param name="name">
+ /// The name to use for the added field. This value must not be null.
+ /// </param>
+ /// <param name="type">
+ /// The type code for the added field. This must be a fixed-size type
+ /// (e.g. string types are not supported).
+ /// </param>
+ public void AddScalar(string name, TraceLoggingDataType type)
+ {
+ int size;
+ switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask))
+ {
+ case TraceLoggingDataType.Int8:
+ case TraceLoggingDataType.UInt8:
+ case TraceLoggingDataType.Char8:
+ size = 1;
+ break;
+ case TraceLoggingDataType.Int16:
+ case TraceLoggingDataType.UInt16:
+ case TraceLoggingDataType.Char16:
+ size = 2;
+ break;
+ case TraceLoggingDataType.Int32:
+ case TraceLoggingDataType.UInt32:
+ case TraceLoggingDataType.HexInt32:
+ case TraceLoggingDataType.Float:
+ case TraceLoggingDataType.Boolean32:
+ size = 4;
+ break;
+ case TraceLoggingDataType.Int64:
+ case TraceLoggingDataType.UInt64:
+ case TraceLoggingDataType.HexInt64:
+ case TraceLoggingDataType.Double:
+ case TraceLoggingDataType.FileTime:
+ size = 8;
+ break;
+ case TraceLoggingDataType.Guid:
+ case TraceLoggingDataType.SystemTime:
+ size = 16;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(type));
+ }
+
+ this.impl.AddScalar(size);
+ this.AddField(new FieldMetadata(name, type, this.Tags, this.BeginningBufferedArray));
+ }
+
+ /// <summary>
+ /// Adds a binary-format field to an event.
+ /// Compatible with core types: Binary, CountedUtf16String, CountedMbcsString.
+ /// Compatible with dataCollector methods: AddBinary(string), AddArray(Any8bitType[]).
+ /// </summary>
+ /// <param name="name">
+ /// The name to use for the added field. This value must not be null.
+ /// </param>
+ /// <param name="type">
+ /// The type code for the added field. This must be a Binary or CountedString type.
+ /// </param>
+ public void AddBinary(string name, TraceLoggingDataType type)
+ {
+ switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask))
+ {
+ case TraceLoggingDataType.Binary:
+ case TraceLoggingDataType.CountedMbcsString:
+ case TraceLoggingDataType.CountedUtf16String:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(type));
+ }
+
+ this.impl.AddScalar(2);
+ this.impl.AddNonscalar();
+ this.AddField(new FieldMetadata(name, type, this.Tags, this.BeginningBufferedArray));
+ }
+
+ /// <summary>
+ /// Adds a null-terminated string field to an event.
+ /// Compatible with core types: Utf16String, MbcsString.
+ /// Compatible with dataCollector method: AddNullTerminatedString(string).
+ /// </summary>
+ /// <param name="name">
+ /// The name to use for the added field. This value must not be null.
+ /// </param>
+ /// <param name="type">
+ /// The type code for the added field. This must be a null-terminated string type.
+ /// </param>
+ public void AddNullTerminatedString(string name, TraceLoggingDataType type)
+ {
+ switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask))
+ {
+ case TraceLoggingDataType.Utf16String:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(type));
+ }
+
+ this.impl.AddNonscalar();
+ this.AddField(new FieldMetadata(name, type, this.Tags, this.BeginningBufferedArray));
+ }
+
+ /// <summary>
+ /// Adds an array field to an event.
+ /// </summary>
+ /// <param name="name">
+ /// The name to use for the added field. This value must not be null.
+ /// </param>
+ /// <param name="type">
+ /// The type code for the added field. This must be a fixed-size type.
+ /// </param>
+ public void AddArray(string name, TraceLoggingDataType type)
+ {
+ switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask))
+ {
+ case TraceLoggingDataType.Int8:
+ case TraceLoggingDataType.UInt8:
+ case TraceLoggingDataType.Int16:
+ case TraceLoggingDataType.UInt16:
+ case TraceLoggingDataType.Int32:
+ case TraceLoggingDataType.UInt32:
+ case TraceLoggingDataType.Int64:
+ case TraceLoggingDataType.UInt64:
+ case TraceLoggingDataType.Float:
+ case TraceLoggingDataType.Double:
+ case TraceLoggingDataType.Boolean32:
+ case TraceLoggingDataType.Guid:
+ case TraceLoggingDataType.FileTime:
+ case TraceLoggingDataType.HexInt32:
+ case TraceLoggingDataType.HexInt64:
+ case TraceLoggingDataType.Char16:
+ case TraceLoggingDataType.Char8:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(type));
+ }
+
+ if (this.BeginningBufferedArray)
+ {
+ throw new NotSupportedException(SR.EventSource_NotSupportedNestedArraysEnums);
+ }
+
+ this.impl.AddScalar(2);
+ this.impl.AddNonscalar();
+ this.AddField(new FieldMetadata(name, type, this.Tags, true));
+ }
+
+ public void BeginBufferedArray()
+ {
+ if (this.bufferedArrayFieldCount >= 0)
+ {
+ throw new NotSupportedException(SR.EventSource_NotSupportedNestedArraysEnums);
+ }
+
+ this.bufferedArrayFieldCount = 0;
+ this.impl.BeginBuffered();
+ }
+
+ public void EndBufferedArray()
+ {
+ if (this.bufferedArrayFieldCount != 1)
+ {
+ throw new InvalidOperationException(SR.EventSource_IncorrentlyAuthoredTypeInfo);
+ }
+
+ this.bufferedArrayFieldCount = int.MinValue;
+ this.impl.EndBuffered();
+ }
+
+ /// <summary>
+ /// Adds a custom-serialized field to an event.
+ /// </summary>
+ /// <param name="name">
+ /// The name to use for the added field. This value must not be null.
+ /// </param>
+ /// <param name="type">The encoding type for the field.</param>
+ /// <param name="metadata">Additional information needed to decode the field, if any.</param>
+ public void AddCustom(string name, TraceLoggingDataType type, byte[] metadata)
+ {
+ if (this.BeginningBufferedArray)
+ {
+ throw new NotSupportedException(SR.EventSource_NotSupportedCustomSerializedData);
+ }
+
+ this.impl.AddScalar(2);
+ this.impl.AddNonscalar();
+ this.AddField(new FieldMetadata(
+ name,
+ type,
+ this.Tags,
+ metadata));
+ }
+
+ internal byte[] GetMetadata()
+ {
+ var size = this.impl.Encode(null);
+ var metadata = new byte[size];
+ this.impl.Encode(metadata);
+ return metadata;
+ }
+
+ private void AddField(FieldMetadata fieldMetadata)
+ {
+ this.Tags = EventFieldTags.None;
+ this.bufferedArrayFieldCount++;
+ this.impl.fields.Add(fieldMetadata);
+
+ if (this.currentGroup != null)
+ {
+ this.currentGroup.IncrementStructFieldCount();
+ }
+ }
+
+ private class Impl
+ {
+ internal readonly List<FieldMetadata> fields = new List<FieldMetadata>();
+ internal short scratchSize;
+ internal sbyte dataCount;
+ internal sbyte pinCount;
+ private int bufferNesting;
+ private bool scalar;
+
+ public void AddScalar(int size)
+ {
+ if (this.bufferNesting == 0)
+ {
+ if (!this.scalar)
+ {
+ this.dataCount = checked((sbyte)(this.dataCount + 1));
+ }
+
+ this.scalar = true;
+ this.scratchSize = checked((short)(this.scratchSize + size));
+ }
+ }
+
+ public void AddNonscalar()
+ {
+ if (this.bufferNesting == 0)
+ {
+ this.scalar = false;
+ this.pinCount = checked((sbyte)(this.pinCount + 1));
+ this.dataCount = checked((sbyte)(this.dataCount + 1));
+ }
+ }
+
+ public void BeginBuffered()
+ {
+ if (this.bufferNesting == 0)
+ {
+ this.AddNonscalar();
+ }
+
+ this.bufferNesting++;
+ }
+
+ public void EndBuffered()
+ {
+ this.bufferNesting--;
+ }
+
+ public int Encode(byte[] metadata)
+ {
+ int size = 0;
+
+ foreach (var field in this.fields)
+ {
+ field.Encode(ref size, metadata);
+ }
+
+ return size;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs
new file mode 100644
index 0000000000..511a4fe480
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs
@@ -0,0 +1,205 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+#if !ES_BUILD_AGAINST_DOTNET_V35
+using Contract = System.Diagnostics.Contracts.Contract;
+#else
+using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: used when implementing a custom TraceLoggingTypeInfo.
+ /// Non-generic base class for TraceLoggingTypeInfo&lt;DataType>. Do not derive
+ /// from this class. Instead, derive from TraceLoggingTypeInfo&lt;DataType>.
+ /// </summary>
+ internal abstract class TraceLoggingTypeInfo
+ {
+ private readonly string name;
+ private readonly EventKeywords keywords;
+ private readonly EventLevel level = (EventLevel)(-1);
+ private readonly EventOpcode opcode = (EventOpcode)(-1);
+ private readonly EventTags tags;
+ private readonly Type dataType;
+ private readonly Func<object, PropertyValue> propertyValueFactory;
+
+ internal TraceLoggingTypeInfo(Type dataType)
+ {
+ if (dataType == null)
+ {
+ throw new ArgumentNullException(nameof(dataType));
+ }
+
+ this.name = dataType.Name;
+ this.dataType = dataType;
+ this.propertyValueFactory = PropertyValue.GetFactory(dataType);
+ }
+
+ internal TraceLoggingTypeInfo(
+ Type dataType,
+ string name,
+ EventLevel level,
+ EventOpcode opcode,
+ EventKeywords keywords,
+ EventTags tags)
+ {
+ if (dataType == null)
+ {
+ throw new ArgumentNullException(nameof(dataType));
+ }
+
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ Statics.CheckName(name);
+
+ this.name = name;
+ this.keywords = keywords;
+ this.level = level;
+ this.opcode = opcode;
+ this.tags = tags;
+ this.dataType = dataType;
+ this.propertyValueFactory = PropertyValue.GetFactory(dataType);
+ }
+
+ /// <summary>
+ /// Gets the name to use for the event if this type is the top-level type,
+ /// or the name to use for an implicitly-named field.
+ /// Never null.
+ /// </summary>
+ public string Name
+ {
+ get { return this.name; }
+ }
+
+ /// <summary>
+ /// Gets the event level associated with this type. Any value in the range 0..255
+ /// is an associated event level. Any value outside the range 0..255 is invalid and
+ /// indicates that this type has no associated event level.
+ /// </summary>
+ public EventLevel Level
+ {
+ get { return this.level; }
+ }
+
+ /// <summary>
+ /// Gets the event opcode associated with this type. Any value in the range 0..255
+ /// is an associated event opcode. Any value outside the range 0..255 is invalid and
+ /// indicates that this type has no associated event opcode.
+ /// </summary>
+ public EventOpcode Opcode
+ {
+ get { return this.opcode; }
+ }
+
+ /// <summary>
+ /// Gets the keyword(s) associated with this type.
+ /// </summary>
+ public EventKeywords Keywords
+ {
+ get { return this.keywords; }
+ }
+
+ /// <summary>
+ /// Gets the event tags associated with this type.
+ /// </summary>
+ public EventTags Tags
+ {
+ get { return this.tags; }
+ }
+
+ internal Type DataType
+ {
+ get { return this.dataType; }
+ }
+
+ internal Func<object, PropertyValue> PropertyValueFactory
+ {
+ get { return this.propertyValueFactory; }
+ }
+
+ /// <summary>
+ /// When overridden by a derived class, writes the metadata (schema) for
+ /// this type. Note that the sequence of operations in WriteMetadata should be
+ /// essentially identical to the sequence of operations in
+ /// WriteData/WriteObjectData. Otherwise, the metadata and data will not match,
+ /// which may cause trouble when decoding the event.
+ /// </summary>
+ /// <param name="collector">
+ /// The object that collects metadata for this object's type. Metadata is written
+ /// by calling methods on the collector object. Note that if the type contains
+ /// sub-objects, the implementation of this method may need to call the
+ /// WriteMetadata method for the type of the sub-object, e.g. by calling
+ /// TraceLoggingTypeInfo&lt;SubType&gt;.Instance.WriteMetadata(...).
+ /// </param>
+ /// <param name="name">
+ /// The name of the property that contains an object of this type, or null if this
+ /// object is being written as a top-level object of an event. Typical usage
+ /// is to pass this value to collector.AddGroup.
+ /// </param>
+ /// <param name="format">
+ /// The format attribute for the field that contains an object of this type.
+ /// </param>
+ public abstract void WriteMetadata(
+ TraceLoggingMetadataCollector collector,
+ string name,
+ EventFieldFormat format);
+
+ /// <summary>
+ /// Refer to TraceLoggingTypeInfo.WriteObjectData for information about this
+ /// method.
+ /// </summary>
+ /// <param name="collector">
+ /// Refer to TraceLoggingTypeInfo.WriteObjectData for information about this
+ /// method.
+ /// </param>
+ /// <param name="value">
+ /// Refer to TraceLoggingTypeInfo.WriteObjectData for information about this
+ /// method.
+ /// </param>
+ public abstract void WriteData(
+ TraceLoggingDataCollector collector,
+ PropertyValue value);
+
+ /// <summary>
+ /// Fetches the event parameter data for internal serialization.
+ /// </summary>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public virtual object GetData(object value)
+ {
+ return value;
+ }
+
+ [ThreadStatic] // per-thread cache to avoid synchronization
+ private static Dictionary<Type, TraceLoggingTypeInfo> threadCache;
+
+ public static TraceLoggingTypeInfo GetInstance(Type type, List<Type> recursionCheck)
+ {
+ var cache = threadCache ?? (threadCache = new Dictionary<Type, TraceLoggingTypeInfo>());
+
+ TraceLoggingTypeInfo instance;
+ if (!cache.TryGetValue(type, out instance))
+ {
+ if (recursionCheck == null)
+ recursionCheck = new List<Type>();
+ var recursionCheckCount = recursionCheck.Count;
+ instance = Statics.CreateDefaultTypeInfo(type, recursionCheck);
+ cache[type] = instance;
+ recursionCheck.RemoveRange(recursionCheckCount, recursionCheck.Count - recursionCheckCount);
+ }
+ return instance;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs
new file mode 100644
index 0000000000..42cdde5f6a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs
@@ -0,0 +1,103 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ /// <summary>
+ /// TraceLogging: stores the per-type information obtained by reflecting over a type.
+ /// </summary>
+ internal sealed class TypeAnalysis
+ {
+ internal readonly PropertyAnalysis[] properties;
+ internal readonly string name;
+ internal readonly EventKeywords keywords;
+ internal readonly EventLevel level = (EventLevel)(-1);
+ internal readonly EventOpcode opcode = (EventOpcode)(-1);
+ internal readonly EventTags tags;
+
+ public TypeAnalysis(
+ Type dataType,
+ EventDataAttribute eventAttrib,
+ List<Type> recursionCheck)
+ {
+ var propertyInfos = Statics.GetProperties(dataType);
+ var propertyList = new List<PropertyAnalysis>();
+
+ foreach (var propertyInfo in propertyInfos)
+ {
+ if (Statics.HasCustomAttribute(propertyInfo, typeof(EventIgnoreAttribute)))
+ {
+ continue;
+ }
+
+ if (!propertyInfo.CanRead ||
+ propertyInfo.GetIndexParameters().Length != 0)
+ {
+ continue;
+ }
+
+ MethodInfo getterInfo = Statics.GetGetMethod(propertyInfo);
+ if (getterInfo == null)
+ {
+ continue;
+ }
+
+ if (getterInfo.IsStatic || !getterInfo.IsPublic)
+ {
+ continue;
+ }
+
+ var propertyType = propertyInfo.PropertyType;
+ var propertyTypeInfo = TraceLoggingTypeInfo.GetInstance(propertyType, recursionCheck);
+ var fieldAttribute = Statics.GetCustomAttribute<EventFieldAttribute>(propertyInfo);
+
+ string propertyName =
+ fieldAttribute != null && fieldAttribute.Name != null
+ ? fieldAttribute.Name
+ : Statics.ShouldOverrideFieldName(propertyInfo.Name)
+ ? propertyTypeInfo.Name
+ : propertyInfo.Name;
+ propertyList.Add(new PropertyAnalysis(
+ propertyName,
+ propertyInfo,
+ propertyTypeInfo,
+ fieldAttribute));
+ }
+
+ this.properties = propertyList.ToArray();
+
+ foreach (var property in this.properties)
+ {
+ var typeInfo = property.typeInfo;
+ this.level = (EventLevel)Statics.Combine((int)typeInfo.Level, (int)this.level);
+ this.opcode = (EventOpcode)Statics.Combine((int)typeInfo.Opcode, (int)this.opcode);
+ this.keywords |= typeInfo.Keywords;
+ this.tags |= typeInfo.Tags;
+ }
+
+ if (eventAttrib != null)
+ {
+ this.level = (EventLevel)Statics.Combine((int)eventAttrib.Level, (int)this.level);
+ this.opcode = (EventOpcode)Statics.Combine((int)eventAttrib.Opcode, (int)this.opcode);
+ this.keywords |= eventAttrib.Keywords;
+ this.tags |= eventAttrib.Tags;
+ this.name = eventAttrib.Name;
+ }
+
+ if (this.name == null)
+ {
+ this.name = dataType.Name;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/Winmeta.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/Winmeta.cs
new file mode 100644
index 0000000000..c60ca5b365
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/Winmeta.cs
@@ -0,0 +1,187 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+** Purpose:
+** Contains eventing constants defined by the Windows
+** environment.
+**
+============================================================*/
+#if ES_BUILD_STANDALONE
+#define FEATURE_MANAGED_ETW_CHANNELS
+#endif
+
+#if ES_BUILD_STANDALONE
+namespace Microsoft.Diagnostics.Tracing
+#else
+namespace System.Diagnostics.Tracing
+#endif
+{
+ using System;
+
+ /// <summary>
+ /// WindowsEventLevel. Custom values must be in the range from 16 through 255
+ /// </summary>
+ public enum EventLevel
+ {
+ /// <summary>
+ /// Log always
+ /// </summary>
+ LogAlways = 0,
+ /// <summary>
+ /// Only critical errors
+ /// </summary>
+ Critical,
+ /// <summary>
+ /// All errors, including previous levels
+ /// </summary>
+ Error,
+ /// <summary>
+ /// All warnings, including previous levels
+ /// </summary>
+ Warning,
+ /// <summary>
+ /// All informational events, including previous levels
+ /// </summary>
+ Informational,
+ /// <summary>
+ /// All events, including previous levels
+ /// </summary>
+ Verbose
+ }
+ /// <summary>
+ /// WindowsEventTask. Custom values must be in the range from 1 through 65534
+ /// </summary>
+ public enum EventTask
+ {
+ /// <summary>
+ /// Undefined task
+ /// </summary>
+ None = 0
+ }
+ /// <summary>
+ /// EventOpcode. Custom values must be in the range from 11 through 239
+ /// </summary>
+ public enum EventOpcode
+ {
+ /// <summary>
+ /// An informational event
+ /// </summary>
+ Info = 0,
+ /// <summary>
+ /// An activity start event
+ /// </summary>
+ Start,
+ /// <summary>
+ /// An activity end event
+ /// </summary>
+ Stop,
+ /// <summary>
+ /// A trace collection start event
+ /// </summary>
+ DataCollectionStart,
+ /// <summary>
+ /// A trace collection end event
+ /// </summary>
+ DataCollectionStop,
+ /// <summary>
+ /// An extensional event
+ /// </summary>
+ Extension,
+ /// <summary>
+ /// A reply event
+ /// </summary>
+ Reply,
+ /// <summary>
+ /// An event representing the activity resuming from the suspension
+ /// </summary>
+ Resume,
+ /// <summary>
+ /// An event representing the activity is suspended, pending another activity's completion
+ /// </summary>
+ Suspend,
+ /// <summary>
+ /// An event representing the activity is transferred to another component, and can continue to work
+ /// </summary>
+ Send,
+ /// <summary>
+ /// An event representing receiving an activity transfer from another component
+ /// </summary>
+ Receive = 240
+ }
+
+ // Added for CLR V4
+ /// <summary>
+ /// EventChannel. Custom values must be in the range from 16 through 255. Currently only predefined values allowed.
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32", Justification = "Backwards compatibility")]
+ public enum EventChannel : byte
+ {
+ /// <summary>
+ /// No channel
+ /// </summary>
+ None = 0,
+ // Channels 1 - 15 are reserved...
+ /// <summary>The admin channel</summary>
+ Admin = 16,
+ /// <summary>The operational channel</summary>
+ Operational = 17,
+ /// <summary>The analytic channel</summary>
+ Analytic = 18,
+ /// <summary>The debug channel</summary>
+ Debug = 19,
+
+ };
+
+ /// <summary>
+ /// EventOpcode
+ /// </summary>
+ [Flags]
+ public enum EventKeywords : long
+ {
+ /// <summary>
+ /// No events.
+ /// </summary>
+ None = 0x0,
+ /// <summary>
+ /// All Events
+ /// </summary>
+ All = ~0,
+ /// <summary>
+ /// Telemetry events
+ /// </summary>
+ MicrosoftTelemetry = 0x02000000000000,
+ /// <summary>
+ /// WDI context events
+ /// </summary>
+ WdiContext = 0x02000000000000,
+ /// <summary>
+ /// WDI diagnostic events
+ /// </summary>
+ WdiDiagnostic = 0x04000000000000,
+ /// <summary>
+ /// SQM events
+ /// </summary>
+ Sqm = 0x08000000000000,
+ /// <summary>
+ /// Failed security audits
+ /// </summary>
+ AuditFailure = 0x10000000000000,
+ /// <summary>
+ /// Successful security audits
+ /// </summary>
+ AuditSuccess = 0x20000000000000,
+ /// <summary>
+ /// Transfer events where the related Activity ID is a computed value and not a GUID
+ /// N.B. The correct value for this field is 0x40000000000000.
+ /// </summary>
+ CorrelationHint = 0x10000000000000,
+ /// <summary>
+ /// Events raised using classic eventlog API
+ /// </summary>
+ EventLogClassic = 0x80000000000000
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/DivideByZeroException.cs b/src/System.Private.CoreLib/shared/System/DivideByZeroException.cs
new file mode 100644
index 0000000000..b309695ff3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DivideByZeroException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for bad arithmetic conditions!
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class DivideByZeroException : ArithmeticException
+ {
+ public DivideByZeroException()
+ : base(SR.Arg_DivideByZero)
+ {
+ HResult = HResults.COR_E_DIVIDEBYZERO;
+ }
+
+ public DivideByZeroException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_DIVIDEBYZERO;
+ }
+
+ public DivideByZeroException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_DIVIDEBYZERO;
+ }
+
+ protected DivideByZeroException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/DllNotFoundException.cs b/src/System.Private.CoreLib/shared/System/DllNotFoundException.cs
new file mode 100644
index 0000000000..14fb50d9c5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DllNotFoundException.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+** Class: DllNotFoundException
+**
+**
+** Purpose: The exception class for some failed P/Invoke calls.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class DllNotFoundException : TypeLoadException
+ {
+ public DllNotFoundException()
+ : base(SR.Arg_DllNotFoundException)
+ {
+ HResult = HResults.COR_E_DLLNOTFOUND;
+ }
+
+ public DllNotFoundException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_DLLNOTFOUND;
+ }
+
+ public DllNotFoundException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_DLLNOTFOUND;
+ }
+
+ protected DllNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Double.cs b/src/System.Private.CoreLib/shared/System/Double.cs
new file mode 100644
index 0000000000..e466935953
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Double.cs
@@ -0,0 +1,453 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: A representation of an IEEE double precision
+** floating point number.
+**
+**
+===========================================================*/
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Double : IComparable, IConvertible, IFormattable, IComparable<Double>, IEquatable<Double>, ISpanFormattable
+ {
+ private double m_value; // Do not rename (binary serialization)
+
+ //
+ // Public Constants
+ //
+ public const double MinValue = -1.7976931348623157E+308;
+ public const double MaxValue = 1.7976931348623157E+308;
+
+ // Note Epsilon should be a double whose hex representation is 0x1
+ // on little endian machines.
+ public const double Epsilon = 4.9406564584124654E-324;
+ public const double NegativeInfinity = (double)-1.0 / (double)(0.0);
+ public const double PositiveInfinity = (double)1.0 / (double)(0.0);
+ public const double NaN = (double)0.0 / (double)0.0;
+
+ // We use this explicit definition to avoid the confusion between 0.0 and -0.0.
+ internal const double NegativeZero = -0.0;
+
+ /// <summary>Determines whether the specified value is finite (zero, subnormal, or normal).</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsFinite(double d)
+ {
+ var bits = BitConverter.DoubleToInt64Bits(d);
+ return (bits & 0x7FFFFFFFFFFFFFFF) < 0x7FF0000000000000;
+ }
+
+ /// <summary>Determines whether the specified value is infinite.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsInfinity(double d)
+ {
+ var bits = BitConverter.DoubleToInt64Bits(d);
+ return (bits & 0x7FFFFFFFFFFFFFFF) == 0x7FF0000000000000;
+ }
+
+ /// <summary>Determines whether the specified value is NaN.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsNaN(double d)
+ {
+ var bits = BitConverter.DoubleToInt64Bits(d);
+ return (bits & 0x7FFFFFFFFFFFFFFF) > 0x7FF0000000000000;
+ }
+
+ /// <summary>Determines whether the specified value is negative.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsNegative(double d)
+ {
+ var bits = unchecked((ulong)BitConverter.DoubleToInt64Bits(d));
+ return (bits & 0x8000000000000000) == 0x8000000000000000;
+ }
+
+ /// <summary>Determines whether the specified value is negative infinity.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsNegativeInfinity(double d)
+ {
+ return (d == double.NegativeInfinity);
+ }
+
+ /// <summary>Determines whether the specified value is normal.</summary>
+ [NonVersionable]
+ // This is probably not worth inlining, it has branches and should be rarely called
+ public static unsafe bool IsNormal(double d)
+ {
+ var bits = BitConverter.DoubleToInt64Bits(d);
+ bits &= 0x7FFFFFFFFFFFFFFF;
+ return (bits < 0x7FF0000000000000) && (bits != 0) && ((bits & 0x7FF0000000000000) != 0);
+ }
+
+ /// <summary>Determines whether the specified value is positive infinity.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsPositiveInfinity(double d)
+ {
+ return (d == double.PositiveInfinity);
+ }
+
+ /// <summary>Determines whether the specified value is subnormal.</summary>
+ [NonVersionable]
+ // This is probably not worth inlining, it has branches and should be rarely called
+ public static unsafe bool IsSubnormal(double d)
+ {
+ var bits = BitConverter.DoubleToInt64Bits(d);
+ bits &= 0x7FFFFFFFFFFFFFFF;
+ return (bits < 0x7FF0000000000000) && (bits != 0) && ((bits & 0x7FF0000000000000) == 0);
+ }
+
+ // Compares this object to another object, returning an instance of System.Relation.
+ // Null is considered less than any instance.
+ //
+ // If object is not of type Double, this method throws an ArgumentException.
+ //
+ // Returns a value less than zero if this object
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (value is Double)
+ {
+ double d = (double)value;
+ if (m_value < d) return -1;
+ if (m_value > d) return 1;
+ if (m_value == d) return 0;
+
+ // At least one of the values is NaN.
+ if (IsNaN(m_value))
+ return (IsNaN(d) ? 0 : -1);
+ else
+ return 1;
+ }
+ throw new ArgumentException(SR.Arg_MustBeDouble);
+ }
+
+ public int CompareTo(Double value)
+ {
+ if (m_value < value) return -1;
+ if (m_value > value) return 1;
+ if (m_value == value) return 0;
+
+ // At least one of the values is NaN.
+ if (IsNaN(m_value))
+ return (IsNaN(value) ? 0 : -1);
+ else
+ return 1;
+ }
+
+ // True if obj is another Double with the same value as the current instance. This is
+ // a method of object equality, that only returns true if obj is also a double.
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is Double))
+ {
+ return false;
+ }
+ double temp = ((Double)obj).m_value;
+ // This code below is written this way for performance reasons i.e the != and == check is intentional.
+ if (temp == m_value)
+ {
+ return true;
+ }
+ return IsNaN(temp) && IsNaN(m_value);
+ }
+
+ [NonVersionable]
+ public static bool operator ==(Double left, Double right)
+ {
+ return left == right;
+ }
+
+ [NonVersionable]
+ public static bool operator !=(Double left, Double right)
+ {
+ return left != right;
+ }
+
+ [NonVersionable]
+ public static bool operator <(Double left, Double right)
+ {
+ return left < right;
+ }
+
+ [NonVersionable]
+ public static bool operator >(Double left, Double right)
+ {
+ return left > right;
+ }
+
+ [NonVersionable]
+ public static bool operator <=(Double left, Double right)
+ {
+ return left <= right;
+ }
+
+ [NonVersionable]
+ public static bool operator >=(Double left, Double right)
+ {
+ return left >= right;
+ }
+
+ public bool Equals(Double obj)
+ {
+ if (obj == m_value)
+ {
+ return true;
+ }
+ return IsNaN(obj) && IsNaN(m_value);
+ }
+
+ //The hashcode for a double is the absolute value of the integer representation
+ //of that double.
+ //
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // 64-bit constants make the IL unusually large that makes the inliner to reject the method
+ public override int GetHashCode()
+ {
+ var bits = Unsafe.As<double, long>(ref m_value);
+
+ // Optimized check for IsNan() || IsZero()
+ if (((bits - 1) & 0x7FFFFFFFFFFFFFFF) >= 0x7FF0000000000000)
+ {
+ // Ensure that all NaNs and both zeros have the same hash code
+ bits &= 0x7FF0000000000000;
+ }
+
+ return unchecked((int)bits) ^ ((int)(bits >> 32));
+ }
+
+ public override String ToString()
+ {
+ return Number.FormatDouble(m_value, null, NumberFormatInfo.CurrentInfo);
+ }
+
+ public String ToString(String format)
+ {
+ return Number.FormatDouble(m_value, format, NumberFormatInfo.CurrentInfo);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatDouble(m_value, null, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return Number.FormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ return Number.TryFormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten);
+ }
+
+ public static double Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseDouble(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static double Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseDouble(s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static double Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseDouble(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static double Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseDouble(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ // Parses a double from a String in the given style. If
+ // a NumberFormatInfo isn't specified, the current culture's
+ // NumberFormatInfo is assumed.
+ //
+ // This method will not throw an OverflowException, but will return
+ // PositiveInfinity or NegativeInfinity for a number that is too
+ // large or too small.
+
+ public static double Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ return Number.ParseDouble(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+
+
+ public static bool TryParse(String s, out double result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, out double result)
+ {
+ return TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(String s, NumberStyles style, IFormatProvider provider, out double result)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out double result)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ return TryParse(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ private static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out double result)
+ {
+ bool success = Number.TryParseDouble(s, style, info, out result);
+ if (!success)
+ {
+ ReadOnlySpan<char> sTrim = s.Trim();
+ if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol))
+ {
+ result = PositiveInfinity;
+ }
+ else if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol))
+ {
+ result = NegativeInfinity;
+ }
+ else if (sTrim.EqualsOrdinal(info.NaNSymbol))
+ {
+ result = NaN;
+ }
+ else
+ {
+ return false; // We really failed
+ }
+ }
+ return true;
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Double;
+ }
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Double", "Char"));
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Double", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/DuplicateWaitObjectException.cs b/src/System.Private.CoreLib/shared/System/DuplicateWaitObjectException.cs
new file mode 100644
index 0000000000..77303846a3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/DuplicateWaitObjectException.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for duplicate objects in WaitAll/WaitAny.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The DuplicateWaitObjectException is thrown when an object
+ // appears more than once in the list of objects to WaitAll or WaitAny.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class DuplicateWaitObjectException : ArgumentException
+ {
+ private static volatile String s_duplicateWaitObjectMessage = null;
+
+ private static String DuplicateWaitObjectMessage
+ {
+ get
+ {
+ if (s_duplicateWaitObjectMessage == null)
+ s_duplicateWaitObjectMessage = SR.Arg_DuplicateWaitObjectException;
+ return s_duplicateWaitObjectMessage;
+ }
+ }
+
+ // Creates a new DuplicateWaitObjectException with its message
+ // string set to a default message.
+ public DuplicateWaitObjectException()
+ : base(DuplicateWaitObjectMessage)
+ {
+ HResult = HResults.COR_E_DUPLICATEWAITOBJECT;
+ }
+
+ public DuplicateWaitObjectException(String parameterName)
+ : base(DuplicateWaitObjectMessage, parameterName)
+ {
+ HResult = HResults.COR_E_DUPLICATEWAITOBJECT;
+ }
+
+ public DuplicateWaitObjectException(String parameterName, String message)
+ : base(message, parameterName)
+ {
+ HResult = HResults.COR_E_DUPLICATEWAITOBJECT;
+ }
+
+ public DuplicateWaitObjectException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_DUPLICATEWAITOBJECT;
+ }
+
+ protected DuplicateWaitObjectException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Empty.cs b/src/System.Private.CoreLib/shared/System/Empty.cs
new file mode 100644
index 0000000000..186b92078e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Empty.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+#if CORERT
+ public // Needs to be public so that Reflection.Core can see it.
+#else
+ internal
+#endif
+ sealed class Empty
+ {
+ private Empty()
+ {
+ }
+
+ public static readonly Empty Value = new Empty();
+
+ public override string ToString()
+ {
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/EntryPointNotFoundException.cs b/src/System.Private.CoreLib/shared/System/EntryPointNotFoundException.cs
new file mode 100644
index 0000000000..dac1cdb971
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/EntryPointNotFoundException.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: The exception class for some failed P/Invoke calls.
+**
+**
+=============================================================================*/
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class EntryPointNotFoundException : TypeLoadException
+ {
+ public EntryPointNotFoundException()
+ : base(SR.Arg_EntryPointNotFoundException)
+ {
+ HResult = HResults.COR_E_ENTRYPOINTNOTFOUND;
+ }
+
+ public EntryPointNotFoundException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ENTRYPOINTNOTFOUND;
+ }
+
+ public EntryPointNotFoundException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_ENTRYPOINTNOTFOUND;
+ }
+
+ protected EntryPointNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/EventArgs.cs b/src/System.Private.CoreLib/shared/System/EventArgs.cs
new file mode 100644
index 0000000000..f3561a8d0b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/EventArgs.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System
+{
+ // The base class for all event classes.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class EventArgs
+ {
+ public static readonly EventArgs Empty = new EventArgs();
+
+ public EventArgs()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/EventHandler.cs b/src/System.Private.CoreLib/shared/System/EventHandler.cs
new file mode 100644
index 0000000000..3d1cbfef26
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/EventHandler.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System
+{
+ public delegate void EventHandler(Object sender, EventArgs e);
+
+ public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e); // Removed TEventArgs constraint post-.NET 4
+}
diff --git a/src/System.Private.CoreLib/shared/System/ExecutionEngineException.cs b/src/System.Private.CoreLib/shared/System/ExecutionEngineException.cs
new file mode 100644
index 0000000000..5edd5cf19f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ExecutionEngineException.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+//
+/*=============================================================================
+**
+**
+**
+** Purpose: The exception class for misc execution engine exceptions.
+** Currently, its only used as a placeholder type when the EE
+** does a FailFast.
+**
+**
+=============================================================================*/
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Obsolete("This type previously indicated an unspecified fatal error in the runtime. The runtime no longer raises this exception so this type is obsolete.")]
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class ExecutionEngineException : SystemException
+ {
+ public ExecutionEngineException()
+ : base(SR.Arg_ExecutionEngineException)
+ {
+ HResult = HResults.COR_E_EXECUTIONENGINE;
+ }
+
+ public ExecutionEngineException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_EXECUTIONENGINE;
+ }
+
+ public ExecutionEngineException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_EXECUTIONENGINE;
+ }
+
+ internal ExecutionEngineException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/FieldAccessException.cs b/src/System.Private.CoreLib/shared/System/FieldAccessException.cs
new file mode 100644
index 0000000000..cb28264d61
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/FieldAccessException.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+** Purpose: The exception class for class loading failures.
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class FieldAccessException : MemberAccessException
+ {
+ public FieldAccessException()
+ : base(SR.Arg_FieldAccessException)
+ {
+ HResult = HResults.COR_E_FIELDACCESS;
+ }
+
+ public FieldAccessException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_FIELDACCESS;
+ }
+
+ public FieldAccessException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_FIELDACCESS;
+ }
+
+ protected FieldAccessException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/FlagsAttribute.cs b/src/System.Private.CoreLib/shared/System/FlagsAttribute.cs
new file mode 100644
index 0000000000..4f3ab36bfd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/FlagsAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+namespace System
+{
+ // Custom attribute to indicate that the enum
+ // should be treated as a bitfield (or set of flags).
+ // An IDE may use this information to provide a richer
+ // development experience.
+ [AttributeUsage(AttributeTargets.Enum, Inherited = false)]
+ public class FlagsAttribute : Attribute
+ {
+ public FlagsAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/FormatException.cs b/src/System.Private.CoreLib/shared/System/FormatException.cs
new file mode 100644
index 0000000000..b0e273369c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/FormatException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Exception to designate an illegal argument to FormatMessage.
+**
+**
+===========================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class FormatException : SystemException
+ {
+ public FormatException()
+ : base(SR.Arg_FormatException)
+ {
+ HResult = HResults.COR_E_FORMAT;
+ }
+
+ public FormatException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_FORMAT;
+ }
+
+ public FormatException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_FORMAT;
+ }
+
+ protected FormatException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/FormattableString.cs b/src/System.Private.CoreLib/shared/System/FormattableString.cs
new file mode 100644
index 0000000000..6369363b5d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/FormattableString.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: implementation of the FormattableString
+** class.
+**
+===========================================================*/
+
+namespace System
+{
+ /// <summary>
+ /// A composite format string along with the arguments to be formatted. An instance of this
+ /// type may result from the use of the C# or VB language primitive "interpolated string".
+ /// </summary>
+ public abstract class FormattableString : IFormattable
+ {
+ /// <summary>
+ /// The composite format string.
+ /// </summary>
+ public abstract string Format { get; }
+
+ /// <summary>
+ /// Returns an object array that contains zero or more objects to format. Clients should not
+ /// mutate the contents of the array.
+ /// </summary>
+ public abstract object[] GetArguments();
+
+ /// <summary>
+ /// The number of arguments to be formatted.
+ /// </summary>
+ public abstract int ArgumentCount { get; }
+
+ /// <summary>
+ /// Returns one argument to be formatted from argument position <paramref name="index"/>.
+ /// </summary>
+ public abstract object GetArgument(int index);
+
+ /// <summary>
+ /// Format to a string using the given culture.
+ /// </summary>
+ public abstract string ToString(IFormatProvider formatProvider);
+
+ string IFormattable.ToString(string ignored, IFormatProvider formatProvider)
+ {
+ return ToString(formatProvider);
+ }
+
+ /// <summary>
+ /// Format the given object in the invariant culture. This static method may be
+ /// imported in C# by
+ /// <code>
+ /// using static System.FormattableString;
+ /// </code>.
+ /// Within the scope
+ /// of that import directive an interpolated string may be formatted in the
+ /// invariant culture by writing, for example,
+ /// <code>
+ /// Invariant($"{{ lat = {latitude}; lon = {longitude} }}")
+ /// </code>
+ /// </summary>
+ public static string Invariant(FormattableString formattable)
+ {
+ if (formattable == null)
+ {
+ throw new ArgumentNullException(nameof(formattable));
+ }
+
+ return formattable.ToString(Globalization.CultureInfo.InvariantCulture);
+ }
+
+ public override string ToString()
+ {
+ return ToString(Globalization.CultureInfo.CurrentCulture);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Gen2GcCallback.cs b/src/System.Private.CoreLib/shared/System/Gen2GcCallback.cs
new file mode 100644
index 0000000000..2252681224
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Gen2GcCallback.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ /// <summary>
+ /// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once)
+ /// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care)
+ /// </summary>
+ internal sealed class Gen2GcCallback : CriticalFinalizerObject
+ {
+ private Gen2GcCallback()
+ : base()
+ {
+ }
+
+ /// <summary>
+ /// Schedule 'callback' to be called in the next GC. If the callback returns true it is
+ /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
+ ///
+ /// NOTE: This callback will be kept alive until either the callback function returns false,
+ /// or the target object dies.
+ /// </summary>
+ public static void Register(Func<object, bool> callback, object targetObj)
+ {
+ // Create a unreachable object that remembers the callback function and target object.
+ Gen2GcCallback gcCallback = new Gen2GcCallback();
+ gcCallback.Setup(callback, targetObj);
+ }
+
+ private Func<object, bool> _callback;
+ private GCHandle _weakTargetObj;
+
+ private void Setup(Func<object, bool> callback, object targetObj)
+ {
+ _callback = callback;
+ _weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak);
+ }
+
+ ~Gen2GcCallback()
+ {
+ // Check to see if the target object is still alive.
+ object targetObj = _weakTargetObj.Target;
+ if (targetObj == null)
+ {
+ // The target object is dead, so this callback object is no longer needed.
+ _weakTargetObj.Free();
+ return;
+ }
+
+ // Execute the callback method.
+ try
+ {
+ if (!_callback(targetObj))
+ {
+ // If the callback returns false, this callback object is no longer needed.
+ return;
+ }
+ }
+ catch
+ {
+ // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
+ }
+
+ // Resurrect ourselves by re-registering for finalization.
+ if (!Environment.HasShutdownStarted)
+ {
+ GC.ReRegisterForFinalize(this);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/BidiCategory.cs b/src/System.Private.CoreLib/shared/System/Globalization/BidiCategory.cs
new file mode 100644
index 0000000000..abe6950893
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/BidiCategory.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ internal enum BidiCategory
+ {
+ LeftToRight = 0,
+ LeftToRightEmbedding = 1,
+ LeftToRightOverride = 2,
+ RightToLeft = 3,
+ RightToLeftArabic = 4,
+ RightToLeftEmbedding = 5,
+ RightToLeftOverride = 6,
+ PopDirectionalFormat = 7,
+ EuropeanNumber = 8,
+ EuropeanNumberSeparator = 9,
+ EuropeanNumberTerminator = 10,
+ ArabicNumber = 11,
+ CommonNumberSeparator = 12,
+ NonSpacingMark = 13,
+ BoundaryNeutral = 14,
+ ParagraphSeparator = 15,
+ SegmentSeparator = 16,
+ Whitespace = 17,
+ OtherNeutrals = 18,
+ LeftToRightIsolate = 19,
+ RightToLeftIsolate = 20,
+ FirstStrongIsolate = 21,
+ PopDirectionIsolate = 22,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/Calendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/Calendar.cs
new file mode 100644
index 0000000000..49ad597ceb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/Calendar.cs
@@ -0,0 +1,840 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.Serialization;
+
+namespace System.Globalization
+{
+ // This abstract class represents a calendar. A calendar reckons time in
+ // divisions such as weeks, months and years. The number, length and start of
+ // the divisions vary in each calendar.
+ //
+ // Any instant in time can be represented as an n-tuple of numeric values using
+ // a particular calendar. For example, the next vernal equinox occurs at (0.0, 0
+ // , 46, 8, 20, 3, 1999) in the Gregorian calendar. An implementation of
+ // Calendar can map any DateTime value to such an n-tuple and vice versa. The
+ // DateTimeFormat class can map between such n-tuples and a textual
+ // representation such as "8:46 AM March 20th 1999 AD".
+ //
+ // Most calendars identify a year which begins the current era. There may be any
+ // number of previous eras. The Calendar class identifies the eras as enumerated
+ // integers where the current era (CurrentEra) has the value zero.
+ //
+ // For consistency, the first unit in each interval, e.g. the first month, is
+ // assigned the value one.
+ // The calculation of hour/minute/second is moved to Calendar from GregorianCalendar,
+ // since most of the calendars (or all?) have the same way of calcuating hour/minute/second.
+
+ public abstract class Calendar : ICloneable
+ {
+ // Number of 100ns (10E-7 second) ticks per time unit
+ internal const long TicksPerMillisecond = 10000;
+ internal const long TicksPerSecond = TicksPerMillisecond * 1000;
+ internal const long TicksPerMinute = TicksPerSecond * 60;
+ internal const long TicksPerHour = TicksPerMinute * 60;
+ internal const long TicksPerDay = TicksPerHour * 24;
+
+ // Number of milliseconds per time unit
+ internal const int MillisPerSecond = 1000;
+ internal const int MillisPerMinute = MillisPerSecond * 60;
+ internal const int MillisPerHour = MillisPerMinute * 60;
+ internal const int MillisPerDay = MillisPerHour * 24;
+
+ // Number of days in a non-leap year
+ internal const int DaysPerYear = 365;
+ // Number of days in 4 years
+ internal const int DaysPer4Years = DaysPerYear * 4 + 1;
+ // Number of days in 100 years
+ internal const int DaysPer100Years = DaysPer4Years * 25 - 1;
+ // Number of days in 400 years
+ internal const int DaysPer400Years = DaysPer100Years * 4 + 1;
+
+ // Number of days from 1/1/0001 to 1/1/10000
+ internal const int DaysTo10000 = DaysPer400Years * 25 - 366;
+
+ internal const long MaxMillis = (long)DaysTo10000 * MillisPerDay;
+
+ private int _currentEraValue = -1;
+
+ private bool _isReadOnly = false;
+
+ // The minimum supported DateTime range for the calendar.
+
+ public virtual DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ // The maximum supported DateTime range for the calendar.
+
+ public virtual DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ public virtual CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.Unknown;
+ }
+ }
+
+ protected Calendar()
+ {
+ //Do-nothing constructor.
+ }
+
+ ///
+ // This can not be abstract, otherwise no one can create a subclass of Calendar.
+ //
+ internal virtual CalendarId ID
+ {
+ get
+ {
+ return CalendarId.UNINITIALIZED_VALUE;
+ }
+ }
+
+ ///
+ // Return the Base calendar ID for calendars that didn't have defined data in calendarData
+ //
+
+ internal virtual CalendarId BaseCalendarID
+ {
+ get { return ID; }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsReadOnly
+ //
+ // Detect if the object is readonly.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public bool IsReadOnly
+ {
+ get { return (_isReadOnly); }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Clone
+ //
+ // Is the implementation of ICloneable.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual object Clone()
+ {
+ object o = MemberwiseClone();
+ ((Calendar)o).SetReadOnlyState(false);
+ return (o);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ReadOnly
+ //
+ // Create a cloned readonly instance or return the input one if it is
+ // readonly.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public static Calendar ReadOnly(Calendar calendar)
+ {
+ if (calendar == null) { throw new ArgumentNullException(nameof(calendar)); }
+ if (calendar.IsReadOnly) { return (calendar); }
+
+ Calendar clonedCalendar = (Calendar)(calendar.MemberwiseClone());
+ clonedCalendar.SetReadOnlyState(true);
+
+ return (clonedCalendar);
+ }
+
+ internal void VerifyWritable()
+ {
+ if (_isReadOnly)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ }
+ }
+
+ internal void SetReadOnlyState(bool readOnly)
+ {
+ _isReadOnly = readOnly;
+ }
+
+
+ /*=================================CurrentEraValue==========================
+ **Action: This is used to convert CurretEra(0) to an appropriate era value.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ **Notes:
+ ** The value is from calendar.nlp.
+ ============================================================================*/
+
+ internal virtual int CurrentEraValue
+ {
+ get
+ {
+ // The following code assumes that the current era value can not be -1.
+ if (_currentEraValue == -1)
+ {
+ Debug.Assert(BaseCalendarID != CalendarId.UNINITIALIZED_VALUE, "[Calendar.CurrentEraValue] Expected a real calendar ID");
+ _currentEraValue = CalendarData.GetCalendarData(BaseCalendarID).iCurrentEra;
+ }
+ return (_currentEraValue);
+ }
+ }
+
+ // The current era for a calendar.
+
+ public const int CurrentEra = 0;
+
+ internal int twoDigitYearMax = -1;
+
+ internal static void CheckAddResult(long ticks, DateTime minValue, DateTime maxValue)
+ {
+ if (ticks < minValue.Ticks || ticks > maxValue.Ticks)
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.InvariantCulture, SR.Format(SR.Argument_ResultCalendarRange,
+ minValue, maxValue)));
+ }
+ }
+
+ internal DateTime Add(DateTime time, double value, int scale)
+ {
+ // From ECMA CLI spec, Partition III, section 3.27:
+ //
+ // If overflow occurs converting a floating-point type to an integer, or if the floating-point value
+ // being converted to an integer is a NaN, the value returned is unspecified.
+ //
+ // Based upon this, this method should be performing the comparison against the double
+ // before attempting a cast. Otherwise, the result is undefined.
+ double tempMillis = (value * scale + (value >= 0 ? 0.5 : -0.5));
+ if (!((tempMillis > -(double)MaxMillis) && (tempMillis < (double)MaxMillis)))
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_AddValue);
+ }
+
+ long millis = (long)tempMillis;
+ long ticks = time.Ticks + millis * TicksPerMillisecond;
+ CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // milliseconds to the specified DateTime. The result is computed by rounding
+ // the number of milliseconds given by value to the nearest integer,
+ // and adding that interval to the specified DateTime. The value
+ // argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddMilliseconds(DateTime time, double milliseconds)
+ {
+ return (Add(time, milliseconds, 1));
+ }
+
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // days to the specified DateTime. The result is computed by rounding the
+ // fractional number of days given by value to the nearest
+ // millisecond, and adding that interval to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddDays(DateTime time, int days)
+ {
+ return (Add(time, days, MillisPerDay));
+ }
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // hours to the specified DateTime. The result is computed by rounding the
+ // fractional number of hours given by value to the nearest
+ // millisecond, and adding that interval to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddHours(DateTime time, int hours)
+ {
+ return (Add(time, hours, MillisPerHour));
+ }
+
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // minutes to the specified DateTime. The result is computed by rounding the
+ // fractional number of minutes given by value to the nearest
+ // millisecond, and adding that interval to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddMinutes(DateTime time, int minutes)
+ {
+ return (Add(time, minutes, MillisPerMinute));
+ }
+
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+ public abstract DateTime AddMonths(DateTime time, int months);
+
+ // Returns the DateTime resulting from adding a number of
+ // seconds to the specified DateTime. The result is computed by rounding the
+ // fractional number of seconds given by value to the nearest
+ // millisecond, and adding that interval to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddSeconds(DateTime time, int seconds)
+ {
+ return Add(time, seconds, MillisPerSecond);
+ }
+
+ // Returns the DateTime resulting from adding a number of
+ // weeks to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddWeeks(DateTime time, int weeks)
+ {
+ return (AddDays(time, weeks * 7));
+ }
+
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+ public abstract DateTime AddYears(DateTime time, int years);
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+ public abstract int GetDayOfMonth(DateTime time);
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public abstract DayOfWeek GetDayOfWeek(DateTime time);
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+ public abstract int GetDayOfYear(DateTime time);
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+ public virtual int GetDaysInMonth(int year, int month)
+ {
+ return (GetDaysInMonth(year, month, CurrentEra));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments for the specified era.
+ //
+
+ public abstract int GetDaysInMonth(int year, int month, int era);
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public virtual int GetDaysInYear(int year)
+ {
+ return (GetDaysInYear(year, CurrentEra));
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public abstract int GetDaysInYear(int year, int era);
+
+ // Returns the era for the specified DateTime value.
+
+ public abstract int GetEra(DateTime time);
+
+ /*=================================Eras==========================
+ **Action: Get the list of era values.
+ **Returns: The int array of the era names supported in this calendar.
+ ** null if era is not used.
+ **Arguments: None.
+ **Exceptions: None.
+ ============================================================================*/
+
+
+ public abstract int[] Eras
+ {
+ get;
+ }
+
+
+ // Returns the hour part of the specified DateTime. The returned value is an
+ // integer between 0 and 23.
+ //
+
+ public virtual int GetHour(DateTime time)
+ {
+ return ((int)((time.Ticks / TicksPerHour) % 24));
+ }
+
+ // Returns the millisecond part of the specified DateTime. The returned value
+ // is an integer between 0 and 999.
+ //
+
+ public virtual double GetMilliseconds(DateTime time)
+ {
+ return (double)((time.Ticks / TicksPerMillisecond) % 1000);
+ }
+
+ // Returns the minute part of the specified DateTime. The returned value is
+ // an integer between 0 and 59.
+ //
+
+ public virtual int GetMinute(DateTime time)
+ {
+ return ((int)((time.Ticks / TicksPerMinute) % 60));
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+ public abstract int GetMonth(DateTime time);
+
+ // Returns the number of months in the specified year in the current era.
+
+ public virtual int GetMonthsInYear(int year)
+ {
+ return (GetMonthsInYear(year, CurrentEra));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public abstract int GetMonthsInYear(int year, int era);
+
+ // Returns the second part of the specified DateTime. The returned value is
+ // an integer between 0 and 59.
+ //
+
+ public virtual int GetSecond(DateTime time)
+ {
+ return ((int)((time.Ticks / TicksPerSecond) % 60));
+ }
+
+ /*=================================GetFirstDayWeekOfYear==========================
+ **Action: Get the week of year using the FirstDay rule.
+ **Returns: the week of year.
+ **Arguments:
+ ** time
+ ** firstDayOfWeek the first day of week (0=Sunday, 1=Monday, ... 6=Saturday)
+ **Notes:
+ ** The CalendarWeekRule.FirstDay rule: Week 1 begins on the first day of the year.
+ ** Assume f is the specifed firstDayOfWeek,
+ ** and n is the day of week for January 1 of the specified year.
+ ** Assign offset = n - f;
+ ** Case 1: offset = 0
+ ** E.g.
+ ** f=1
+ ** weekday 0 1 2 3 4 5 6 0 1
+ ** date 1/1
+ ** week# 1 2
+ ** then week of year = (GetDayOfYear(time) - 1) / 7 + 1
+ **
+ ** Case 2: offset < 0
+ ** e.g.
+ ** n=1 f=3
+ ** weekday 0 1 2 3 4 5 6 0
+ ** date 1/1
+ ** week# 1 2
+ ** This means that the first week actually starts 5 days before 1/1.
+ ** So week of year = (GetDayOfYear(time) + (7 + offset) - 1) / 7 + 1
+ ** Case 3: offset > 0
+ ** e.g.
+ ** f=0 n=2
+ ** weekday 0 1 2 3 4 5 6 0 1 2
+ ** date 1/1
+ ** week# 1 2
+ ** This means that the first week actually starts 2 days before 1/1.
+ ** So Week of year = (GetDayOfYear(time) + offset - 1) / 7 + 1
+ ============================================================================*/
+
+ internal int GetFirstDayWeekOfYear(DateTime time, int firstDayOfWeek)
+ {
+ int dayOfYear = GetDayOfYear(time) - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
+ // Calculate the day of week for the first day of the year.
+ // dayOfWeek - (dayOfYear % 7) is the day of week for the first day of this year. Note that
+ // this value can be less than 0. It's fine since we are making it positive again in calculating offset.
+ int dayForJan1 = (int)GetDayOfWeek(time) - (dayOfYear % 7);
+ int offset = (dayForJan1 - firstDayOfWeek + 14) % 7;
+ Debug.Assert(offset >= 0, "Calendar.GetFirstDayWeekOfYear(): offset >= 0");
+ return ((dayOfYear + offset) / 7 + 1);
+ }
+
+ private int GetWeekOfYearFullDays(DateTime time, int firstDayOfWeek, int fullDays)
+ {
+ int dayForJan1;
+ int offset;
+ int day;
+
+ int dayOfYear = GetDayOfYear(time) - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
+ //
+ // Calculate the number of days between the first day of year (1/1) and the first day of the week.
+ // This value will be a positive value from 0 ~ 6. We call this value as "offset".
+ //
+ // If offset is 0, it means that the 1/1 is the start of the first week.
+ // Assume the first day of the week is Monday, it will look like this:
+ // Sun Mon Tue Wed Thu Fri Sat
+ // 12/31 1/1 1/2 1/3 1/4 1/5 1/6
+ // +--> First week starts here.
+ //
+ // If offset is 1, it means that the first day of the week is 1 day ahead of 1/1.
+ // Assume the first day of the week is Monday, it will look like this:
+ // Sun Mon Tue Wed Thu Fri Sat
+ // 1/1 1/2 1/3 1/4 1/5 1/6 1/7
+ // +--> First week starts here.
+ //
+ // If offset is 2, it means that the first day of the week is 2 days ahead of 1/1.
+ // Assume the first day of the week is Monday, it will look like this:
+ // Sat Sun Mon Tue Wed Thu Fri Sat
+ // 1/1 1/2 1/3 1/4 1/5 1/6 1/7 1/8
+ // +--> First week starts here.
+
+
+
+ // Day of week is 0-based.
+ // Get the day of week for 1/1. This can be derived from the day of week of the target day.
+ // Note that we can get a negative value. It's ok since we are going to make it a positive value when calculating the offset.
+ dayForJan1 = (int)GetDayOfWeek(time) - (dayOfYear % 7);
+
+ // Now, calculate the offset. Subtract the first day of week from the dayForJan1. And make it a positive value.
+ offset = (firstDayOfWeek - dayForJan1 + 14) % 7;
+ if (offset != 0 && offset >= fullDays)
+ {
+ //
+ // If the offset is greater than the value of fullDays, it means that
+ // the first week of the year starts on the week where Jan/1 falls on.
+ //
+ offset -= 7;
+ }
+ //
+ // Calculate the day of year for specified time by taking offset into account.
+ //
+ day = dayOfYear - offset;
+ if (day >= 0)
+ {
+ //
+ // If the day of year value is greater than zero, get the week of year.
+ //
+ return (day / 7 + 1);
+ }
+ //
+ // Otherwise, the specified time falls on the week of previous year.
+ // Call this method again by passing the last day of previous year.
+ //
+ // the last day of the previous year may "underflow" to no longer be a valid date time for
+ // this calendar if we just subtract so we need the subclass to provide us with
+ // that information
+ if (time <= MinSupportedDateTime.AddDays(dayOfYear))
+ {
+ return GetWeekOfYearOfMinSupportedDateTime(firstDayOfWeek, fullDays);
+ }
+ return (GetWeekOfYearFullDays(time.AddDays(-(dayOfYear + 1)), firstDayOfWeek, fullDays));
+ }
+
+ private int GetWeekOfYearOfMinSupportedDateTime(int firstDayOfWeek, int minimumDaysInFirstWeek)
+ {
+ int dayOfYear = GetDayOfYear(MinSupportedDateTime) - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
+ int dayOfWeekOfFirstOfYear = (int)GetDayOfWeek(MinSupportedDateTime) - dayOfYear % 7;
+
+ // Calculate the offset (how many days from the start of the year to the start of the week)
+ int offset = (firstDayOfWeek + 7 - dayOfWeekOfFirstOfYear) % 7;
+ if (offset == 0 || offset >= minimumDaysInFirstWeek)
+ {
+ // First of year falls in the first week of the year
+ return 1;
+ }
+
+ int daysInYearBeforeMinSupportedYear = DaysInYearBeforeMinSupportedYear - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
+ int dayOfWeekOfFirstOfPreviousYear = dayOfWeekOfFirstOfYear - 1 - (daysInYearBeforeMinSupportedYear % 7);
+
+ // starting from first day of the year, how many days do you have to go forward
+ // before getting to the first day of the week?
+ int daysInInitialPartialWeek = (firstDayOfWeek - dayOfWeekOfFirstOfPreviousYear + 14) % 7;
+ int day = daysInYearBeforeMinSupportedYear - daysInInitialPartialWeek;
+ if (daysInInitialPartialWeek >= minimumDaysInFirstWeek)
+ {
+ // If the offset is greater than the minimum Days in the first week, it means that
+ // First of year is part of the first week of the year even though it is only a partial week
+ // add another week
+ day += 7;
+ }
+
+ return (day / 7 + 1);
+ }
+
+ // it would be nice to make this abstract but we can't since that would break previous implementations
+ protected virtual int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ return 365;
+ }
+ }
+
+
+ // Returns the week of year for the specified DateTime. The returned value is an
+ // integer between 1 and 53.
+ //
+
+ public virtual int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ if ((int)firstDayOfWeek < 0 || (int)firstDayOfWeek > 6)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(firstDayOfWeek), SR.Format(SR.ArgumentOutOfRange_Range,
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ switch (rule)
+ {
+ case CalendarWeekRule.FirstDay:
+ return (GetFirstDayWeekOfYear(time, (int)firstDayOfWeek));
+ case CalendarWeekRule.FirstFullWeek:
+ return (GetWeekOfYearFullDays(time, (int)firstDayOfWeek, 7));
+ case CalendarWeekRule.FirstFourDayWeek:
+ return (GetWeekOfYearFullDays(time, (int)firstDayOfWeek, 4));
+ }
+ throw new ArgumentOutOfRangeException(
+ nameof(rule), SR.Format(SR.ArgumentOutOfRange_Range,
+ CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+
+ public abstract int GetYear(DateTime time);
+
+ // Checks whether a given day in the current era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public virtual bool IsLeapDay(int year, int month, int day)
+ {
+ return (IsLeapDay(year, month, day, CurrentEra));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public abstract bool IsLeapDay(int year, int month, int day, int era);
+
+ // Checks whether a given month in the current era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public virtual bool IsLeapMonth(int year, int month)
+ {
+ return (IsLeapMonth(year, month, CurrentEra));
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public abstract bool IsLeapMonth(int year, int month, int era);
+
+ // Returns the leap month in a calendar year of the current era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public virtual int GetLeapMonth(int year)
+ {
+ return (GetLeapMonth(year, CurrentEra));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public virtual int GetLeapMonth(int year, int era)
+ {
+ if (!IsLeapYear(year, era))
+ return 0;
+
+ int monthsCount = GetMonthsInYear(year, era);
+ for (int month = 1; month <= monthsCount; month++)
+ {
+ if (IsLeapMonth(year, month, era))
+ return month;
+ }
+
+ return 0;
+ }
+
+ // Checks whether a given year in the current era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public virtual bool IsLeapYear(int year)
+ {
+ return (IsLeapYear(year, CurrentEra));
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public abstract bool IsLeapYear(int year, int era);
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public virtual DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond)
+ {
+ return (ToDateTime(year, month, day, hour, minute, second, millisecond, CurrentEra));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public abstract DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era);
+
+ internal virtual Boolean TryToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era, out DateTime result)
+ {
+ result = DateTime.MinValue;
+ try
+ {
+ result = ToDateTime(year, month, day, hour, minute, second, millisecond, era);
+ return true;
+ }
+ catch (ArgumentException)
+ {
+ return false;
+ }
+ }
+
+ internal virtual bool IsValidYear(int year, int era)
+ {
+ return (year >= GetYear(MinSupportedDateTime) && year <= GetYear(MaxSupportedDateTime));
+ }
+
+ internal virtual bool IsValidMonth(int year, int month, int era)
+ {
+ return (IsValidYear(year, era) && month >= 1 && month <= GetMonthsInYear(year, era));
+ }
+
+ internal virtual bool IsValidDay(int year, int month, int day, int era)
+ {
+ return (IsValidMonth(year, month, era) && day >= 1 && day <= GetDaysInMonth(year, month, era));
+ }
+
+
+ // Returns and assigns the maximum value to represent a two digit year. This
+ // value is the upper boundary of a 100 year range that allows a two digit year
+ // to be properly translated to a four digit year. For example, if 2029 is the
+ // upper boundary, then a two digit value of 30 should be interpreted as 1930
+ // while a two digit value of 29 should be interpreted as 2029. In this example
+ // , the 100 year range would be from 1930-2029. See ToFourDigitYear().
+
+ public virtual int TwoDigitYearMax
+ {
+ get
+ {
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ twoDigitYearMax = value;
+ }
+ }
+
+ // Converts the year value to the appropriate century by using the
+ // TwoDigitYearMax property. For example, if the TwoDigitYearMax value is 2029,
+ // then a two digit value of 30 will get converted to 1930 while a two digit
+ // value of 29 will get converted to 2029.
+
+ public virtual int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (year < 100)
+ {
+ return ((TwoDigitYearMax / 100 - (year > TwoDigitYearMax % 100 ? 1 : 0)) * 100 + year);
+ }
+ // If the year value is above 100, just return the year value. Don't have to do
+ // the TwoDigitYearMax comparison.
+ return (year);
+ }
+
+ // Return the tick count corresponding to the given hour, minute, second.
+ // Will check the if the parameters are valid.
+ internal static long TimeToTicks(int hour, int minute, int second, int millisecond)
+ {
+ if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60)
+ {
+ if (millisecond < 0 || millisecond >= MillisPerSecond)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(millisecond),
+ String.Format(
+ CultureInfo.InvariantCulture,
+ SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1)));
+ }
+ return InternalGlobalizationHelper.TimeToTicks(hour, minute, second) + millisecond * TicksPerMillisecond;
+ }
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
+ }
+
+ internal static int GetSystemTwoDigitYearSetting(CalendarId CalID, int defaultYearValue)
+ {
+ int twoDigitYearMax = CalendarData.GetTwoDigitYearMax(CalID);
+ if (twoDigitYearMax < 0)
+ {
+ twoDigitYearMax = defaultYearValue;
+ }
+ return (twoDigitYearMax);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CalendarAlgorithmType.cs b/src/System.Private.CoreLib/shared/System/Globalization/CalendarAlgorithmType.cs
new file mode 100644
index 0000000000..4ddc307abf
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CalendarAlgorithmType.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ public enum CalendarAlgorithmType
+ {
+ Unknown = 0, // This is the default value to return in the Calendar base class.
+ SolarCalendar = 1, // Solar-base calendar, such as GregorianCalendar, jaoaneseCalendar, JulianCalendar, etc.
+ // Solar calendars are based on the solar year and seasons.
+ LunarCalendar = 2, // Lunar-based calendar, such as Hijri and UmAlQuraCalendar.
+ // Lunar calendars are based on the path of the moon. The seasons are not accurately represented.
+ LunisolarCalendar = 3 // Lunisolar-based calendar which use leap month rule, such as HebrewCalendar and Asian Lunisolar calendars.
+ // Lunisolar calendars are based on the cycle of the moon, but consider the seasons as a secondary consideration,
+ // so they align with the seasons as well as lunar events.
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs
new file mode 100644
index 0000000000..17d6ed7a01
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs
@@ -0,0 +1,363 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Security;
+using System.Text;
+using Internal.Runtime.CompilerServices;
+
+namespace System.Globalization
+{
+ // needs to be kept in sync with CalendarDataType in System.Globalization.Native
+ internal enum CalendarDataType
+ {
+ Uninitialized = 0,
+ NativeName = 1,
+ MonthDay = 2,
+ ShortDates = 3,
+ LongDates = 4,
+ YearMonths = 5,
+ DayNames = 6,
+ AbbrevDayNames = 7,
+ MonthNames = 8,
+ AbbrevMonthNames = 9,
+ SuperShortDayNames = 10,
+ MonthGenitiveNames = 11,
+ AbbrevMonthGenitiveNames = 12,
+ EraNames = 13,
+ AbbrevEraNames = 14,
+ }
+
+ internal partial class CalendarData
+ {
+ private bool LoadCalendarDataFromSystem(String localeName, CalendarId calendarId)
+ {
+ bool result = true;
+ result &= GetCalendarInfo(localeName, calendarId, CalendarDataType.NativeName, out this.sNativeName);
+ result &= GetCalendarInfo(localeName, calendarId, CalendarDataType.MonthDay, out this.sMonthDay);
+ this.sMonthDay = NormalizeDatePattern(this.sMonthDay);
+
+ result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.ShortDates, out this.saShortDates);
+ result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.LongDates, out this.saLongDates);
+ result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.YearMonths, out this.saYearMonths);
+ result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.DayNames, out this.saDayNames);
+ result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.AbbrevDayNames, out this.saAbbrevDayNames);
+ result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.SuperShortDayNames, out this.saSuperShortDayNames);
+
+ string leapHebrewMonthName = null;
+ result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthNames, out this.saMonthNames, ref leapHebrewMonthName);
+ if (leapHebrewMonthName != null)
+ {
+ // In Hebrew calendar, get the leap month name Adar II and override the non-leap month 7
+ Debug.Assert(calendarId == CalendarId.HEBREW && saMonthNames.Length == 13);
+ saLeapYearMonthNames = (string[]) saMonthNames.Clone();
+ saLeapYearMonthNames[6] = leapHebrewMonthName;
+
+ // The returned data from ICU has 6th month name as 'Adar I' and 7th month name as 'Adar'
+ // We need to adjust that in the list used with non-leap year to have 6th month as 'Adar' and 7th month as 'Adar II'
+ // note that when formatting non-leap year dates, 7th month shouldn't get used at all.
+ saMonthNames[5] = saMonthNames[6];
+ saMonthNames[6] = leapHebrewMonthName;
+
+ }
+ result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthNames, out this.saAbbrevMonthNames, ref leapHebrewMonthName);
+ result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthGenitiveNames, out this.saMonthGenitiveNames, ref leapHebrewMonthName);
+ result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthGenitiveNames, out this.saAbbrevMonthGenitiveNames, ref leapHebrewMonthName);
+
+ result &= EnumEraNames(localeName, calendarId, CalendarDataType.EraNames, out this.saEraNames);
+ result &= EnumEraNames(localeName, calendarId, CalendarDataType.AbbrevEraNames, out this.saAbbrevEraNames);
+
+ return result;
+ }
+
+ internal static int GetTwoDigitYearMax(CalendarId calendarId)
+ {
+ // There is no user override for this value on Linux or in ICU.
+ // So just return -1 to use the hard-coded defaults.
+ return -1;
+ }
+
+ // Call native side to figure out which calendars are allowed
+ internal static int GetCalendars(string localeName, bool useUserOverride, CalendarId[] calendars)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ // NOTE: there are no 'user overrides' on Linux
+ int count = Interop.Globalization.GetCalendars(localeName, calendars, calendars.Length);
+
+ // ensure there is at least 1 calendar returned
+ if (count == 0 && calendars.Length > 0)
+ {
+ calendars[0] = CalendarId.GREGORIAN;
+ count = 1;
+ }
+
+ return count;
+ }
+
+ private static bool SystemSupportsTaiwaneseCalendar()
+ {
+ return true;
+ }
+
+ // PAL Layer ends here
+
+ private static bool GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string calendarString)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ return Interop.CallStringMethod(
+ (locale, calId, type, stringBuilder) =>
+ Interop.Globalization.GetCalendarInfo(
+ locale,
+ calId,
+ type,
+ stringBuilder,
+ stringBuilder.Capacity),
+ localeName,
+ calendarId,
+ dataType,
+ out calendarString);
+ }
+
+ private static bool EnumDatePatterns(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] datePatterns)
+ {
+ datePatterns = null;
+
+ EnumCalendarsData callbackContext = new EnumCalendarsData();
+ callbackContext.Results = new List<string>();
+ callbackContext.DisallowDuplicates = true;
+ bool result = EnumCalendarInfo(localeName, calendarId, dataType, ref callbackContext);
+ if (result)
+ {
+ List<string> datePatternsList = callbackContext.Results;
+
+ datePatterns = new string[datePatternsList.Count];
+ for (int i = 0; i < datePatternsList.Count; i++)
+ {
+ datePatterns[i] = NormalizeDatePattern(datePatternsList[i]);
+ }
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// The ICU date format characters are not exactly the same as the .NET date format characters.
+ /// NormalizeDatePattern will take in an ICU date pattern and return the equivalent .NET date pattern.
+ /// </summary>
+ /// <remarks>
+ /// see Date Field Symbol Table in http://userguide.icu-project.org/formatparse/datetime
+ /// and https://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx
+ /// </remarks>
+ private static string NormalizeDatePattern(string input)
+ {
+ StringBuilder destination = StringBuilderCache.Acquire(input.Length);
+
+ int index = 0;
+ while (index < input.Length)
+ {
+ switch (input[index])
+ {
+ case '\'':
+ // single quotes escape characters, like 'de' in es-SP
+ // so read verbatim until the next single quote
+ destination.Append(input[index++]);
+ while (index < input.Length)
+ {
+ char current = input[index++];
+ destination.Append(current);
+ if (current == '\'')
+ {
+ break;
+ }
+ }
+ break;
+ case 'E':
+ case 'e':
+ case 'c':
+ // 'E' in ICU is the day of the week, which maps to 3 or 4 'd's in .NET
+ // 'e' in ICU is the local day of the week, which has no representation in .NET, but
+ // maps closest to 3 or 4 'd's in .NET
+ // 'c' in ICU is the stand-alone day of the week, which has no representation in .NET, but
+ // maps closest to 3 or 4 'd's in .NET
+ NormalizeDayOfWeek(input, destination, ref index);
+ break;
+ case 'L':
+ case 'M':
+ // 'L' in ICU is the stand-alone name of the month,
+ // which maps closest to 'M' in .NET since it doesn't support stand-alone month names in patterns
+ // 'M' in both ICU and .NET is the month,
+ // but ICU supports 5 'M's, which is the super short month name
+ int occurrences = CountOccurrences(input, input[index], ref index);
+ if (occurrences > 4)
+ {
+ // 5 'L's or 'M's in ICU is the super short name, which maps closest to MMM in .NET
+ occurrences = 3;
+ }
+ destination.Append('M', occurrences);
+ break;
+ case 'G':
+ // 'G' in ICU is the era, which maps to 'g' in .NET
+ occurrences = CountOccurrences(input, 'G', ref index);
+
+ // it doesn't matter how many 'G's, since .NET only supports 'g' or 'gg', and they
+ // have the same meaning
+ destination.Append('g');
+ break;
+ case 'y':
+ // a single 'y' in ICU is the year with no padding or trimming.
+ // a single 'y' in .NET is the year with 1 or 2 digits
+ // so convert any single 'y' to 'yyyy'
+ occurrences = CountOccurrences(input, 'y', ref index);
+ if (occurrences == 1)
+ {
+ occurrences = 4;
+ }
+ destination.Append('y', occurrences);
+ break;
+ default:
+ const string unsupportedDateFieldSymbols = "YuUrQqwWDFg";
+ Debug.Assert(unsupportedDateFieldSymbols.IndexOf(input[index]) == -1,
+ string.Format(CultureInfo.InvariantCulture,
+ "Encountered an unexpected date field symbol '{0}' from ICU which has no known corresponding .NET equivalent.",
+ input[index]));
+
+ destination.Append(input[index++]);
+ break;
+ }
+ }
+
+ return StringBuilderCache.GetStringAndRelease(destination);
+ }
+
+ private static void NormalizeDayOfWeek(string input, StringBuilder destination, ref int index)
+ {
+ char dayChar = input[index];
+ int occurrences = CountOccurrences(input, dayChar, ref index);
+ occurrences = Math.Max(occurrences, 3);
+ if (occurrences > 4)
+ {
+ // 5 and 6 E/e/c characters in ICU is the super short names, which maps closest to ddd in .NET
+ occurrences = 3;
+ }
+
+ destination.Append('d', occurrences);
+ }
+
+ private static int CountOccurrences(string input, char value, ref int index)
+ {
+ int startIndex = index;
+ while (index < input.Length && input[index] == value)
+ {
+ index++;
+ }
+
+ return index - startIndex;
+ }
+
+ private static bool EnumMonthNames(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] monthNames, ref string leapHebrewMonthName)
+ {
+ monthNames = null;
+
+ EnumCalendarsData callbackContext = new EnumCalendarsData();
+ callbackContext.Results = new List<string>();
+ bool result = EnumCalendarInfo(localeName, calendarId, dataType, ref callbackContext);
+ if (result)
+ {
+ // the month-name arrays are expected to have 13 elements. If ICU only returns 12, add an
+ // extra empty string to fill the array.
+ if (callbackContext.Results.Count == 12)
+ {
+ callbackContext.Results.Add(string.Empty);
+ }
+
+ if (callbackContext.Results.Count > 13)
+ {
+ Debug.Assert(calendarId == CalendarId.HEBREW && callbackContext.Results.Count == 14);
+
+ if (calendarId == CalendarId.HEBREW)
+ {
+ leapHebrewMonthName = callbackContext.Results[13];
+ }
+ callbackContext.Results.RemoveRange(13, callbackContext.Results.Count - 13);
+ }
+
+ monthNames = callbackContext.Results.ToArray();
+ }
+
+ return result;
+ }
+
+ private static bool EnumEraNames(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] eraNames)
+ {
+ bool result = EnumCalendarInfo(localeName, calendarId, dataType, out eraNames);
+
+ // .NET expects that only the Japanese calendars have more than 1 era.
+ // So for other calendars, only return the latest era.
+ if (calendarId != CalendarId.JAPAN && calendarId != CalendarId.JAPANESELUNISOLAR && eraNames.Length > 0)
+ {
+ string[] latestEraName = new string[] { eraNames[eraNames.Length - 1] };
+ eraNames = latestEraName;
+ }
+
+ return result;
+ }
+
+ internal static bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] calendarData)
+ {
+ calendarData = null;
+
+ EnumCalendarsData callbackContext = new EnumCalendarsData();
+ callbackContext.Results = new List<string>();
+ bool result = EnumCalendarInfo(localeName, calendarId, dataType, ref callbackContext);
+ if (result)
+ {
+ calendarData = callbackContext.Results.ToArray();
+ }
+
+ return result;
+ }
+
+ private static unsafe bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, ref EnumCalendarsData callbackContext)
+ {
+ return Interop.Globalization.EnumCalendarInfo(EnumCalendarInfoCallback, localeName, calendarId, dataType, (IntPtr)Unsafe.AsPointer(ref callbackContext));
+ }
+
+ private static unsafe void EnumCalendarInfoCallback(string calendarString, IntPtr context)
+ {
+ try
+ {
+ ref EnumCalendarsData callbackContext = ref Unsafe.As<byte, EnumCalendarsData>(ref *(byte*)context);
+
+ if (callbackContext.DisallowDuplicates)
+ {
+ foreach (string existingResult in callbackContext.Results)
+ {
+ if (string.Equals(calendarString, existingResult, StringComparison.Ordinal))
+ {
+ // the value is already in the results, so don't add it again
+ return;
+ }
+ }
+ }
+
+ callbackContext.Results.Add(calendarString);
+ }
+ catch (Exception e)
+ {
+ Debug.Fail(e.ToString());
+ // we ignore the managed exceptions here because EnumCalendarInfoCallback will get called from the native code.
+ // If we don't ignore the exception here that can cause the runtime to fail fast.
+ }
+ }
+
+ private struct EnumCalendarsData
+ {
+ public List<string> Results;
+ public bool DisallowDuplicates;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Windows.cs
new file mode 100644
index 0000000000..03f9088d62
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Windows.cs
@@ -0,0 +1,469 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using System.Collections.Generic;
+using Internal.Runtime.CompilerServices;
+
+namespace System.Globalization
+{
+ internal partial class CalendarData
+ {
+ private bool LoadCalendarDataFromSystem(String localeName, CalendarId calendarId)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ bool ret = true;
+
+ uint useOverrides = this.bUseUserOverrides ? 0 : CAL_NOUSEROVERRIDE;
+
+ //
+ // Windows doesn't support some calendars right now, so remap those.
+ //
+ switch (calendarId)
+ {
+ case CalendarId.JAPANESELUNISOLAR: // Data looks like Japanese
+ calendarId = CalendarId.JAPAN;
+ break;
+ case CalendarId.JULIAN: // Data looks like gregorian US
+ case CalendarId.CHINESELUNISOLAR: // Algorithmic, so actual data is irrelevent
+ case CalendarId.SAKA: // reserved to match Office but not implemented in our code, so data is irrelevent
+ case CalendarId.LUNAR_ETO_CHN: // reserved to match Office but not implemented in our code, so data is irrelevent
+ case CalendarId.LUNAR_ETO_KOR: // reserved to match Office but not implemented in our code, so data is irrelevent
+ case CalendarId.LUNAR_ETO_ROKUYOU: // reserved to match Office but not implemented in our code, so data is irrelevent
+ case CalendarId.KOREANLUNISOLAR: // Algorithmic, so actual data is irrelevent
+ case CalendarId.TAIWANLUNISOLAR: // Algorithmic, so actual data is irrelevent
+ calendarId = CalendarId.GREGORIAN_US;
+ break;
+ }
+
+ //
+ // Special handling for some special calendar due to OS limitation.
+ // This includes calendar like Taiwan calendar, UmAlQura calendar, etc.
+ //
+ CheckSpecialCalendar(ref calendarId, ref localeName);
+
+ // Numbers
+ ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_ITWODIGITYEARMAX | useOverrides, out this.iTwoDigitYearMax);
+
+ // Strings
+ ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_SCALNAME, out this.sNativeName);
+ ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_SMONTHDAY | useOverrides, out this.sMonthDay);
+
+ // String Arrays
+ // Formats
+ ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SSHORTDATE, LOCALE_SSHORTDATE | useOverrides, out this.saShortDates);
+ ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SLONGDATE, LOCALE_SLONGDATE | useOverrides, out this.saLongDates);
+
+ // Get the YearMonth pattern.
+ ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SYEARMONTH, LOCALE_SYEARMONTH, out this.saYearMonths);
+
+ // Day & Month Names
+ // These are all single calType entries, 1 per day, so we have to make 7 or 13 calls to collect all the names
+
+ // Day
+ // Note that we're off-by-one since managed starts on sunday and windows starts on monday
+ ret &= GetCalendarDayInfo(localeName, calendarId, CAL_SDAYNAME7, out this.saDayNames);
+ ret &= GetCalendarDayInfo(localeName, calendarId, CAL_SABBREVDAYNAME7, out this.saAbbrevDayNames);
+
+ // Month names
+ ret &= GetCalendarMonthInfo(localeName, calendarId, CAL_SMONTHNAME1, out this.saMonthNames);
+ ret &= GetCalendarMonthInfo(localeName, calendarId, CAL_SABBREVMONTHNAME1, out this.saAbbrevMonthNames);
+
+ //
+ // The following LCTYPE are not supported in some platforms. If the call fails,
+ // don't return a failure.
+ //
+ GetCalendarDayInfo(localeName, calendarId, CAL_SSHORTESTDAYNAME7, out this.saSuperShortDayNames);
+
+ // Gregorian may have genitive month names
+ if (calendarId == CalendarId.GREGORIAN)
+ {
+ GetCalendarMonthInfo(localeName, calendarId, CAL_SMONTHNAME1 | CAL_RETURN_GENITIVE_NAMES, out this.saMonthGenitiveNames);
+ GetCalendarMonthInfo(localeName, calendarId, CAL_SABBREVMONTHNAME1 | CAL_RETURN_GENITIVE_NAMES, out this.saAbbrevMonthGenitiveNames);
+ }
+
+ // Calendar Parts Names
+ // This doesn't get always get localized names for gregorian (not available in windows < 7)
+ // so: eg: coreclr on win < 7 won't get these
+ CallEnumCalendarInfo(localeName, calendarId, CAL_SERASTRING, 0, out this.saEraNames);
+ CallEnumCalendarInfo(localeName, calendarId, CAL_SABBREVERASTRING, 0, out this.saAbbrevEraNames);
+
+ //
+ // Calendar Era Info
+ // Note that calendar era data (offsets, etc) is hard coded for each calendar since this
+ // data is implementation specific and not dynamic (except perhaps Japanese)
+ //
+
+ // Clean up the escaping of the formats
+ this.saShortDates = CultureData.ReescapeWin32Strings(this.saShortDates);
+ this.saLongDates = CultureData.ReescapeWin32Strings(this.saLongDates);
+ this.saYearMonths = CultureData.ReescapeWin32Strings(this.saYearMonths);
+ this.sMonthDay = CultureData.ReescapeWin32String(this.sMonthDay);
+
+ return ret;
+ }
+
+ // Get native two digit year max
+ internal static int GetTwoDigitYearMax(CalendarId calendarId)
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ return Invariant.iTwoDigitYearMax;
+ }
+
+ int twoDigitYearMax = -1;
+
+ if (!CallGetCalendarInfoEx(null, calendarId, (uint)CAL_ITWODIGITYEARMAX, out twoDigitYearMax))
+ {
+ twoDigitYearMax = -1;
+ }
+
+ return twoDigitYearMax;
+ }
+
+ // Call native side to figure out which calendars are allowed
+ internal static int GetCalendars(String localeName, bool useUserOverride, CalendarId[] calendars)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ EnumCalendarsData data = new EnumCalendarsData();
+ data.userOverride = 0;
+ data.calendars = new List<int>();
+
+ // First call GetLocaleInfo if necessary
+ if (useUserOverride)
+ {
+ // They want user overrides, see if the user calendar matches the input calendar
+ int userCalendar = CultureData.GetLocaleInfoExInt(localeName, LOCALE_ICALENDARTYPE);
+
+ // If we got a default, then use it as the first calendar
+ if (userCalendar != 0)
+ {
+ data.userOverride = userCalendar;
+ data.calendars.Add(userCalendar);
+ }
+ }
+
+ unsafe
+ {
+ Interop.Kernel32.EnumCalendarInfoExEx(EnumCalendarsCallback, localeName, ENUM_ALL_CALENDARS, null, CAL_ICALINTVALUE, Unsafe.AsPointer(ref data));
+ }
+
+ // Copy to the output array
+ for (int i = 0; i < Math.Min(calendars.Length, data.calendars.Count); i++)
+ calendars[i] = (CalendarId)data.calendars[i];
+
+ // Now we have a list of data, return the count
+ return data.calendars.Count;
+ }
+
+ private static bool SystemSupportsTaiwaneseCalendar()
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ string data;
+ // Taiwanese calendar get listed as one of the optional zh-TW calendars only when having zh-TW UI
+ return CallGetCalendarInfoEx("zh-TW", CalendarId.TAIWAN, CAL_SCALNAME, out data);
+ }
+
+ // PAL Layer ends here
+
+ private const uint CAL_RETURN_NUMBER = 0x20000000;
+ private const uint CAL_RETURN_GENITIVE_NAMES = 0x10000000;
+ private const uint CAL_NOUSEROVERRIDE = 0x80000000;
+ private const uint CAL_SCALNAME = 0x00000002;
+ private const uint CAL_SMONTHDAY = 0x00000038;
+ private const uint CAL_SSHORTDATE = 0x00000005;
+ private const uint CAL_SLONGDATE = 0x00000006;
+ private const uint CAL_SYEARMONTH = 0x0000002f;
+ private const uint CAL_SDAYNAME7 = 0x0000000d;
+ private const uint CAL_SABBREVDAYNAME7 = 0x00000014;
+ private const uint CAL_SMONTHNAME1 = 0x00000015;
+ private const uint CAL_SABBREVMONTHNAME1 = 0x00000022;
+ private const uint CAL_SSHORTESTDAYNAME7 = 0x00000037;
+ private const uint CAL_SERASTRING = 0x00000004;
+ private const uint CAL_SABBREVERASTRING = 0x00000039;
+ private const uint CAL_ICALINTVALUE = 0x00000001;
+ private const uint CAL_ITWODIGITYEARMAX = 0x00000030;
+
+ private const uint ENUM_ALL_CALENDARS = 0xffffffff;
+
+ private const uint LOCALE_SSHORTDATE = 0x0000001F;
+ private const uint LOCALE_SLONGDATE = 0x00000020;
+ private const uint LOCALE_SYEARMONTH = 0x00001006;
+ private const uint LOCALE_ICALENDARTYPE = 0x00001009;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // For calendars like Gregorain US/Taiwan/UmAlQura, they are not available
+ // in all OS or all localized versions of OS.
+ // If OS does not support these calendars, we will fallback by using the
+ // appropriate fallback calendar and locale combination to retrieve data from OS.
+ //
+ // Parameters:
+ // __deref_inout pCalendarInt:
+ // Pointer to the calendar ID. This will be updated to new fallback calendar ID if needed.
+ // __in_out pLocaleNameStackBuffer
+ // Pointer to the StackSString object which holds the locale name to be checked.
+ // This will be updated to new fallback locale name if needed.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ private static void CheckSpecialCalendar(ref CalendarId calendar, ref string localeName)
+ {
+ string data;
+
+ // Gregorian-US isn't always available in the OS, however it is the same for all locales
+ switch (calendar)
+ {
+ case CalendarId.GREGORIAN_US:
+ // See if this works
+ if (!CallGetCalendarInfoEx(localeName, calendar, CAL_SCALNAME, out data))
+ {
+ // Failed, set it to a locale (fa-IR) that's alway has Gregorian US available in the OS
+ localeName = "fa-IR";
+ }
+ // See if that works
+ if (!CallGetCalendarInfoEx(localeName, calendar, CAL_SCALNAME, out data))
+ {
+ // Failed again, just use en-US with the gregorian calendar
+ localeName = "en-US";
+ calendar = CalendarId.GREGORIAN;
+ }
+ break;
+ case CalendarId.TAIWAN:
+ // Taiwan calendar data is not always in all language version of OS due to Geopolical reasons.
+ // It is only available in zh-TW localized versions of Windows.
+ // Let's check if OS supports it. If not, fallback to Greogrian localized for Taiwan calendar.
+ if (!SystemSupportsTaiwaneseCalendar())
+ {
+ calendar = CalendarId.GREGORIAN;
+ }
+ break;
+ }
+ }
+
+ private static bool CallGetCalendarInfoEx(string localeName, CalendarId calendar, uint calType, out int data)
+ {
+ return (Interop.Kernel32.GetCalendarInfoEx(localeName, (uint)calendar, IntPtr.Zero, calType | CAL_RETURN_NUMBER, IntPtr.Zero, 0, out data) != 0);
+ }
+
+ private static unsafe bool CallGetCalendarInfoEx(string localeName, CalendarId calendar, uint calType, out string data)
+ {
+ const int BUFFER_LENGTH = 80;
+
+ // The maximum size for values returned from GetCalendarInfoEx is 80 characters.
+ char* buffer = stackalloc char[BUFFER_LENGTH];
+
+ int ret = Interop.Kernel32.GetCalendarInfoEx(localeName, (uint)calendar, IntPtr.Zero, calType, (IntPtr)buffer, BUFFER_LENGTH, IntPtr.Zero);
+ if (ret > 0)
+ {
+ if (buffer[ret - 1] == '\0')
+ {
+ ret--; // don't include the null termination in the string
+ }
+ data = new string(buffer, 0, ret);
+ return true;
+ }
+ data = "";
+ return false;
+ }
+
+ // Context for EnumCalendarInfoExEx callback.
+ private struct EnumData
+ {
+ public string userOverride;
+ public List<string> strings;
+ }
+
+ // EnumCalendarInfoExEx callback itself.
+ // [NativeCallable(CallingConvention = CallingConvention.StdCall)]
+ private static unsafe Interop.BOOL EnumCalendarInfoCallback(char* lpCalendarInfoString, uint calendar, IntPtr pReserved, void* lParam)
+ {
+ ref EnumData context = ref Unsafe.As<byte, EnumData>(ref *(byte*)lParam);
+ try
+ {
+ string calendarInfo = new string(lpCalendarInfoString);
+
+ // If we had a user override, check to make sure this differs
+ if (context.userOverride != calendarInfo)
+ context.strings.Add(calendarInfo);
+
+ return Interop.BOOL.TRUE;
+ }
+ catch (Exception)
+ {
+ return Interop.BOOL.FALSE;
+ }
+ }
+
+ private static unsafe bool CallEnumCalendarInfo(string localeName, CalendarId calendar, uint calType, uint lcType, out string[] data)
+ {
+ EnumData context = new EnumData();
+ context.userOverride = null;
+ context.strings = new List<string>();
+ // First call GetLocaleInfo if necessary
+ if (((lcType != 0) && ((lcType & CAL_NOUSEROVERRIDE) == 0)) &&
+ // Get user locale, see if it matches localeName.
+ // Note that they should match exactly, including letter case
+ GetUserDefaultLocaleName() == localeName)
+ {
+ // They want user overrides, see if the user calendar matches the input calendar
+ CalendarId userCalendar = (CalendarId)CultureData.GetLocaleInfoExInt(localeName, LOCALE_ICALENDARTYPE);
+
+ // If the calendars were the same, see if the locales were the same
+ if (userCalendar == calendar)
+ {
+ // They matched, get the user override since locale & calendar match
+ string res = CultureData.GetLocaleInfoEx(localeName, lcType);
+
+ // if it succeeded remember the override for the later callers
+ if (res != null)
+ {
+ // Remember this was the override (so we can look for duplicates later in the enum function)
+ context.userOverride = res;
+
+ // Add to the result strings.
+ context.strings.Add(res);
+ }
+ }
+ }
+
+ unsafe
+ {
+ // Now call the enumeration API. Work is done by our callback function
+ Interop.Kernel32.EnumCalendarInfoExEx(EnumCalendarInfoCallback, localeName, (uint)calendar, null, calType, Unsafe.AsPointer(ref context));
+ }
+
+ // Now we have a list of data, fail if we didn't find anything.
+ if (context.strings.Count == 0)
+ {
+ data = null;
+ return false;
+ }
+
+ string[] output = context.strings.ToArray();
+
+ if (calType == CAL_SABBREVERASTRING || calType == CAL_SERASTRING)
+ {
+ // Eras are enumerated backwards. (oldest era name first, but
+ // Japanese calendar has newest era first in array, and is only
+ // calendar with multiple eras)
+ Array.Reverse(output, 0, output.Length);
+ }
+
+ data = output;
+
+ return true;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Get the native day names
+ //
+ // NOTE: There's a disparity between .Net & windows day orders, the input day should
+ // start with Sunday
+ //
+ // Parameters:
+ // OUT pOutputStrings The output string[] value.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ private static bool GetCalendarDayInfo(string localeName, CalendarId calendar, uint calType, out string[] outputStrings)
+ {
+ bool result = true;
+
+ //
+ // We'll need a new array of 7 items
+ //
+ string[] results = new string[7];
+
+ // Get each one of them
+ for (int i = 0; i < 7; i++, calType++)
+ {
+ result &= CallGetCalendarInfoEx(localeName, calendar, calType, out results[i]);
+
+ // On the first iteration we need to go from CAL_SDAYNAME7 to CAL_SDAYNAME1, so subtract 7 before the ++ happens
+ // This is because the framework starts on sunday and windows starts on monday when counting days
+ if (i == 0)
+ calType -= 7;
+ }
+
+ outputStrings = results;
+
+ return result;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Get the native month names
+ //
+ // Parameters:
+ // OUT pOutputStrings The output string[] value.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ private static bool GetCalendarMonthInfo(string localeName, CalendarId calendar, uint calType, out string[] outputStrings)
+ {
+ //
+ // We'll need a new array of 13 items
+ //
+ string[] results = new string[13];
+
+ // Get each one of them
+ for (int i = 0; i < 13; i++, calType++)
+ {
+ if (!CallGetCalendarInfoEx(localeName, calendar, calType, out results[i]))
+ results[i] = "";
+ }
+
+ outputStrings = results;
+
+ return true;
+ }
+
+ //
+ // struct to help our calendar data enumaration callback
+ //
+ private struct EnumCalendarsData
+ {
+ public int userOverride; // user override value (if found)
+ public List<int> calendars; // list of calendars found so far
+ }
+
+ // [NativeCallable(CallingConvention = CallingConvention.StdCall)]
+ private static unsafe Interop.BOOL EnumCalendarsCallback(char* lpCalendarInfoString, uint calendar, IntPtr reserved, void* lParam)
+ {
+ ref EnumCalendarsData context = ref Unsafe.As<byte, EnumCalendarsData>(ref *(byte*)lParam);
+ try
+ {
+ // If we had a user override, check to make sure this differs
+ if (context.userOverride != calendar)
+ context.calendars.Add((int)calendar);
+
+ return Interop.BOOL.TRUE;
+ }
+ catch (Exception)
+ {
+ return Interop.BOOL.FALSE;
+ }
+ }
+
+ private static unsafe String GetUserDefaultLocaleName()
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ const int LOCALE_NAME_MAX_LENGTH = 85;
+ const uint LOCALE_SNAME = 0x0000005c;
+ const string LOCALE_NAME_USER_DEFAULT = null;
+
+ int result;
+ char* localeName = stackalloc char[LOCALE_NAME_MAX_LENGTH];
+ result = CultureData.GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, localeName, LOCALE_NAME_MAX_LENGTH);
+
+ return result <= 0 ? "" : new String(localeName, 0, result - 1); // exclude the null termination
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.cs b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.cs
new file mode 100644
index 0000000000..ea70a1ce9a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.cs
@@ -0,0 +1,378 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ // List of calendar data
+ // Note the we cache overrides.
+ // Note that localized names (resource names) aren't available from here.
+ //
+ // NOTE: Calendars depend on the locale name that creates it. Only a few
+ // properties are available without locales using CalendarData.GetCalendar(CalendarData)
+
+ internal partial class CalendarData
+ {
+ // Max calendars
+ internal const int MAX_CALENDARS = 23;
+
+ // Identity
+ internal String sNativeName; // Calendar Name for the locale
+
+ // Formats
+ internal String[] saShortDates; // Short Data format, default first
+ internal String[] saYearMonths; // Year/Month Data format, default first
+ internal String[] saLongDates; // Long Data format, default first
+ internal String sMonthDay; // Month/Day format
+
+ // Calendar Parts Names
+ internal String[] saEraNames; // Names of Eras
+ internal String[] saAbbrevEraNames; // Abbreviated Era Names
+ internal String[] saAbbrevEnglishEraNames; // Abbreviated Era Names in English
+ internal String[] saDayNames; // Day Names, null to use locale data, starts on Sunday
+ internal String[] saAbbrevDayNames; // Abbrev Day Names, null to use locale data, starts on Sunday
+ internal String[] saSuperShortDayNames; // Super short Day of week names
+ internal String[] saMonthNames; // Month Names (13)
+ internal String[] saAbbrevMonthNames; // Abbrev Month Names (13)
+ internal String[] saMonthGenitiveNames; // Genitive Month Names (13)
+ internal String[] saAbbrevMonthGenitiveNames; // Genitive Abbrev Month Names (13)
+ internal String[] saLeapYearMonthNames; // Multiple strings for the month names in a leap year.
+
+ // Integers at end to make marshaller happier
+ internal int iTwoDigitYearMax = 2029; // Max 2 digit year (for Y2K bug data entry)
+ internal int iCurrentEra = 0; // current era # (usually 1)
+
+ // Use overrides?
+ internal bool bUseUserOverrides; // True if we want user overrides.
+
+ // Static invariant for the invariant locale
+ internal static readonly CalendarData Invariant = CreateInvariant();
+
+ // Private constructor
+ private CalendarData() { }
+
+ // Invariant factory
+ private static CalendarData CreateInvariant()
+ {
+ // Set our default/gregorian US calendar data
+ // Calendar IDs are 1-based, arrays are 0 based.
+ CalendarData invariant = new CalendarData();
+
+ // Set default data for calendar
+ // Note that we don't load resources since this IS NOT supposed to change (by definition)
+ invariant.sNativeName = "Gregorian Calendar"; // Calendar Name
+
+ // Year
+ invariant.iTwoDigitYearMax = 2029; // Max 2 digit year (for Y2K bug data entry)
+ invariant.iCurrentEra = 1; // Current era #
+
+ // Formats
+ invariant.saShortDates = new String[] { "MM/dd/yyyy", "yyyy-MM-dd" }; // short date format
+ invariant.saLongDates = new String[] { "dddd, dd MMMM yyyy" }; // long date format
+ invariant.saYearMonths = new String[] { "yyyy MMMM" }; // year month format
+ invariant.sMonthDay = "MMMM dd"; // Month day pattern
+
+ // Calendar Parts Names
+ invariant.saEraNames = new String[] { "A.D." }; // Era names
+ invariant.saAbbrevEraNames = new String[] { "AD" }; // Abbreviated Era names
+ invariant.saAbbrevEnglishEraNames = new String[] { "AD" }; // Abbreviated era names in English
+ invariant.saDayNames = new String[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };// day names
+ invariant.saAbbrevDayNames = new String[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // abbreviated day names
+ invariant.saSuperShortDayNames = new String[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" }; // The super short day names
+ invariant.saMonthNames = new String[] { "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December", String.Empty}; // month names
+ invariant.saAbbrevMonthNames = new String[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", String.Empty}; // abbreviated month names
+ invariant.saMonthGenitiveNames = invariant.saMonthNames; // Genitive month names (same as month names for invariant)
+ invariant.saAbbrevMonthGenitiveNames = invariant.saAbbrevMonthNames; // Abbreviated genitive month names (same as abbrev month names for invariant)
+ invariant.saLeapYearMonthNames = invariant.saMonthNames; // leap year month names are unused in Gregorian English (invariant)
+
+ invariant.bUseUserOverrides = false;
+
+ return invariant;
+ }
+
+ //
+ // Get a bunch of data for a calendar
+ //
+ internal CalendarData(String localeName, CalendarId calendarId, bool bUseUserOverrides)
+ {
+ this.bUseUserOverrides = bUseUserOverrides;
+
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ if (!LoadCalendarDataFromSystem(localeName, calendarId))
+ {
+ Debug.Fail("[CalendarData] LoadCalendarDataFromSystem call isn't expected to fail for calendar " + calendarId + " locale " + localeName);
+
+ // Something failed, try invariant for missing parts
+ // This is really not good, but we don't want the callers to crash.
+ if (this.sNativeName == null) this.sNativeName = String.Empty; // Calendar Name for the locale.
+
+ // Formats
+ if (this.saShortDates == null) this.saShortDates = Invariant.saShortDates; // Short Data format, default first
+ if (this.saYearMonths == null) this.saYearMonths = Invariant.saYearMonths; // Year/Month Data format, default first
+ if (this.saLongDates == null) this.saLongDates = Invariant.saLongDates; // Long Data format, default first
+ if (this.sMonthDay == null) this.sMonthDay = Invariant.sMonthDay; // Month/Day format
+
+ // Calendar Parts Names
+ if (this.saEraNames == null) this.saEraNames = Invariant.saEraNames; // Names of Eras
+ if (this.saAbbrevEraNames == null) this.saAbbrevEraNames = Invariant.saAbbrevEraNames; // Abbreviated Era Names
+ if (this.saAbbrevEnglishEraNames == null) this.saAbbrevEnglishEraNames = Invariant.saAbbrevEnglishEraNames; // Abbreviated Era Names in English
+ if (this.saDayNames == null) this.saDayNames = Invariant.saDayNames; // Day Names, null to use locale data, starts on Sunday
+ if (this.saAbbrevDayNames == null) this.saAbbrevDayNames = Invariant.saAbbrevDayNames; // Abbrev Day Names, null to use locale data, starts on Sunday
+ if (this.saSuperShortDayNames == null) this.saSuperShortDayNames = Invariant.saSuperShortDayNames; // Super short Day of week names
+ if (this.saMonthNames == null) this.saMonthNames = Invariant.saMonthNames; // Month Names (13)
+ if (this.saAbbrevMonthNames == null) this.saAbbrevMonthNames = Invariant.saAbbrevMonthNames; // Abbrev Month Names (13)
+ // Genitive and Leap names can follow the fallback below
+ }
+
+ if (calendarId == CalendarId.TAIWAN)
+ {
+ if (SystemSupportsTaiwaneseCalendar())
+ {
+ // We got the month/day names from the OS (same as gregorian), but the native name is wrong
+ this.sNativeName = "\x4e2d\x83ef\x6c11\x570b\x66c6";
+ }
+ else
+ {
+ this.sNativeName = String.Empty;
+ }
+ }
+
+ // Check for null genitive names (in case unmanaged side skips it for non-gregorian calendars, etc)
+ if (this.saMonthGenitiveNames == null || this.saMonthGenitiveNames.Length == 0 || String.IsNullOrEmpty(this.saMonthGenitiveNames[0]))
+ this.saMonthGenitiveNames = this.saMonthNames; // Genitive month names (same as month names for invariant)
+ if (this.saAbbrevMonthGenitiveNames == null || this.saAbbrevMonthGenitiveNames.Length == 0 || String.IsNullOrEmpty(this.saAbbrevMonthGenitiveNames[0]))
+ this.saAbbrevMonthGenitiveNames = this.saAbbrevMonthNames; // Abbreviated genitive month names (same as abbrev month names for invariant)
+ if (this.saLeapYearMonthNames == null || this.saLeapYearMonthNames.Length == 0 || String.IsNullOrEmpty(this.saLeapYearMonthNames[0]))
+ this.saLeapYearMonthNames = this.saMonthNames;
+
+ InitializeEraNames(localeName, calendarId);
+
+ InitializeAbbreviatedEraNames(localeName, calendarId);
+
+ // Abbreviated English Era Names are only used for the Japanese calendar.
+ if (calendarId == CalendarId.JAPAN)
+ {
+ this.saAbbrevEnglishEraNames = JapaneseCalendar.EnglishEraNames();
+ }
+ else
+ {
+ // For all others just use the an empty string (doesn't matter we'll never ask for it for other calendars)
+ this.saAbbrevEnglishEraNames = new String[] { "" };
+ }
+
+ // Japanese is the only thing with > 1 era. Its current era # is how many ever
+ // eras are in the array. (And the others all have 1 string in the array)
+ this.iCurrentEra = this.saEraNames.Length;
+ }
+
+ private void InitializeEraNames(string localeName, CalendarId calendarId)
+ {
+ // Note that the saEraNames only include "A.D." We don't have localized names for other calendars available from windows
+ switch (calendarId)
+ {
+ // For Localized Gregorian we really expect the data from the OS.
+ case CalendarId.GREGORIAN:
+ // Fallback for CoreCLR < Win7 or culture.dll missing
+ if (this.saEraNames == null || this.saEraNames.Length == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
+ {
+ this.saEraNames = new String[] { "A.D." };
+ }
+ break;
+
+ // The rest of the calendars have constant data, so we'll just use that
+ case CalendarId.GREGORIAN_US:
+ case CalendarId.JULIAN:
+ this.saEraNames = new String[] { "A.D." };
+ break;
+ case CalendarId.HEBREW:
+ this.saEraNames = new String[] { "C.E." };
+ break;
+ case CalendarId.HIJRI:
+ case CalendarId.UMALQURA:
+ if (localeName == "dv-MV")
+ {
+ // Special case for Divehi
+ this.saEraNames = new String[] { "\x0780\x07a8\x0796\x07b0\x0783\x07a9" };
+ }
+ else
+ {
+ this.saEraNames = new String[] { "\x0628\x0639\x062F \x0627\x0644\x0647\x062C\x0631\x0629" };
+ }
+ break;
+ case CalendarId.GREGORIAN_ARABIC:
+ case CalendarId.GREGORIAN_XLIT_ENGLISH:
+ case CalendarId.GREGORIAN_XLIT_FRENCH:
+ // These are all the same:
+ this.saEraNames = new String[] { "\x0645" };
+ break;
+
+ case CalendarId.GREGORIAN_ME_FRENCH:
+ this.saEraNames = new String[] { "ap. J.-C." };
+ break;
+
+ case CalendarId.TAIWAN:
+ if (SystemSupportsTaiwaneseCalendar())
+ {
+ this.saEraNames = new String[] { "\x4e2d\x83ef\x6c11\x570b" };
+ }
+ else
+ {
+ this.saEraNames = new String[] { String.Empty };
+ }
+ break;
+
+ case CalendarId.KOREA:
+ this.saEraNames = new String[] { "\xb2e8\xae30" };
+ break;
+
+ case CalendarId.THAI:
+ this.saEraNames = new String[] { "\x0e1e\x002e\x0e28\x002e" };
+ break;
+
+ case CalendarId.JAPAN:
+ case CalendarId.JAPANESELUNISOLAR:
+ this.saEraNames = JapaneseCalendar.EraNames();
+ break;
+
+ case CalendarId.PERSIAN:
+ if (this.saEraNames == null || this.saEraNames.Length == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
+ {
+ this.saEraNames = new String[] { "\x0647\x002e\x0634" };
+ }
+ break;
+
+ default:
+ // Most calendars are just "A.D."
+ this.saEraNames = Invariant.saEraNames;
+ break;
+ }
+ }
+
+ private void InitializeAbbreviatedEraNames(string localeName, CalendarId calendarId)
+ {
+ // Note that the saAbbrevEraNames only include "AD" We don't have localized names for other calendars available from windows
+ switch (calendarId)
+ {
+ // For Localized Gregorian we really expect the data from the OS.
+ case CalendarId.GREGORIAN:
+ // Fallback for CoreCLR < Win7 or culture.dll missing
+ if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
+ {
+ this.saAbbrevEraNames = new String[] { "AD" };
+ }
+ break;
+
+ // The rest of the calendars have constant data, so we'll just use that
+ case CalendarId.GREGORIAN_US:
+ case CalendarId.JULIAN:
+ this.saAbbrevEraNames = new String[] { "AD" };
+ break;
+ case CalendarId.JAPAN:
+ case CalendarId.JAPANESELUNISOLAR:
+ this.saAbbrevEraNames = JapaneseCalendar.AbbrevEraNames();
+ break;
+ case CalendarId.HIJRI:
+ case CalendarId.UMALQURA:
+ if (localeName == "dv-MV")
+ {
+ // Special case for Divehi
+ this.saAbbrevEraNames = new String[] { "\x0780\x002e" };
+ }
+ else
+ {
+ this.saAbbrevEraNames = new String[] { "\x0647\x0640" };
+ }
+ break;
+ case CalendarId.TAIWAN:
+ // Get era name and abbreviate it
+ this.saAbbrevEraNames = new String[1];
+ if (this.saEraNames[0].Length == 4)
+ {
+ this.saAbbrevEraNames[0] = this.saEraNames[0].Substring(2, 2);
+ }
+ else
+ {
+ this.saAbbrevEraNames[0] = this.saEraNames[0];
+ }
+ break;
+
+ case CalendarId.PERSIAN:
+ if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
+ {
+ this.saAbbrevEraNames = this.saEraNames;
+ }
+ break;
+
+ default:
+ // Most calendars just use the full name
+ this.saAbbrevEraNames = this.saEraNames;
+ break;
+ }
+ }
+
+ internal static CalendarData GetCalendarData(CalendarId calendarId)
+ {
+ //
+ // Get a calendar.
+ // Unfortunately we depend on the locale in the OS, so we need a locale
+ // no matter what. So just get the appropriate calendar from the
+ // appropriate locale here
+ //
+
+ // Get a culture name
+ // TODO: Note that this doesn't handle the new calendars (lunisolar, etc)
+ String culture = CalendarIdToCultureName(calendarId);
+
+ // Return our calendar
+ return CultureInfo.GetCultureInfo(culture)._cultureData.GetCalendar(calendarId);
+ }
+
+ private static String CalendarIdToCultureName(CalendarId calendarId)
+ {
+ switch (calendarId)
+ {
+ case CalendarId.GREGORIAN_US:
+ return "fa-IR"; // "fa-IR" Iran
+
+ case CalendarId.JAPAN:
+ return "ja-JP"; // "ja-JP" Japan
+
+ case CalendarId.TAIWAN:
+ return "zh-TW"; // zh-TW Taiwan
+
+ case CalendarId.KOREA:
+ return "ko-KR"; // "ko-KR" Korea
+
+ case CalendarId.HIJRI:
+ case CalendarId.GREGORIAN_ARABIC:
+ case CalendarId.UMALQURA:
+ return "ar-SA"; // "ar-SA" Saudi Arabia
+
+ case CalendarId.THAI:
+ return "th-TH"; // "th-TH" Thailand
+
+ case CalendarId.HEBREW:
+ return "he-IL"; // "he-IL" Israel
+
+ case CalendarId.GREGORIAN_ME_FRENCH:
+ return "ar-DZ"; // "ar-DZ" Algeria
+
+ case CalendarId.GREGORIAN_XLIT_ENGLISH:
+ case CalendarId.GREGORIAN_XLIT_FRENCH:
+ return "ar-IQ"; // "ar-IQ"; Iraq
+
+ default:
+ // Default to gregorian en-US
+ break;
+ }
+
+ return "en-US";
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CalendarWeekRule.cs b/src/System.Private.CoreLib/shared/System/Globalization/CalendarWeekRule.cs
new file mode 100644
index 0000000000..f683ed05e7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CalendarWeekRule.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ public enum CalendarWeekRule
+ {
+ FirstDay = 0, // Week 1 begins on the first day of the year
+
+ FirstFullWeek = 1, // Week 1 begins on first FirstDayOfWeek not before the first day of the year
+
+ FirstFourDayWeek = 2 // Week 1 begins on first FirstDayOfWeek such that FirstDayOfWeek+3 is not before the first day of the year
+ };
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CalendricalCalculationsHelper.cs b/src/System.Private.CoreLib/shared/System/Globalization/CalendricalCalculationsHelper.cs
new file mode 100644
index 0000000000..e0a3072b22
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CalendricalCalculationsHelper.cs
@@ -0,0 +1,412 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ internal class CalendricalCalculationsHelper
+ {
+ private const double FullCircleOfArc = 360.0; // 360.0;
+ private const int HalfCircleOfArc = 180;
+ private const double TwelveHours = 0.5; // half a day
+ private const double Noon2000Jan01 = 730120.5;
+ internal const double MeanTropicalYearInDays = 365.242189;
+ private const double MeanSpeedOfSun = MeanTropicalYearInDays / FullCircleOfArc;
+ private const double LongitudeSpring = 0.0;
+ private const double TwoDegreesAfterSpring = 2.0;
+ private const int SecondsPerDay = 24 * 60 * 60; // 24 hours * 60 minutes * 60 seconds
+
+ private const int DaysInUniformLengthCentury = 36525;
+ private const int SecondsPerMinute = 60;
+ private const int MinutesPerDegree = 60;
+
+ private static readonly long s_startOf1810 = GetNumberOfDays(new DateTime(1810, 1, 1));
+ private static readonly long s_startOf1900Century = GetNumberOfDays(new DateTime(1900, 1, 1));
+
+ private static readonly double[] s_coefficients1900to1987 = new double[] { -0.00002, 0.000297, 0.025184, -0.181133, 0.553040, -0.861938, 0.677066, -0.212591 };
+ private static readonly double[] s_coefficients1800to1899 = new double[] { -0.000009, 0.003844, 0.083563, 0.865736, 4.867575, 15.845535, 31.332267, 38.291999, 28.316289, 11.636204, 2.043794 };
+ private static readonly double[] s_coefficients1700to1799 = new double[] { 8.118780842, -0.005092142, 0.003336121, -0.0000266484 };
+ private static readonly double[] s_coefficients1620to1699 = new double[] { 196.58333, -4.0675, 0.0219167 };
+ private static readonly double[] s_lambdaCoefficients = new double[] { 280.46645, 36000.76983, 0.0003032 };
+ private static readonly double[] s_anomalyCoefficients = new double[] { 357.52910, 35999.05030, -0.0001559, -0.00000048 };
+ private static readonly double[] s_eccentricityCoefficients = new double[] { 0.016708617, -0.000042037, -0.0000001236 };
+ private static readonly double[] s_coefficients = new double[] { Angle(23, 26, 21.448), Angle(0, 0, -46.8150), Angle(0, 0, -0.00059), Angle(0, 0, 0.001813) };
+ private static readonly double[] s_coefficientsA = new double[] { 124.90, -1934.134, 0.002063 };
+ private static readonly double[] s_coefficientsB = new double[] { 201.11, 72001.5377, 0.00057 };
+
+ private static double RadiansFromDegrees(double degree)
+ {
+ return degree * Math.PI / 180;
+ }
+
+ private static double SinOfDegree(double degree)
+ {
+ return Math.Sin(RadiansFromDegrees(degree));
+ }
+
+ private static double CosOfDegree(double degree)
+ {
+ return Math.Cos(RadiansFromDegrees(degree));
+ }
+ private static double TanOfDegree(double degree)
+ {
+ return Math.Tan(RadiansFromDegrees(degree));
+ }
+
+ public static double Angle(int degrees, int minutes, double seconds)
+ {
+ return ((seconds / SecondsPerMinute + minutes) / MinutesPerDegree) + degrees;
+ }
+
+ private static double Obliquity(double julianCenturies)
+ {
+ return PolynomialSum(s_coefficients, julianCenturies);
+ }
+
+ internal static long GetNumberOfDays(DateTime date)
+ {
+ return date.Ticks / GregorianCalendar.TicksPerDay;
+ }
+
+ private static int GetGregorianYear(double numberOfDays)
+ {
+ return new DateTime(Math.Min((long)(Math.Floor(numberOfDays) * GregorianCalendar.TicksPerDay), DateTime.MaxValue.Ticks)).Year;
+ }
+
+ private enum CorrectionAlgorithm
+ {
+ Default,
+ Year1988to2019,
+ Year1900to1987,
+ Year1800to1899,
+ Year1700to1799,
+ Year1620to1699
+ }
+
+ private struct EphemerisCorrectionAlgorithmMap
+ {
+ public EphemerisCorrectionAlgorithmMap(int year, CorrectionAlgorithm algorithm)
+ {
+ _lowestYear = year;
+ _algorithm = algorithm;
+ }
+
+ internal int _lowestYear;
+ internal CorrectionAlgorithm _algorithm;
+ };
+
+ private static readonly EphemerisCorrectionAlgorithmMap[] s_ephemerisCorrectionTable = new EphemerisCorrectionAlgorithmMap[]
+ {
+ // lowest year that starts algorithm, algorithm to use
+ new EphemerisCorrectionAlgorithmMap(2020, CorrectionAlgorithm.Default),
+ new EphemerisCorrectionAlgorithmMap(1988, CorrectionAlgorithm.Year1988to2019),
+ new EphemerisCorrectionAlgorithmMap(1900, CorrectionAlgorithm.Year1900to1987),
+ new EphemerisCorrectionAlgorithmMap(1800, CorrectionAlgorithm.Year1800to1899),
+ new EphemerisCorrectionAlgorithmMap(1700, CorrectionAlgorithm.Year1700to1799),
+ new EphemerisCorrectionAlgorithmMap(1620, CorrectionAlgorithm.Year1620to1699),
+ new EphemerisCorrectionAlgorithmMap(int.MinValue, CorrectionAlgorithm.Default) // default must be last
+ };
+
+ private static double Reminder(double divisor, double dividend)
+ {
+ double whole = Math.Floor(divisor / dividend);
+ return divisor - (dividend * whole);
+ }
+
+ private static double NormalizeLongitude(double longitude)
+ {
+ longitude = Reminder(longitude, FullCircleOfArc);
+ if (longitude < 0)
+ {
+ longitude += FullCircleOfArc;
+ }
+ return longitude;
+ }
+
+ public static double AsDayFraction(double longitude)
+ {
+ return longitude / FullCircleOfArc;
+ }
+
+ private static double PolynomialSum(double[] coefficients, double indeterminate)
+ {
+ double sum = coefficients[0];
+ double indeterminateRaised = 1;
+ for (int i = 1; i < coefficients.Length; i++)
+ {
+ indeterminateRaised *= indeterminate;
+ sum += (coefficients[i] * indeterminateRaised);
+ }
+
+ return sum;
+ }
+
+ private static double CenturiesFrom1900(int gregorianYear)
+ {
+ long july1stOfYear = GetNumberOfDays(new DateTime(gregorianYear, 7, 1));
+ return (double)(july1stOfYear - s_startOf1900Century) / DaysInUniformLengthCentury;
+ }
+
+ // the following formulas defines a polynomial function which gives us the amount that the earth is slowing down for specific year ranges
+ private static double DefaultEphemerisCorrection(int gregorianYear)
+ {
+ Debug.Assert(gregorianYear < 1620 || 2020 <= gregorianYear);
+ long january1stOfYear = GetNumberOfDays(new DateTime(gregorianYear, 1, 1));
+ double daysSinceStartOf1810 = january1stOfYear - s_startOf1810;
+ double x = TwelveHours + daysSinceStartOf1810;
+ return ((Math.Pow(x, 2) / 41048480) - 15) / SecondsPerDay;
+ }
+
+ private static double EphemerisCorrection1988to2019(int gregorianYear)
+ {
+ Debug.Assert(1988 <= gregorianYear && gregorianYear <= 2019);
+ return (double)(gregorianYear - 1933) / SecondsPerDay;
+ }
+
+ private static double EphemerisCorrection1900to1987(int gregorianYear)
+ {
+ Debug.Assert(1900 <= gregorianYear && gregorianYear <= 1987);
+ double centuriesFrom1900 = CenturiesFrom1900(gregorianYear);
+ return PolynomialSum(s_coefficients1900to1987, centuriesFrom1900);
+ }
+
+ private static double EphemerisCorrection1800to1899(int gregorianYear)
+ {
+ Debug.Assert(1800 <= gregorianYear && gregorianYear <= 1899);
+ double centuriesFrom1900 = CenturiesFrom1900(gregorianYear);
+ return PolynomialSum(s_coefficients1800to1899, centuriesFrom1900);
+ }
+
+ private static double EphemerisCorrection1700to1799(int gregorianYear)
+ {
+ Debug.Assert(1700 <= gregorianYear && gregorianYear <= 1799);
+ double yearsSince1700 = gregorianYear - 1700;
+ return PolynomialSum(s_coefficients1700to1799, yearsSince1700) / SecondsPerDay;
+ }
+
+ private static double EphemerisCorrection1620to1699(int gregorianYear)
+ {
+ Debug.Assert(1620 <= gregorianYear && gregorianYear <= 1699);
+ double yearsSince1600 = gregorianYear - 1600;
+ return PolynomialSum(s_coefficients1620to1699, yearsSince1600) / SecondsPerDay;
+ }
+
+ // ephemeris-correction: correction to account for the slowing down of the rotation of the earth
+ private static double EphemerisCorrection(double time)
+ {
+ int year = GetGregorianYear(time);
+ foreach (EphemerisCorrectionAlgorithmMap map in s_ephemerisCorrectionTable)
+ {
+ if (map._lowestYear <= year)
+ {
+ switch (map._algorithm)
+ {
+ case CorrectionAlgorithm.Default: return DefaultEphemerisCorrection(year);
+ case CorrectionAlgorithm.Year1988to2019: return EphemerisCorrection1988to2019(year);
+ case CorrectionAlgorithm.Year1900to1987: return EphemerisCorrection1900to1987(year);
+ case CorrectionAlgorithm.Year1800to1899: return EphemerisCorrection1800to1899(year);
+ case CorrectionAlgorithm.Year1700to1799: return EphemerisCorrection1700to1799(year);
+ case CorrectionAlgorithm.Year1620to1699: return EphemerisCorrection1620to1699(year);
+ }
+
+ break; // break the loop and assert eventually
+ }
+ }
+
+ Debug.Fail("Not expected to come here");
+ return DefaultEphemerisCorrection(year);
+ }
+
+ public static double JulianCenturies(double moment)
+ {
+ double dynamicalMoment = moment + EphemerisCorrection(moment);
+ return (dynamicalMoment - Noon2000Jan01) / DaysInUniformLengthCentury;
+ }
+
+ private static bool IsNegative(double value)
+ {
+ return Math.Sign(value) == -1;
+ }
+
+ private static double CopySign(double value, double sign)
+ {
+ return (IsNegative(value) == IsNegative(sign)) ? value : -value;
+ }
+
+ // equation-of-time; approximate the difference between apparent solar time and mean solar time
+ // formal definition is EOT = GHA - GMHA
+ // GHA is the Greenwich Hour Angle of the apparent (actual) Sun
+ // GMHA is the Greenwich Mean Hour Angle of the mean (fictitious) Sun
+ // http://www.esrl.noaa.gov/gmd/grad/solcalc/
+ // http://en.wikipedia.org/wiki/Equation_of_time
+ private static double EquationOfTime(double time)
+ {
+ double julianCenturies = JulianCenturies(time);
+ double lambda = PolynomialSum(s_lambdaCoefficients, julianCenturies);
+ double anomaly = PolynomialSum(s_anomalyCoefficients, julianCenturies);
+ double eccentricity = PolynomialSum(s_eccentricityCoefficients, julianCenturies);
+
+ double epsilon = Obliquity(julianCenturies);
+ double tanHalfEpsilon = TanOfDegree(epsilon / 2);
+ double y = tanHalfEpsilon * tanHalfEpsilon;
+
+ double dividend = ((y * SinOfDegree(2 * lambda))
+ - (2 * eccentricity * SinOfDegree(anomaly))
+ + (4 * eccentricity * y * SinOfDegree(anomaly) * CosOfDegree(2 * lambda))
+ - (0.5 * Math.Pow(y, 2) * SinOfDegree(4 * lambda))
+ - (1.25 * Math.Pow(eccentricity, 2) * SinOfDegree(2 * anomaly)));
+ double divisor = 2 * Math.PI;
+ double equation = dividend / divisor;
+
+ // approximation of equation of time is not valid for dates that are many millennia in the past or future
+ // thus limited to a half day
+ return CopySign(Math.Min(Math.Abs(equation), TwelveHours), equation);
+ }
+
+ private static double AsLocalTime(double apparentMidday, double longitude)
+ {
+ // slightly inaccurate since equation of time takes mean time not apparent time as its argument, but the difference is negligible
+ double universalTime = apparentMidday - AsDayFraction(longitude);
+ return apparentMidday - EquationOfTime(universalTime);
+ }
+
+ // midday
+ public static double Midday(double date, double longitude)
+ {
+ return AsLocalTime(date + TwelveHours, longitude) - AsDayFraction(longitude);
+ }
+
+ private static double InitLongitude(double longitude)
+ {
+ return NormalizeLongitude(longitude + HalfCircleOfArc) - HalfCircleOfArc;
+ }
+
+ // midday-in-tehran
+ public static double MiddayAtPersianObservationSite(double date)
+ {
+ return Midday(date, InitLongitude(52.5)); // 52.5 degrees east - longitude of UTC+3:30 which defines Iranian Standard Time
+ }
+
+ private static double PeriodicTerm(double julianCenturies, int x, double y, double z)
+ {
+ return x * SinOfDegree(y + z * julianCenturies);
+ }
+
+ private static double SumLongSequenceOfPeriodicTerms(double julianCenturies)
+ {
+ double sum = 0.0;
+ sum += PeriodicTerm(julianCenturies, 403406, 270.54861, 0.9287892);
+ sum += PeriodicTerm(julianCenturies, 195207, 340.19128, 35999.1376958);
+ sum += PeriodicTerm(julianCenturies, 119433, 63.91854, 35999.4089666);
+ sum += PeriodicTerm(julianCenturies, 112392, 331.2622, 35998.7287385);
+ sum += PeriodicTerm(julianCenturies, 3891, 317.843, 71998.20261);
+ sum += PeriodicTerm(julianCenturies, 2819, 86.631, 71998.4403);
+ sum += PeriodicTerm(julianCenturies, 1721, 240.052, 36000.35726);
+ sum += PeriodicTerm(julianCenturies, 660, 310.26, 71997.4812);
+ sum += PeriodicTerm(julianCenturies, 350, 247.23, 32964.4678);
+ sum += PeriodicTerm(julianCenturies, 334, 260.87, -19.441);
+ sum += PeriodicTerm(julianCenturies, 314, 297.82, 445267.1117);
+ sum += PeriodicTerm(julianCenturies, 268, 343.14, 45036.884);
+ sum += PeriodicTerm(julianCenturies, 242, 166.79, 3.1008);
+ sum += PeriodicTerm(julianCenturies, 234, 81.53, 22518.4434);
+ sum += PeriodicTerm(julianCenturies, 158, 3.5, -19.9739);
+ sum += PeriodicTerm(julianCenturies, 132, 132.75, 65928.9345);
+ sum += PeriodicTerm(julianCenturies, 129, 182.95, 9038.0293);
+ sum += PeriodicTerm(julianCenturies, 114, 162.03, 3034.7684);
+ sum += PeriodicTerm(julianCenturies, 99, 29.8, 33718.148);
+ sum += PeriodicTerm(julianCenturies, 93, 266.4, 3034.448);
+ sum += PeriodicTerm(julianCenturies, 86, 249.2, -2280.773);
+ sum += PeriodicTerm(julianCenturies, 78, 157.6, 29929.992);
+ sum += PeriodicTerm(julianCenturies, 72, 257.8, 31556.493);
+ sum += PeriodicTerm(julianCenturies, 68, 185.1, 149.588);
+ sum += PeriodicTerm(julianCenturies, 64, 69.9, 9037.75);
+ sum += PeriodicTerm(julianCenturies, 46, 8.0, 107997.405);
+ sum += PeriodicTerm(julianCenturies, 38, 197.1, -4444.176);
+ sum += PeriodicTerm(julianCenturies, 37, 250.4, 151.771);
+ sum += PeriodicTerm(julianCenturies, 32, 65.3, 67555.316);
+ sum += PeriodicTerm(julianCenturies, 29, 162.7, 31556.08);
+ sum += PeriodicTerm(julianCenturies, 28, 341.5, -4561.54);
+ sum += PeriodicTerm(julianCenturies, 27, 291.6, 107996.706);
+ sum += PeriodicTerm(julianCenturies, 27, 98.5, 1221.655);
+ sum += PeriodicTerm(julianCenturies, 25, 146.7, 62894.167);
+ sum += PeriodicTerm(julianCenturies, 24, 110.0, 31437.369);
+ sum += PeriodicTerm(julianCenturies, 21, 5.2, 14578.298);
+ sum += PeriodicTerm(julianCenturies, 21, 342.6, -31931.757);
+ sum += PeriodicTerm(julianCenturies, 20, 230.9, 34777.243);
+ sum += PeriodicTerm(julianCenturies, 18, 256.1, 1221.999);
+ sum += PeriodicTerm(julianCenturies, 17, 45.3, 62894.511);
+ sum += PeriodicTerm(julianCenturies, 14, 242.9, -4442.039);
+ sum += PeriodicTerm(julianCenturies, 13, 115.2, 107997.909);
+ sum += PeriodicTerm(julianCenturies, 13, 151.8, 119.066);
+ sum += PeriodicTerm(julianCenturies, 13, 285.3, 16859.071);
+ sum += PeriodicTerm(julianCenturies, 12, 53.3, -4.578);
+ sum += PeriodicTerm(julianCenturies, 10, 126.6, 26895.292);
+ sum += PeriodicTerm(julianCenturies, 10, 205.7, -39.127);
+ sum += PeriodicTerm(julianCenturies, 10, 85.9, 12297.536);
+ sum += PeriodicTerm(julianCenturies, 10, 146.1, 90073.778);
+ return sum;
+ }
+
+ private static double Aberration(double julianCenturies)
+ {
+ return (0.0000974 * CosOfDegree(177.63 + (35999.01848 * julianCenturies))) - 0.005575;
+ }
+
+ private static double Nutation(double julianCenturies)
+ {
+ double a = PolynomialSum(s_coefficientsA, julianCenturies);
+ double b = PolynomialSum(s_coefficientsB, julianCenturies);
+ return (-0.004778 * SinOfDegree(a)) - (0.0003667 * SinOfDegree(b));
+ }
+
+ public static double Compute(double time)
+ {
+ double julianCenturies = JulianCenturies(time);
+ double lambda = 282.7771834
+ + (36000.76953744 * julianCenturies)
+ + (0.000005729577951308232 * SumLongSequenceOfPeriodicTerms(julianCenturies));
+
+ double longitude = lambda + Aberration(julianCenturies) + Nutation(julianCenturies);
+ return InitLongitude(longitude);
+ }
+
+ public static double AsSeason(double longitude)
+ {
+ return (longitude < 0) ? (longitude + FullCircleOfArc) : longitude;
+ }
+
+ private static double EstimatePrior(double longitude, double time)
+ {
+ double timeSunLastAtLongitude = time - (MeanSpeedOfSun * AsSeason(InitLongitude(Compute(time) - longitude)));
+ double longitudeErrorDelta = InitLongitude(Compute(timeSunLastAtLongitude) - longitude);
+ return Math.Min(time, timeSunLastAtLongitude - (MeanSpeedOfSun * longitudeErrorDelta));
+ }
+
+ // persian-new-year-on-or-before
+ // number of days is the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
+ // 1/1/0001 is absolute date 1.
+ internal static long PersianNewYearOnOrBefore(long numberOfDays)
+ {
+ double date = (double)numberOfDays;
+
+ double approx = EstimatePrior(LongitudeSpring, MiddayAtPersianObservationSite(date));
+ long lowerBoundNewYearDay = (long)Math.Floor(approx) - 1;
+ long upperBoundNewYearDay = lowerBoundNewYearDay + 3; // estimate is generally within a day of the actual occurrance (at the limits, the error expands, since the calculations rely on the mean tropical year which changes...)
+ long day = lowerBoundNewYearDay;
+ for (; day != upperBoundNewYearDay; ++day)
+ {
+ double midday = MiddayAtPersianObservationSite((double)day);
+ double l = Compute(midday);
+ if ((LongitudeSpring <= l) && (l <= TwoDegreesAfterSpring))
+ {
+ break;
+ }
+ }
+ Debug.Assert(day != upperBoundNewYearDay);
+
+ return day - 1;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CharUnicodeInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/CharUnicodeInfo.cs
new file mode 100644
index 0000000000..0cd8429bbc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CharUnicodeInfo.cs
@@ -0,0 +1,397 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class implements a set of methods for retrieving
+// character type information. Character type information is
+// independent of culture and region.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ public static partial class CharUnicodeInfo
+ {
+ //--------------------------------------------------------------------//
+ // Internal Information //
+ //--------------------------------------------------------------------//
+
+ //
+ // Native methods to access the Unicode category data tables in charinfo.nlp.
+ //
+ internal const char HIGH_SURROGATE_START = '\ud800';
+ internal const char HIGH_SURROGATE_END = '\udbff';
+ internal const char LOW_SURROGATE_START = '\udc00';
+ internal const char LOW_SURROGATE_END = '\udfff';
+
+ internal const int UNICODE_CATEGORY_OFFSET = 0;
+ internal const int BIDI_CATEGORY_OFFSET = 1;
+
+ // The starting codepoint for Unicode plane 1. Plane 1 contains 0x010000 ~ 0x01ffff.
+ internal const int UNICODE_PLANE01_START = 0x10000;
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Convert the BMP character or surrogate pointed by index to a UTF32 value.
+ // This is similar to Char.ConvertToUTF32, but the difference is that
+ // it does not throw exceptions when invalid surrogate characters are passed in.
+ //
+ // WARNING: since it doesn't throw an exception it CAN return a value
+ // in the surrogate range D800-DFFF, which are not legal unicode values.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static int InternalConvertToUtf32(String s, int index)
+ {
+ Debug.Assert(s != null, "s != null");
+ Debug.Assert(index >= 0 && index < s.Length, "index < s.Length");
+ if (index < s.Length - 1)
+ {
+ int temp1 = (int)s[index] - HIGH_SURROGATE_START;
+ if (temp1 >= 0 && temp1 <= 0x3ff)
+ {
+ int temp2 = (int)s[index + 1] - LOW_SURROGATE_START;
+ if (temp2 >= 0 && temp2 <= 0x3ff)
+ {
+ // Convert the surrogate to UTF32 and get the result.
+ return ((temp1 * 0x400) + temp2 + UNICODE_PLANE01_START);
+ }
+ }
+ }
+ return ((int)s[index]);
+ }
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Convert a character or a surrogate pair starting at index of string s
+ // to UTF32 value.
+ //
+ // Parameters:
+ // s The string
+ // index The starting index. It can point to a BMP character or
+ // a surrogate pair.
+ // len The length of the string.
+ // charLength [out] If the index points to a BMP char, charLength
+ // will be 1. If the index points to a surrogate pair,
+ // charLength will be 2.
+ //
+ // WARNING: since it doesn't throw an exception it CAN return a value
+ // in the surrogate range D800-DFFF, which are not legal unicode values.
+ //
+ // Returns:
+ // The UTF32 value
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static int InternalConvertToUtf32(String s, int index, out int charLength)
+ {
+ Debug.Assert(s != null, "s != null");
+ Debug.Assert(s.Length > 0, "s.Length > 0");
+ Debug.Assert(index >= 0 && index < s.Length, "index >= 0 && index < s.Length");
+ charLength = 1;
+ if (index < s.Length - 1)
+ {
+ int temp1 = (int)s[index] - HIGH_SURROGATE_START;
+ if (temp1 >= 0 && temp1 <= 0x3ff)
+ {
+ int temp2 = (int)s[index + 1] - LOW_SURROGATE_START;
+ if (temp2 >= 0 && temp2 <= 0x3ff)
+ {
+ // Convert the surrogate to UTF32 and get the result.
+ charLength++;
+ return ((temp1 * 0x400) + temp2 + UNICODE_PLANE01_START);
+ }
+ }
+ }
+ return ((int)s[index]);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsWhiteSpace
+ //
+ // Determines if the given character is a white space character.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static bool IsWhiteSpace(String s, int index)
+ {
+ Debug.Assert(s != null, "s!=null");
+ Debug.Assert(index >= 0 && index < s.Length, "index >= 0 && index < s.Length");
+
+ UnicodeCategory uc = GetUnicodeCategory(s, index);
+ // In Unicode 3.0, U+2028 is the only character which is under the category "LineSeparator".
+ // And U+2029 is th eonly character which is under the category "ParagraphSeparator".
+ switch (uc)
+ {
+ case (UnicodeCategory.SpaceSeparator):
+ case (UnicodeCategory.LineSeparator):
+ case (UnicodeCategory.ParagraphSeparator):
+ return (true);
+ }
+ return (false);
+ }
+
+
+ internal static bool IsWhiteSpace(char c)
+ {
+ UnicodeCategory uc = GetUnicodeCategory(c);
+ // In Unicode 3.0, U+2028 is the only character which is under the category "LineSeparator".
+ // And U+2029 is th eonly character which is under the category "ParagraphSeparator".
+ switch (uc)
+ {
+ case (UnicodeCategory.SpaceSeparator):
+ case (UnicodeCategory.LineSeparator):
+ case (UnicodeCategory.ParagraphSeparator):
+ return (true);
+ }
+
+ return (false);
+ }
+
+
+ //
+ // This is called by the public char and string, index versions
+ //
+ // Note that for ch in the range D800-DFFF we just treat it as any other non-numeric character
+ //
+ internal static unsafe double InternalGetNumericValue(int ch)
+ {
+ Debug.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range.");
+ // Get the level 2 item from the highest 12 bit (8 - 19) of ch.
+ ushort index = s_pNumericLevel1Index[ch >> 8];
+ // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table.
+ // The offset is referred to an float item in m_pNumericFloatData.
+ // Note that & has the lower precedence than addition, so don't forget the parathesis.
+ index = s_pNumericLevel1Index[index + ((ch >> 4) & 0x000f)];
+
+ fixed (ushort* pUshortPtr = &(s_pNumericLevel1Index[index]))
+ {
+ byte* pBytePtr = (byte*)pUshortPtr;
+ fixed (byte* pByteNum = s_pNumericValues)
+ {
+ double* pDouble = (double*)pByteNum;
+ return pDouble[pBytePtr[(ch & 0x000f)]];
+ }
+ }
+ }
+
+ internal static unsafe ushort InternalGetDigitValues(int ch)
+ {
+ Debug.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range.");
+ // Get the level 2 item from the highest 12 bit (8 - 19) of ch.
+ ushort index = s_pNumericLevel1Index[ch >> 8];
+ // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table.
+ // Note that & has the lower precedence than addition, so don't forget the parathesis.
+ index = s_pNumericLevel1Index[index + ((ch >> 4) & 0x000f)];
+
+ fixed (ushort* pUshortPtr = &(s_pNumericLevel1Index[index]))
+ {
+ byte* pBytePtr = (byte*)pUshortPtr;
+ return s_pDigitValues[pBytePtr[(ch & 0x000f)]];
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //Returns the numeric value associated with the character c. If the character is a fraction,
+ // the return value will not be an integer. If the character does not have a numeric value, the return value is -1.
+ //
+ //Returns:
+ // the numeric value for the specified Unicode character. If the character does not have a numeric value, the return value is -1.
+ //Arguments:
+ // ch a Unicode character
+ //Exceptions:
+ // ArgumentNullException
+ // ArgumentOutOfRangeException
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public static double GetNumericValue(char ch)
+ {
+ return (InternalGetNumericValue(ch));
+ }
+
+
+ public static double GetNumericValue(String s, int index)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+ if (index < 0 || index >= s.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+ return (InternalGetNumericValue(InternalConvertToUtf32(s, index)));
+ }
+
+ public static int GetDecimalDigitValue(char ch)
+ {
+ return (sbyte)(InternalGetDigitValues(ch) >> 8);
+ }
+
+ public static int GetDecimalDigitValue(String s, int index)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+
+ if (index < 0 || index >= s.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ return (sbyte)(InternalGetDigitValues(InternalConvertToUtf32(s, index)) >> 8);
+ }
+
+ public static int GetDigitValue(char ch)
+ {
+ return (sbyte)(InternalGetDigitValues(ch) & 0x00FF);
+ }
+
+ public static int GetDigitValue(String s, int index)
+ {
+ if (s == null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+
+ if (index < 0 || index >= s.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ return (sbyte)(InternalGetDigitValues(InternalConvertToUtf32(s, index)) & 0x00FF);
+ }
+
+ public static UnicodeCategory GetUnicodeCategory(char ch)
+ {
+ return (GetUnicodeCategory((int)ch));
+ }
+
+ public static UnicodeCategory GetUnicodeCategory(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ return InternalGetUnicodeCategory(s, index);
+ }
+
+ public static UnicodeCategory GetUnicodeCategory(int codePoint)
+ {
+ return ((UnicodeCategory)InternalGetCategoryValue(codePoint, UNICODE_CATEGORY_OFFSET));
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //Action: Returns the Unicode Category property for the character c.
+ //Returns:
+ // an value in UnicodeCategory enum
+ //Arguments:
+ // ch a Unicode character
+ //Exceptions:
+ // None
+ //
+ //Note that this API will return values for D800-DF00 surrogate halves.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static unsafe byte InternalGetCategoryValue(int ch, int offset)
+ {
+ Debug.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range.");
+ // Get the level 2 item from the highest 12 bit (8 - 19) of ch.
+ ushort index = s_pCategoryLevel1Index[ch >> 8];
+ // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table.
+ // Note that & has the lower precedence than addition, so don't forget the parathesis.
+ index = s_pCategoryLevel1Index[index + ((ch >> 4) & 0x000f)];
+
+ fixed (ushort* pUshortPtr = &(s_pCategoryLevel1Index[index]))
+ {
+ byte* pBytePtr = (byte*)pUshortPtr;
+ // Get the result from the 0 -3 bit of ch.
+ byte valueIndex = pBytePtr[(ch & 0x000f)];
+ byte uc = s_pCategoriesValue[valueIndex * 2 + offset];
+ //
+ // Make sure that OtherNotAssigned is the last category in UnicodeCategory.
+ // If that changes, change the following assertion as well.
+ //
+ //Debug.Assert(uc >= 0 && uc <= UnicodeCategory.OtherNotAssigned, "Table returns incorrect Unicode category");
+ return (uc);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //Action: Returns the Unicode Category property for the character c.
+ //Returns:
+ // an value in UnicodeCategory enum
+ //Arguments:
+ // value a Unicode String
+ // index Index for the specified string.
+ //Exceptions:
+ // None
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static UnicodeCategory InternalGetUnicodeCategory(String value, int index)
+ {
+ Debug.Assert(value != null, "value can not be null");
+ Debug.Assert(index < value.Length, "index < value.Length");
+
+ return (GetUnicodeCategory(InternalConvertToUtf32(value, index)));
+ }
+
+ internal static BidiCategory GetBidiCategory(String s, int index)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+
+ if (((uint)index) >= ((uint)s.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ return ((BidiCategory) InternalGetCategoryValue(InternalConvertToUtf32(s, index), BIDI_CATEGORY_OFFSET));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Get the Unicode category of the character starting at index. If the character is in BMP, charLength will return 1.
+ // If the character is a valid surrogate pair, charLength will return 2.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static UnicodeCategory InternalGetUnicodeCategory(String str, int index, out int charLength)
+ {
+ Debug.Assert(str != null, "str can not be null");
+ Debug.Assert(str.Length > 0, "str.Length > 0"); ;
+ Debug.Assert(index >= 0 && index < str.Length, "index >= 0 && index < str.Length");
+
+ return (GetUnicodeCategory(InternalConvertToUtf32(str, index, out charLength)));
+ }
+
+ internal static bool IsCombiningCategory(UnicodeCategory uc)
+ {
+ Debug.Assert(uc >= 0, "uc >= 0");
+ return (
+ uc == UnicodeCategory.NonSpacingMark ||
+ uc == UnicodeCategory.SpacingCombiningMark ||
+ uc == UnicodeCategory.EnclosingMark
+ );
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/ChineseLunisolarCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/ChineseLunisolarCalendar.cs
new file mode 100644
index 0000000000..d2b52b97be
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/ChineseLunisolarCalendar.cs
@@ -0,0 +1,386 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization
+{
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1901/02/19 2101/01/28
+ ** ChineseLunisolar 1901/01/01 2100/12/29
+ */
+
+ public class ChineseLunisolarCalendar : EastAsianLunisolarCalendar
+ {
+ //
+ // The era value for the current era.
+ //
+
+ public const int ChineseEra = 1;
+
+ internal const int MIN_LUNISOLAR_YEAR = 1901;
+ internal const int MAX_LUNISOLAR_YEAR = 2100;
+
+ internal const int MIN_GREGORIAN_YEAR = 1901;
+ internal const int MIN_GREGORIAN_MONTH = 2;
+ internal const int MIN_GREGORIAN_DAY = 19;
+
+ internal const int MAX_GREGORIAN_YEAR = 2101;
+ internal const int MAX_GREGORIAN_MONTH = 1;
+ internal const int MAX_GREGORIAN_DAY = 28;
+
+ internal static DateTime minDate = new DateTime(MIN_GREGORIAN_YEAR, MIN_GREGORIAN_MONTH, MIN_GREGORIAN_DAY);
+ internal static DateTime maxDate = new DateTime((new DateTime(MAX_GREGORIAN_YEAR, MAX_GREGORIAN_MONTH, MAX_GREGORIAN_DAY, 23, 59, 59, 999)).Ticks + 9999);
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // 1900: 1-29 2-30 3-29 4-29 5-30 6-29 7-30 8-30 Leap8-29 9-30 10-30 11-29 12-30 from Calendrical Tabulations
+ return 384;
+ }
+ }
+
+
+ private static readonly int[,] s_yinfo =
+ {
+ /*Y LM Lmon Lday DaysPerMonth D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 #Days
+ 1901 */
+ { 0 , 2 , 19 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1902 */{ 0 , 2 , 8 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1903 */{ 5 , 1 , 29 , 21096 },/* 29 30 29 30 29 29 30 29 29 30 30 29 30 383
+1904 */{ 0 , 2 , 16 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1905 */{ 0 , 2 , 4 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1906 */{ 4 , 1 , 25 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1907 */{ 0 , 2 , 13 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1908 */{ 0 , 2 , 2 , 39632 },/* 30 29 29 30 30 29 30 29 30 30 29 30 0 355
+1909 */{ 2 , 1 , 22 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1910 */{ 0 , 2 , 10 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1911 */{ 6 , 1 , 30 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1912 */{ 0 , 2 , 18 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1913 */{ 0 , 2 , 6 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1914 */{ 5 , 1 , 26 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1915 */{ 0 , 2 , 14 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1916 */{ 0 , 2 , 3 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 0 355
+1917 */{ 2 , 1 , 23 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1918 */{ 0 , 2 , 11 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1919 */{ 7 , 2 , 1 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1920 */{ 0 , 2 , 20 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1921 */{ 0 , 2 , 8 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1922 */{ 5 , 1 , 28 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1923 */{ 0 , 2 , 16 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1924 */{ 0 , 2 , 5 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1925 */{ 4 , 1 , 24 , 44456 },/* 30 29 30 29 30 30 29 30 30 29 30 29 30 385
+1926 */{ 0 , 2 , 13 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1927 */{ 0 , 2 , 2 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1928 */{ 2 , 1 , 23 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1929 */{ 0 , 2 , 10 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1930 */{ 6 , 1 , 30 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1931 */{ 0 , 2 , 17 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1932 */{ 0 , 2 , 6 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1933 */{ 5 , 1 , 26 , 27976 },/* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1934 */{ 0 , 2 , 14 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1935 */{ 0 , 2 , 4 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1936 */{ 3 , 1 , 24 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1937 */{ 0 , 2 , 11 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1938 */{ 7 , 1 , 31 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1939 */{ 0 , 2 , 19 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1940 */{ 0 , 2 , 8 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1941 */{ 6 , 1 , 27 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1942 */{ 0 , 2 , 15 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1943 */{ 0 , 2 , 5 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1944 */{ 4 , 1 , 25 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+1945 */{ 0 , 2 , 13 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1946 */{ 0 , 2 , 2 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+1947 */{ 2 , 1 , 22 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+1948 */{ 0 , 2 , 10 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1949 */{ 7 , 1 , 29 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+1950 */{ 0 , 2 , 17 , 27808 },/* 29 30 30 29 30 30 29 29 30 29 30 29 0 354
+1951 */{ 0 , 2 , 6 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1952 */{ 5 , 1 , 27 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+1953 */{ 0 , 2 , 14 , 19872 },/* 29 30 29 29 30 30 29 30 30 29 30 29 0 354
+1954 */{ 0 , 2 , 3 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+1955 */{ 3 , 1 , 24 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1956 */{ 0 , 2 , 12 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1957 */{ 8 , 1 , 31 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1958 */{ 0 , 2 , 18 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 0 355
+1959 */{ 0 , 2 , 8 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1960 */{ 6 , 1 , 28 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1961 */{ 0 , 2 , 15 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1962 */{ 0 , 2 , 5 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1963 */{ 4 , 1 , 25 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1964 */{ 0 , 2 , 13 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1965 */{ 0 , 2 , 2 , 21088 },/* 29 30 29 30 29 29 30 29 29 30 30 29 0 353
+1966 */{ 3 , 1 , 21 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1967 */{ 0 , 2 , 9 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1968 */{ 7 , 1 , 30 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1969 */{ 0 , 2 , 17 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1970 */{ 0 , 2 , 6 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1971 */{ 5 , 1 , 27 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1972 */{ 0 , 2 , 15 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+1973 */{ 0 , 2 , 3 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1974 */{ 4 , 1 , 23 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1975 */{ 0 , 2 , 11 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1976 */{ 8 , 1 , 31 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1977 */{ 0 , 2 , 18 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1978 */{ 0 , 2 , 7 , 46752 },/* 30 29 30 30 29 30 30 29 30 29 30 29 0 355
+1979 */{ 6 , 1 , 28 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1980 */{ 0 , 2 , 16 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1981 */{ 0 , 2 , 5 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1982 */{ 4 , 1 , 25 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1983 */{ 0 , 2 , 13 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1984 */{ 10 , 2 , 2 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1985 */{ 0 , 2 , 20 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1986 */{ 0 , 2 , 9 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1987 */{ 6 , 1 , 29 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 29 384
+1988 */{ 0 , 2 , 17 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1989 */{ 0 , 2 , 6 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1990 */{ 5 , 1 , 27 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1991 */{ 0 , 2 , 15 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1992 */{ 0 , 2 , 4 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1993 */{ 3 , 1 , 23 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1994 */{ 0 , 2 , 10 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1995 */{ 8 , 1 , 31 , 27432 },/* 29 30 30 29 30 29 30 30 29 29 30 29 30 384
+1996 */{ 0 , 2 , 19 , 23232 },/* 29 30 29 30 30 29 30 29 30 30 29 29 0 354
+1997 */{ 0 , 2 , 7 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1998 */{ 5 , 1 , 28 , 37736 },/* 30 29 29 30 29 29 30 30 29 30 30 29 30 384
+1999 */{ 0 , 2 , 16 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2000 */{ 0 , 2 , 5 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2001 */{ 4 , 1 , 24 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+2002 */{ 0 , 2 , 12 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2003 */{ 0 , 2 , 1 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2004 */{ 2 , 1 , 22 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2005 */{ 0 , 2 , 9 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+2006 */{ 7 , 1 , 29 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+2007 */{ 0 , 2 , 18 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+2008 */{ 0 , 2 , 7 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+2009 */{ 5 , 1 , 26 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+2010 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2011 */{ 0 , 2 , 3 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2012 */{ 4 , 1 , 23 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+2013 */{ 0 , 2 , 10 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2014 */{ 9 , 1 , 31 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2015 */{ 0 , 2 , 19 , 19360 },/* 29 30 29 29 30 29 30 30 30 29 30 29 0 354
+2016 */{ 0 , 2 , 8 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2017 */{ 6 , 1 , 28 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+2018 */{ 0 , 2 , 16 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2019 */{ 0 , 2 , 5 , 43312 },/* 30 29 30 29 30 29 29 30 29 29 30 30 0 354
+2020 */{ 4 , 1 , 25 , 29864 },/* 29 30 30 30 29 30 29 29 30 29 30 29 30 384
+2021 */{ 0 , 2 , 12 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2022 */{ 0 , 2 , 1 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2023 */{ 2 , 1 , 22 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+2024 */{ 0 , 2 , 10 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2025 */{ 6 , 1 , 29 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+2026 */{ 0 , 2 , 17 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+2027 */{ 0 , 2 , 6 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+2028 */{ 5 , 1 , 26 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+2029 */{ 0 , 2 , 13 , 54576 },/* 30 30 29 30 29 30 29 30 29 29 30 30 0 355
+2030 */{ 0 , 2 , 3 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+2031 */{ 3 , 1 , 23 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+2032 */{ 0 , 2 , 11 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2033 */{ 11 , 1 , 31 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+2034 */{ 0 , 2 , 19 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+2035 */{ 0 , 2 , 8 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2036 */{ 6 , 1 , 28 , 53848 },/* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+2037 */{ 0 , 2 , 15 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+2038 */{ 0 , 2 , 4 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2039 */{ 5 , 1 , 24 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 29 384
+2040 */{ 0 , 2 , 12 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 0 355
+2041 */{ 0 , 2 , 1 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+2042 */{ 2 , 1 , 22 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+2043 */{ 0 , 2 , 10 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+2044 */{ 7 , 1 , 30 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+2045 */{ 0 , 2 , 17 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+2046 */{ 0 , 2 , 6 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+2047 */{ 5 , 1 , 26 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+2048 */{ 0 , 2 , 14 , 27936 },/* 29 30 30 29 30 30 29 30 29 29 30 29 0 354
+2049 */{ 0 , 2 , 2 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+2050 */{ 3 , 1 , 23 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+2051 */{ 0 , 2 , 11 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+2052 */{ 8 , 2 , 1 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+2053 */{ 0 , 2 , 19 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+2054 */{ 0 , 2 , 8 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+2055 */{ 6 , 1 , 28 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+2056 */{ 0 , 2 , 15 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+2057 */{ 0 , 2 , 4 , 27424 },/* 29 30 30 29 30 29 30 30 29 29 30 29 0 354
+2058 */{ 4 , 1 , 24 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 29 384
+2059 */{ 0 , 2 , 12 , 43744 },/* 30 29 30 29 30 29 30 29 30 30 30 29 0 355
+2060 */{ 0 , 2 , 2 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2061 */{ 3 , 1 , 21 , 51568 },/* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+2062 */{ 0 , 2 , 9 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2063 */{ 7 , 1 , 29 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+2064 */{ 0 , 2 , 17 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2065 */{ 0 , 2 , 5 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2066 */{ 5 , 1 , 26 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2067 */{ 0 , 2 , 14 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+2068 */{ 0 , 2 , 3 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 0 355
+2069 */{ 4 , 1 , 23 , 21224 },/* 29 30 29 30 29 29 30 29 30 30 30 29 30 384
+2070 */{ 0 , 2 , 11 , 21200 },/* 29 30 29 30 29 29 30 29 30 30 29 30 0 354
+2071 */{ 8 , 1 , 31 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+2072 */{ 0 , 2 , 19 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2073 */{ 0 , 2 , 7 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2074 */{ 6 , 1 , 27 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+2075 */{ 0 , 2 , 15 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2076 */{ 0 , 2 , 5 , 21920 },/* 29 30 29 30 29 30 29 30 30 29 30 29 0 354
+2077 */{ 4 , 1 , 24 , 42448 },/* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+2078 */{ 0 , 2 , 12 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2079 */{ 0 , 2 , 2 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2080 */{ 3 , 1 , 22 , 43320 },/* 30 29 30 29 30 29 29 30 29 29 30 30 30 384
+2081 */{ 0 , 2 , 9 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 0 354
+2082 */{ 7 , 1 , 29 , 29336 },/* 29 30 30 30 29 29 30 29 30 29 29 30 30 384
+2083 */{ 0 , 2 , 17 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2084 */{ 0 , 2 , 6 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2085 */{ 5 , 1 , 26 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+2086 */{ 0 , 2 , 14 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2087 */{ 0 , 2 , 3 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+2088 */{ 4 , 1 , 24 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 29 383
+2089 */{ 0 , 2 , 10 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+2090 */{ 8 , 1 , 30 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+2091 */{ 0 , 2 , 18 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2092 */{ 0 , 2 , 7 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 0 355
+2093 */{ 6 , 1 , 27 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+2094 */{ 0 , 2 , 15 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+2095 */{ 0 , 2 , 5 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+2096 */{ 4 , 1 , 25 , 42216 },/* 30 29 30 29 29 30 29 29 30 30 30 29 30 384
+2097 */{ 0 , 2 , 12 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2098 */{ 0 , 2 , 1 , 53584 },/* 30 30 29 30 29 29 29 30 29 30 29 30 0 354
+2099 */{ 2 , 1 , 21 , 55592 },/* 30 30 29 30 30 29 29 30 29 29 30 29 30 384
+2100 */{ 0 , 2 , 9 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+ */};
+
+
+ internal override int MinCalendarYear
+ {
+ get
+ {
+ return (MIN_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override int MaxCalendarYear
+ {
+ get
+ {
+ return (MAX_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override DateTime MinDate
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ internal override DateTime MaxDate
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ internal override EraInfo[] CalEraInfo
+ {
+ get
+ {
+ return (null);
+ }
+ }
+
+ internal override int GetYearInfo(int lunarYear, int index)
+ {
+ if ((lunarYear < MIN_LUNISOLAR_YEAR) || (lunarYear > MAX_LUNISOLAR_YEAR))
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR));
+ }
+
+ return s_yinfo[lunarYear - MIN_LUNISOLAR_YEAR, index];
+ }
+
+ internal override int GetYear(int year, DateTime time)
+ {
+ return year;
+ }
+
+ internal override int GetGregorianYear(int year, int era)
+ {
+ if (era != CurrentEra && era != ChineseEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ if (year < MIN_LUNISOLAR_YEAR || year > MAX_LUNISOLAR_YEAR)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR));
+ }
+
+ return year;
+ }
+
+ public ChineseLunisolarCalendar()
+ {
+ }
+
+ public override int GetEra(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return (ChineseEra);
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.CHINESELUNISOLAR);
+ }
+ }
+
+ internal override CalendarId BaseCalendarID
+ {
+ get
+ {
+ //Use CAL_GREGORIAN just to get CurrentEraValue as 1 since we do not have data under the ID CAL_ChineseLunisolar yet
+ return (CalendarId.GREGORIAN);
+ }
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { ChineseEra });
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs
new file mode 100644
index 0000000000..29e4f53212
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Invariant.cs
@@ -0,0 +1,249 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Globalization
+{
+ public partial class CompareInfo
+ {
+ internal static unsafe int InvariantIndexOf(string source, string value, int startIndex, int count, bool ignoreCase)
+ {
+ Debug.Assert(source != null);
+ Debug.Assert(value != null);
+ Debug.Assert(startIndex >= 0 && startIndex < source.Length);
+
+ fixed (char* pSource = source) fixed (char* pValue = value)
+ {
+ char* pSrc = &pSource[startIndex];
+ int index = InvariantFindString(pSrc, count, pValue, value.Length, ignoreCase, start : true);
+ if (index >= 0)
+ {
+ return index + startIndex;
+ }
+ return -1;
+ }
+ }
+
+ internal static unsafe int InvariantIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ return InvariantFindString(pSource, source.Length, pValue, value.Length, ignoreCase, start: true);
+ }
+ }
+
+ internal static unsafe int InvariantLastIndexOf(string source, string value, int startIndex, int count, bool ignoreCase)
+ {
+ Debug.Assert(source != null);
+ Debug.Assert(value != null);
+ Debug.Assert(startIndex >= 0 && startIndex < source.Length);
+
+ fixed (char* pSource = source) fixed (char* pValue = value)
+ {
+ char* pSrc = &pSource[startIndex - count + 1];
+ int index = InvariantFindString(pSrc, count, pValue, value.Length, ignoreCase, start : false);
+ if (index >= 0)
+ {
+ return index + startIndex - count + 1;
+ }
+ return -1;
+ }
+ }
+
+ private static unsafe int InvariantFindString(char* source, int sourceCount, char* value, int valueCount, bool ignoreCase, bool start)
+ {
+ int ctrSource = 0; // index value into source
+ int ctrValue = 0; // index value into value
+ char sourceChar; // Character for case lookup in source
+ char valueChar; // Character for case lookup in value
+ int lastSourceStart;
+
+ Debug.Assert(source != null);
+ Debug.Assert(value != null);
+ Debug.Assert(sourceCount >= 0);
+ Debug.Assert(valueCount >= 0);
+
+ if (valueCount == 0)
+ {
+ return start ? 0 : sourceCount - 1;
+ }
+
+ if (sourceCount < valueCount)
+ {
+ return -1;
+ }
+
+ if (start)
+ {
+ lastSourceStart = sourceCount - valueCount;
+ if (ignoreCase)
+ {
+ char firstValueChar = InvariantToUpper(value[0]);
+ for (ctrSource = 0; ctrSource <= lastSourceStart; ctrSource++)
+ {
+ sourceChar = InvariantToUpper(source[ctrSource]);
+ if (sourceChar != firstValueChar)
+ {
+ continue;
+ }
+
+ for (ctrValue = 1; ctrValue < valueCount; ctrValue++)
+ {
+ sourceChar = InvariantToUpper(source[ctrSource + ctrValue]);
+ valueChar = InvariantToUpper(value[ctrValue]);
+
+ if (sourceChar != valueChar)
+ {
+ break;
+ }
+ }
+
+ if (ctrValue == valueCount)
+ {
+ return ctrSource;
+ }
+ }
+ }
+ else
+ {
+ char firstValueChar = value[0];
+ for (ctrSource = 0; ctrSource <= lastSourceStart; ctrSource++)
+ {
+ sourceChar = source[ctrSource];
+ if (sourceChar != firstValueChar)
+ {
+ continue;
+ }
+
+ for (ctrValue = 1; ctrValue < valueCount; ctrValue++)
+ {
+ sourceChar = source[ctrSource + ctrValue];
+ valueChar = value[ctrValue];
+
+ if (sourceChar != valueChar)
+ {
+ break;
+ }
+ }
+
+ if (ctrValue == valueCount)
+ {
+ return ctrSource;
+ }
+ }
+ }
+ }
+ else
+ {
+ lastSourceStart = sourceCount - valueCount;
+ if (ignoreCase)
+ {
+ char firstValueChar = InvariantToUpper(value[0]);
+ for (ctrSource = lastSourceStart; ctrSource >= 0; ctrSource--)
+ {
+ sourceChar = InvariantToUpper(source[ctrSource]);
+ if (sourceChar != firstValueChar)
+ {
+ continue;
+ }
+ for (ctrValue = 1; ctrValue < valueCount; ctrValue++)
+ {
+ sourceChar = InvariantToUpper(source[ctrSource + ctrValue]);
+ valueChar = InvariantToUpper(value[ctrValue]);
+
+ if (sourceChar != valueChar)
+ {
+ break;
+ }
+ }
+
+ if (ctrValue == valueCount)
+ {
+ return ctrSource;
+ }
+ }
+ }
+ else
+ {
+ char firstValueChar = value[0];
+ for (ctrSource = lastSourceStart; ctrSource >= 0; ctrSource--)
+ {
+ sourceChar = source[ctrSource];
+ if (sourceChar != firstValueChar)
+ {
+ continue;
+ }
+
+ for (ctrValue = 1; ctrValue < valueCount; ctrValue++)
+ {
+ sourceChar = source[ctrSource + ctrValue];
+ valueChar = value[ctrValue];
+
+ if (sourceChar != valueChar)
+ {
+ break;
+ }
+ }
+
+ if (ctrValue == valueCount)
+ {
+ return ctrSource;
+ }
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ private static char InvariantToUpper(char c)
+ {
+ return (uint)(c - 'a') <= (uint)('z' - 'a') ? (char)(c - 0x20) : c;
+ }
+
+ private unsafe SortKey InvariantCreateSortKey(string source, CompareOptions options)
+ {
+ if (source == null) { throw new ArgumentNullException(nameof(source)); }
+
+ if ((options & ValidSortkeyCtorMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ byte [] keyData;
+ if (source.Length == 0)
+ {
+ keyData = Array.Empty<byte>();
+ }
+ else
+ {
+ // In the invariant mode, all string comparisons are done as ordinal so when generating the sort keys we generate it according to this fact
+ keyData = new byte[source.Length * sizeof(char)];
+
+ fixed (char* pChar = source) fixed (byte* pByte = keyData)
+ {
+ if ((options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0)
+ {
+ short *pShort = (short *) pByte;
+ for (int i=0; i<source.Length; i++)
+ {
+ pShort[i] = (short) InvariantToUpper(source[i]);
+ }
+ }
+ else
+ {
+ Buffer.MemoryCopy(pChar, pByte, keyData.Length, keyData.Length);
+ }
+ }
+ }
+ return new SortKey(Name, source, options, keyData);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs
new file mode 100644
index 0000000000..5a68492c69
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs
@@ -0,0 +1,1014 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
+
+namespace System.Globalization
+{
+ public partial class CompareInfo
+ {
+ [NonSerialized]
+ private Interop.Globalization.SafeSortHandle _sortHandle;
+
+ [NonSerialized]
+ private bool _isAsciiEqualityOrdinal;
+
+ private void InitSort(CultureInfo culture)
+ {
+ _sortName = culture.SortName;
+
+ if (_invariantMode)
+ {
+ _isAsciiEqualityOrdinal = true;
+ }
+ else
+ {
+ Interop.Globalization.ResultCode resultCode = Interop.Globalization.GetSortHandle(GetNullTerminatedUtf8String(_sortName), out _sortHandle);
+ if (resultCode != Interop.Globalization.ResultCode.Success)
+ {
+ _sortHandle.Dispose();
+
+ if (resultCode == Interop.Globalization.ResultCode.OutOfMemory)
+ throw new OutOfMemoryException();
+
+ throw new ExternalException(SR.Arg_ExternalException);
+ }
+ _isAsciiEqualityOrdinal = (_sortName == "en-US" || _sortName == "");
+ }
+ }
+
+ internal static unsafe int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source != null);
+ Debug.Assert(value != null);
+
+ if (value.Length == 0)
+ {
+ return startIndex;
+ }
+
+ if (count < value.Length)
+ {
+ return -1;
+ }
+
+ if (ignoreCase)
+ {
+ fixed (char* pSource = source)
+ {
+ int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(value, value.Length, pSource + startIndex, count, findLast: false);
+ return index != -1 ?
+ startIndex + index :
+ -1;
+ }
+ }
+
+ int endIndex = startIndex + (count - value.Length);
+ for (int i = startIndex; i <= endIndex; i++)
+ {
+ int valueIndex, sourceIndex;
+
+ for (valueIndex = 0, sourceIndex = i;
+ valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
+ valueIndex++, sourceIndex++) ;
+
+ if (valueIndex == value.Length)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (source.Length < value.Length)
+ {
+ return -1;
+ }
+
+ if (ignoreCase)
+ {
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(pValue, value.Length, pSource, source.Length, findLast: false);
+ return index;
+ }
+ }
+
+ int endIndex = source.Length - value.Length;
+ for (int i = 0; i <= endIndex; i++)
+ {
+ int valueIndex, sourceIndex;
+
+ for (valueIndex = 0, sourceIndex = i;
+ valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
+ valueIndex++, sourceIndex++)
+ ;
+
+ if (valueIndex == value.Length)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ internal static unsafe int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source != null);
+ Debug.Assert(value != null);
+
+ if (value.Length == 0)
+ {
+ return startIndex;
+ }
+
+ if (count < value.Length)
+ {
+ return -1;
+ }
+
+ // startIndex is the index into source where we start search backwards from.
+ // leftStartIndex is the index into source of the start of the string that is
+ // count characters away from startIndex.
+ int leftStartIndex = startIndex - count + 1;
+
+ if (ignoreCase)
+ {
+ fixed (char* pSource = source)
+ {
+ int lastIndex = Interop.Globalization.IndexOfOrdinalIgnoreCase(value, value.Length, pSource + leftStartIndex, count, findLast: true);
+ return lastIndex != -1 ?
+ leftStartIndex + lastIndex :
+ -1;
+ }
+ }
+
+ for (int i = startIndex - value.Length + 1; i >= leftStartIndex; i--)
+ {
+ int valueIndex, sourceIndex;
+
+ for (valueIndex = 0, sourceIndex = i;
+ valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
+ valueIndex++, sourceIndex++) ;
+
+ if (valueIndex == value.Length) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private static unsafe int CompareStringOrdinalIgnoreCase(char* string1, int count1, char* string2, int count2)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(string1 != null);
+ Debug.Assert(string2 != null);
+
+ return Interop.Globalization.CompareStringOrdinalIgnoreCase(string1, count1, string2, count2);
+ }
+
+ // TODO https://github.com/dotnet/coreclr/issues/13827:
+ // This method shouldn't be necessary, as we should be able to just use the overload
+ // that takes two spans. But due to this issue, that's adding significant overhead.
+ private unsafe int CompareString(ReadOnlySpan<char> string1, string string2, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(string2 != null);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
+ fixed (char* pString2 = &string2.GetRawStringData())
+ {
+ return Interop.Globalization.CompareString(_sortHandle, pString1, string1.Length, pString2, string2.Length, options);
+ }
+ }
+
+ private unsafe int CompareString(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
+ fixed (char* pString2 = &MemoryMarshal.GetReference(string2))
+ {
+ return Interop.Globalization.CompareString(_sortHandle, pString1, string1.Length, pString2, string2.Length, options);
+ }
+ }
+
+ internal unsafe int IndexOfCore(string source, string target, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!string.IsNullOrEmpty(source));
+ Debug.Assert(target != null);
+ Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
+
+ int index;
+
+ if (target.Length == 0)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = 0;
+ return startIndex;
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ index = IndexOfOrdinal(source, target, startIndex, count, ignoreCase: false);
+ if (index != -1)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ }
+ return index;
+ }
+
+#if CORECLR
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort())
+ {
+ index = IndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options));
+ if (index != -1)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ }
+ return index;
+ }
+#endif
+
+ fixed (char* pSource = source)
+ {
+ index = Interop.Globalization.IndexOf(_sortHandle, target, target.Length, pSource + startIndex, count, options, matchLengthPtr);
+
+ return index != -1 ? index + startIndex : -1;
+ }
+ }
+
+ // For now, this method is only called from Span APIs with either options == CompareOptions.None or CompareOptions.IgnoreCase
+ internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(target.Length != 0);
+
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
+ {
+ if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
+ {
+ return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr);
+ }
+ else
+ {
+ return IndexOfOrdinalHelper(source, target, options, matchLengthPtr);
+ }
+ }
+ else
+ {
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pTarget = &MemoryMarshal.GetReference(target))
+ {
+ return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
+ }
+ }
+ }
+
+ private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!target.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(target))
+ {
+ char* a = ap;
+ char* b = bp;
+ int endIndex = source.Length - target.Length;
+
+ if (endIndex < 0)
+ goto InteropCall;
+
+ for (int j = 0; j < target.Length; j++)
+ {
+ char targetChar = *(b + j);
+ if (targetChar >= 0x80 || s_highCharTable[targetChar])
+ goto InteropCall;
+ }
+
+ int i = 0;
+ for (; i <= endIndex; i++)
+ {
+ int targetIndex = 0;
+ int sourceIndex = i;
+
+ for (; targetIndex < target.Length; targetIndex++)
+ {
+ char valueChar = *(a + sourceIndex);
+ char targetChar = *(b + targetIndex);
+
+ if (valueChar == targetChar && valueChar < 0x80 && !s_highCharTable[valueChar])
+ {
+ sourceIndex++;
+ continue;
+ }
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(valueChar - 'a') <= ('z' - 'a'))
+ valueChar = (char)(valueChar - 0x20);
+ if ((uint)(targetChar - 'a') <= ('z' - 'a'))
+ targetChar = (char)(targetChar - 0x20);
+
+ if (valueChar >= 0x80 || s_highCharTable[valueChar])
+ goto InteropCall;
+ else if (valueChar != targetChar)
+ break;
+ sourceIndex++;
+ }
+
+ if (targetIndex == target.Length)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ return i;
+ }
+ }
+ if (i > endIndex)
+ return -1;
+ InteropCall:
+ return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ }
+ }
+
+ private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!target.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(target))
+ {
+ char* a = ap;
+ char* b = bp;
+ int endIndex = source.Length - target.Length;
+
+ if (endIndex < 0)
+ goto InteropCall;
+
+ for (int j = 0; j < target.Length; j++)
+ {
+ char targetChar = *(b + j);
+ if (targetChar >= 0x80 || s_highCharTable[targetChar])
+ goto InteropCall;
+ }
+
+ int i = 0;
+ for (; i <= endIndex; i++)
+ {
+ int targetIndex = 0;
+ int sourceIndex = i;
+
+ for (; targetIndex < target.Length; targetIndex++)
+ {
+ char valueChar = *(a + sourceIndex);
+ char targetChar = *(b + targetIndex);
+ if (valueChar >= 0x80 || s_highCharTable[valueChar])
+ goto InteropCall;
+ else if (valueChar != targetChar)
+ break;
+ sourceIndex++;
+ }
+
+ if (targetIndex == target.Length)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ return i;
+ }
+ }
+ if (i > endIndex)
+ return -1;
+ InteropCall:
+ return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ }
+ }
+
+ private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!string.IsNullOrEmpty(source));
+ Debug.Assert(target != null);
+ Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
+
+ if (target.Length == 0)
+ {
+ return startIndex;
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return LastIndexOfOrdinalCore(source, target, startIndex, count, ignoreCase: false);
+ }
+
+#if CORECLR
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort())
+ {
+ return LastIndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options));
+ }
+#endif
+
+ // startIndex is the index into source where we start search backwards from. leftStartIndex is the index into source
+ // of the start of the string that is count characters away from startIndex.
+ int leftStartIndex = (startIndex - count + 1);
+
+ fixed (char* pSource = source)
+ {
+ int lastIndex = Interop.Globalization.LastIndexOf(_sortHandle, target, target.Length, pSource + (startIndex - count + 1), count, options);
+
+ return lastIndex != -1 ? lastIndex + leftStartIndex : -1;
+ }
+ }
+
+ private bool StartsWith(string source, string prefix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!string.IsNullOrEmpty(source));
+ Debug.Assert(!string.IsNullOrEmpty(prefix));
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+#if CORECLR
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && prefix.IsFastSort())
+ {
+ return IsPrefix(source, prefix, GetOrdinalCompareOptions(options));
+ }
+#endif
+
+ return Interop.Globalization.StartsWith(_sortHandle, prefix, prefix.Length, source, source.Length, options);
+ }
+
+ private unsafe bool StartsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!prefix.IsEmpty);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
+ {
+ if (source.Length < prefix.Length)
+ {
+ return false;
+ }
+
+ if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
+ {
+ return StartsWithOrdinalIgnoreCaseHelper(source, prefix, options);
+ }
+ else
+ {
+ return StartsWithOrdinalHelper(source, prefix, options);
+ }
+ }
+ else
+ {
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pPrefix = &MemoryMarshal.GetReference(prefix))
+ {
+ return Interop.Globalization.StartsWith(_sortHandle, pPrefix, prefix.Length, pSource, source.Length, options);
+ }
+ }
+ }
+
+ private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!prefix.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+ Debug.Assert(source.Length >= prefix.Length);
+
+ int length = prefix.Length;
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(prefix))
+ {
+ char* a = ap;
+ char* b = bp;
+
+ while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
+ {
+ int charA = *a;
+ int charB = *b;
+
+ if (charA == charB)
+ {
+ a++; b++;
+ length--;
+ continue;
+ }
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
+ if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
+
+ if (charA != charB)
+ return false;
+
+ // Next char
+ a++; b++;
+ length--;
+ }
+
+ if (length == 0) return true;
+ return Interop.Globalization.StartsWith(_sortHandle, b, length, a, length, options);
+ }
+ }
+
+ private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!prefix.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+ Debug.Assert(source.Length >= prefix.Length);
+
+ int length = prefix.Length;
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(prefix))
+ {
+ char* a = ap;
+ char* b = bp;
+
+ while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
+ {
+ int charA = *a;
+ int charB = *b;
+
+ if (charA != charB)
+ return false;
+
+ // Next char
+ a++; b++;
+ length--;
+ }
+
+ if (length == 0) return true;
+ return Interop.Globalization.StartsWith(_sortHandle, b, length, a, length, options);
+ }
+ }
+
+ private bool EndsWith(string source, string suffix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!string.IsNullOrEmpty(source));
+ Debug.Assert(!string.IsNullOrEmpty(suffix));
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+#if CORECLR
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && suffix.IsFastSort())
+ {
+ return IsSuffix(source, suffix, GetOrdinalCompareOptions(options));
+ }
+#endif
+
+ return Interop.Globalization.EndsWith(_sortHandle, suffix, suffix.Length, source, source.Length, options);
+ }
+
+ private unsafe bool EndsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!suffix.IsEmpty);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
+ {
+ if (source.Length < suffix.Length)
+ {
+ return false;
+ }
+
+ if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
+ {
+ return EndsWithOrdinalIgnoreCaseHelper(source, suffix, options);
+ }
+ else
+ {
+ return EndsWithOrdinalHelper(source, suffix, options);
+ }
+ }
+ else
+ {
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pSuffix = &MemoryMarshal.GetReference(suffix))
+ {
+ return Interop.Globalization.EndsWith(_sortHandle, pSuffix, suffix.Length, pSource, source.Length, options);
+ }
+ }
+ }
+
+ private unsafe bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!suffix.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+ Debug.Assert(source.Length >= suffix.Length);
+
+ int length = suffix.Length;
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(suffix))
+ {
+ char* a = ap + source.Length - 1;
+ char* b = bp + suffix.Length - 1;
+
+ while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
+ {
+ int charA = *a;
+ int charB = *b;
+
+ if (charA == charB)
+ {
+ a--; b--;
+ length--;
+ continue;
+ }
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
+ if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
+
+ if (charA != charB)
+ return false;
+
+ // Next char
+ a--; b--;
+ length--;
+ }
+
+ if (length == 0) return true;
+ return Interop.Globalization.EndsWith(_sortHandle, b - length + 1, length, a - length + 1, length, options);
+ }
+ }
+
+ private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!suffix.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+ Debug.Assert(source.Length >= suffix.Length);
+
+ int length = suffix.Length;
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(suffix))
+ {
+ char* a = ap + source.Length - 1;
+ char* b = bp + suffix.Length - 1;
+
+ while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
+ {
+ int charA = *a;
+ int charB = *b;
+
+ if (charA != charB)
+ return false;
+
+ // Next char
+ a--; b--;
+ length--;
+ }
+
+ if (length == 0) return true;
+ return Interop.Globalization.EndsWith(_sortHandle, b - length + 1, length, a - length + 1, length, options);
+ }
+ }
+
+ private unsafe SortKey CreateSortKey(String source, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ if (source==null) { throw new ArgumentNullException(nameof(source)); }
+
+ if ((options & ValidSortkeyCtorMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ byte [] keyData;
+ if (source.Length == 0)
+ {
+ keyData = Array.Empty<Byte>();
+ }
+ else
+ {
+ int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, null, 0, options);
+ keyData = new byte[sortKeyLength];
+
+ fixed (byte* pSortKey = keyData)
+ {
+ if (Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
+ {
+ throw new ArgumentException(SR.Arg_ExternalException);
+ }
+ }
+ }
+
+ return new SortKey(Name, source, options, keyData);
+ }
+
+ private static unsafe bool IsSortable(char *text, int length)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ int index = 0;
+ UnicodeCategory uc;
+
+ while (index < length)
+ {
+ if (Char.IsHighSurrogate(text[index]))
+ {
+ if (index == length - 1 || !Char.IsLowSurrogate(text[index+1]))
+ return false; // unpaired surrogate
+
+ uc = CharUnicodeInfo.GetUnicodeCategory(Char.ConvertToUtf32(text[index], text[index+1]));
+ if (uc == UnicodeCategory.PrivateUse || uc == UnicodeCategory.OtherNotAssigned)
+ return false;
+
+ index += 2;
+ continue;
+ }
+
+ if (Char.IsLowSurrogate(text[index]))
+ {
+ return false; // unpaired surrogate
+ }
+
+ uc = CharUnicodeInfo.GetUnicodeCategory(text[index]);
+ if (uc == UnicodeCategory.PrivateUse || uc == UnicodeCategory.OtherNotAssigned)
+ {
+ return false;
+ }
+
+ index++;
+ }
+
+ return true;
+ }
+
+ // -----------------------------
+ // ---- PAL layer ends here ----
+ // -----------------------------
+
+ internal unsafe int GetHashCodeOfStringCore(string source, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(source != null);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ if (source.Length == 0)
+ {
+ return 0;
+ }
+
+ int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, null, 0, options);
+
+ byte[] borrowedArr = null;
+ Span<byte> span = sortKeyLength <= 512 ?
+ stackalloc byte[512] :
+ (borrowedArr = ArrayPool<byte>.Shared.Rent(sortKeyLength));
+
+ fixed (byte* pSortKey = &MemoryMarshal.GetReference(span))
+ {
+ if (Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
+ {
+ throw new ArgumentException(SR.Arg_ExternalException);
+ }
+ }
+
+ int hash = Marvin.ComputeHash32(span.Slice(0, sortKeyLength), Marvin.DefaultSeed);
+
+ // Return the borrowed array if necessary.
+ if (borrowedArr != null)
+ {
+ ArrayPool<byte>.Shared.Return(borrowedArr);
+ }
+
+ return hash;
+ }
+
+ private static CompareOptions GetOrdinalCompareOptions(CompareOptions options)
+ {
+ if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
+ {
+ return CompareOptions.OrdinalIgnoreCase;
+ }
+ else
+ {
+ return CompareOptions.Ordinal;
+ }
+ }
+
+ private static bool CanUseAsciiOrdinalForOptions(CompareOptions options)
+ {
+ // Unlike the other Ignore options, IgnoreSymbols impacts ASCII characters (e.g. ').
+ return (options & CompareOptions.IgnoreSymbols) == 0;
+ }
+
+ private static byte[] GetNullTerminatedUtf8String(string s)
+ {
+ int byteLen = System.Text.Encoding.UTF8.GetByteCount(s);
+
+ // Allocate an extra byte (which defaults to 0) as the null terminator.
+ byte[] buffer = new byte[byteLen + 1];
+
+ int bytesWritten = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, buffer, 0);
+
+ Debug.Assert(bytesWritten == byteLen);
+
+ return buffer;
+ }
+
+ private SortVersion GetSortVersion()
+ {
+ Debug.Assert(!_invariantMode);
+
+ int sortVersion = Interop.Globalization.GetSortVersion(_sortHandle);
+ return new SortVersion(sortVersion, LCID, new Guid(sortVersion, 0, 0, 0, 0, 0, 0,
+ (byte) (LCID >> 24),
+ (byte) ((LCID & 0x00FF0000) >> 16),
+ (byte) ((LCID & 0x0000FF00) >> 8),
+ (byte) (LCID & 0xFF)));
+ }
+
+ // See https://github.com/dotnet/coreclr/blob/master/src/utilcode/util_nodependencies.cpp#L970
+ private static readonly bool[] s_highCharTable = new bool[0x80]
+ {
+ true, /* 0x0, 0x0 */
+ true, /* 0x1, .*/
+ true, /* 0x2, .*/
+ true, /* 0x3, .*/
+ true, /* 0x4, .*/
+ true, /* 0x5, .*/
+ true, /* 0x6, .*/
+ true, /* 0x7, .*/
+ true, /* 0x8, .*/
+ false, /* 0x9, */
+ true, /* 0xA, */
+ false, /* 0xB, .*/
+ false, /* 0xC, .*/
+ true, /* 0xD, */
+ true, /* 0xE, .*/
+ true, /* 0xF, .*/
+ true, /* 0x10, .*/
+ true, /* 0x11, .*/
+ true, /* 0x12, .*/
+ true, /* 0x13, .*/
+ true, /* 0x14, .*/
+ true, /* 0x15, .*/
+ true, /* 0x16, .*/
+ true, /* 0x17, .*/
+ true, /* 0x18, .*/
+ true, /* 0x19, .*/
+ true, /* 0x1A, */
+ true, /* 0x1B, .*/
+ true, /* 0x1C, .*/
+ true, /* 0x1D, .*/
+ true, /* 0x1E, .*/
+ true, /* 0x1F, .*/
+ false, /*0x20, */
+ false, /*0x21, !*/
+ false, /*0x22, "*/
+ false, /*0x23, #*/
+ false, /*0x24, $*/
+ false, /*0x25, %*/
+ false, /*0x26, &*/
+ true, /*0x27, '*/
+ false, /*0x28, (*/
+ false, /*0x29, )*/
+ false, /*0x2A **/
+ false, /*0x2B, +*/
+ false, /*0x2C, ,*/
+ true, /*0x2D, -*/
+ false, /*0x2E, .*/
+ false, /*0x2F, /*/
+ false, /*0x30, 0*/
+ false, /*0x31, 1*/
+ false, /*0x32, 2*/
+ false, /*0x33, 3*/
+ false, /*0x34, 4*/
+ false, /*0x35, 5*/
+ false, /*0x36, 6*/
+ false, /*0x37, 7*/
+ false, /*0x38, 8*/
+ false, /*0x39, 9*/
+ false, /*0x3A, :*/
+ false, /*0x3B, ;*/
+ false, /*0x3C, <*/
+ false, /*0x3D, =*/
+ false, /*0x3E, >*/
+ false, /*0x3F, ?*/
+ false, /*0x40, @*/
+ false, /*0x41, A*/
+ false, /*0x42, B*/
+ false, /*0x43, C*/
+ false, /*0x44, D*/
+ false, /*0x45, E*/
+ false, /*0x46, F*/
+ false, /*0x47, G*/
+ false, /*0x48, H*/
+ false, /*0x49, I*/
+ false, /*0x4A, J*/
+ false, /*0x4B, K*/
+ false, /*0x4C, L*/
+ false, /*0x4D, M*/
+ false, /*0x4E, N*/
+ false, /*0x4F, O*/
+ false, /*0x50, P*/
+ false, /*0x51, Q*/
+ false, /*0x52, R*/
+ false, /*0x53, S*/
+ false, /*0x54, T*/
+ false, /*0x55, U*/
+ false, /*0x56, V*/
+ false, /*0x57, W*/
+ false, /*0x58, X*/
+ false, /*0x59, Y*/
+ false, /*0x5A, Z*/
+ false, /*0x5B, [*/
+ false, /*0x5C, \*/
+ false, /*0x5D, ]*/
+ false, /*0x5E, ^*/
+ false, /*0x5F, _*/
+ false, /*0x60, `*/
+ false, /*0x61, a*/
+ false, /*0x62, b*/
+ false, /*0x63, c*/
+ false, /*0x64, d*/
+ false, /*0x65, e*/
+ false, /*0x66, f*/
+ false, /*0x67, g*/
+ false, /*0x68, h*/
+ false, /*0x69, i*/
+ false, /*0x6A, j*/
+ false, /*0x6B, k*/
+ false, /*0x6C, l*/
+ false, /*0x6D, m*/
+ false, /*0x6E, n*/
+ false, /*0x6F, o*/
+ false, /*0x70, p*/
+ false, /*0x71, q*/
+ false, /*0x72, r*/
+ false, /*0x73, s*/
+ false, /*0x74, t*/
+ false, /*0x75, u*/
+ false, /*0x76, v*/
+ false, /*0x77, w*/
+ false, /*0x78, x*/
+ false, /*0x79, y*/
+ false, /*0x7A, z*/
+ false, /*0x7B, {*/
+ false, /*0x7C, |*/
+ false, /*0x7D, }*/
+ false, /*0x7E, ~*/
+ true, /*0x7F, */
+ };
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs
new file mode 100644
index 0000000000..37ed9469d9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs
@@ -0,0 +1,644 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Security;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.Globalization
+{
+ public partial class CompareInfo
+ {
+ private unsafe void InitSort(CultureInfo culture)
+ {
+ _sortName = culture.SortName;
+
+ if (_invariantMode)
+ {
+ _sortHandle = IntPtr.Zero;
+ }
+ else
+ {
+ const uint LCMAP_SORTHANDLE = 0x20000000;
+
+ IntPtr handle;
+ int ret = Interop.Kernel32.LCMapStringEx(_sortName, LCMAP_SORTHANDLE, null, 0, &handle, IntPtr.Size, null, null, IntPtr.Zero);
+ _sortHandle = ret > 0 ? handle : IntPtr.Zero;
+ }
+ }
+
+ private static unsafe int FindStringOrdinal(
+ uint dwFindStringOrdinalFlags,
+ string stringSource,
+ int offset,
+ int cchSource,
+ string value,
+ int cchValue,
+ bool bIgnoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(stringSource != null);
+ Debug.Assert(value != null);
+
+ fixed (char* pSource = stringSource)
+ fixed (char* pValue = value)
+ {
+ int ret = Interop.Kernel32.FindStringOrdinal(
+ dwFindStringOrdinalFlags,
+ pSource + offset,
+ cchSource,
+ pValue,
+ cchValue,
+ bIgnoreCase ? 1 : 0);
+ return ret < 0 ? ret : ret + offset;
+ }
+ }
+
+ private static unsafe int FindStringOrdinal(
+ uint dwFindStringOrdinalFlags,
+ ReadOnlySpan<char> source,
+ ReadOnlySpan<char> value,
+ bool bIgnoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!value.IsEmpty);
+
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ int ret = Interop.Kernel32.FindStringOrdinal(
+ dwFindStringOrdinalFlags,
+ pSource,
+ source.Length,
+ pValue,
+ value.Length,
+ bIgnoreCase ? 1 : 0);
+ return ret;
+ }
+ }
+
+ internal static int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source != null);
+ Debug.Assert(value != null);
+
+ return FindStringOrdinal(FIND_FROMSTART, source, startIndex, count, value, value.Length, ignoreCase);
+ }
+
+ internal static int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ return FindStringOrdinal(FIND_FROMSTART, source, value, ignoreCase);
+ }
+
+ internal static int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source != null);
+ Debug.Assert(value != null);
+
+ return FindStringOrdinal(FIND_FROMEND, source, startIndex - count + 1, count, value, value.Length, ignoreCase);
+ }
+
+ private unsafe int GetHashCodeOfStringCore(string source, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(source != null);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ if (source.Length == 0)
+ {
+ return 0;
+ }
+
+ uint flags = LCMAP_SORTKEY | (uint)GetNativeCompareFlags(options);
+
+ fixed (char* pSource = source)
+ {
+ int sortKeyLength = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
+ flags,
+ pSource, source.Length,
+ null, 0,
+ null, null, _sortHandle);
+ if (sortKeyLength == 0)
+ {
+ throw new ArgumentException(SR.Arg_ExternalException);
+ }
+
+ byte[] borrowedArr = null;
+ Span<byte> span = sortKeyLength <= 512 ?
+ stackalloc byte[512] :
+ (borrowedArr = ArrayPool<byte>.Shared.Rent(sortKeyLength));
+
+ fixed (byte* pSortKey = &MemoryMarshal.GetReference(span))
+ {
+ if (Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
+ flags,
+ pSource, source.Length,
+ pSortKey, sortKeyLength,
+ null, null, _sortHandle) != sortKeyLength)
+ {
+ throw new ArgumentException(SR.Arg_ExternalException);
+ }
+ }
+
+ int hash = Marvin.ComputeHash32(span.Slice(0, sortKeyLength), Marvin.DefaultSeed);
+
+ // Return the borrowed array if necessary.
+ if (borrowedArr != null)
+ {
+ ArrayPool<byte>.Shared.Return(borrowedArr);
+ }
+
+ return hash;
+ }
+ }
+
+ private static unsafe int CompareStringOrdinalIgnoreCase(char* string1, int count1, char* string2, int count2)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(string1 != null);
+ Debug.Assert(string2 != null);
+
+ // Use the OS to compare and then convert the result to expected value by subtracting 2
+ return Interop.Kernel32.CompareStringOrdinal(string1, count1, string2, count2, true) - 2;
+ }
+
+ // TODO https://github.com/dotnet/coreclr/issues/13827:
+ // This method shouldn't be necessary, as we should be able to just use the overload
+ // that takes two spans. But due to this issue, that's adding significant overhead.
+ private unsafe int CompareString(ReadOnlySpan<char> string1, string string2, CompareOptions options)
+ {
+ Debug.Assert(string2 != null);
+ Debug.Assert(!_invariantMode);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ string localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
+
+ fixed (char* pLocaleName = localeName)
+ fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
+ fixed (char* pString2 = &string2.GetRawStringData())
+ {
+ Debug.Assert(pString1 != null);
+ int result = Interop.Kernel32.CompareStringEx(
+ pLocaleName,
+ (uint)GetNativeCompareFlags(options),
+ pString1,
+ string1.Length,
+ pString2,
+ string2.Length,
+ null,
+ null,
+ _sortHandle);
+
+ if (result == 0)
+ {
+ throw new ArgumentException(SR.Arg_ExternalException);
+ }
+
+ // Map CompareStringEx return value to -1, 0, 1.
+ return result - 2;
+ }
+ }
+
+ private unsafe int CompareString(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ string localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
+
+ fixed (char* pLocaleName = localeName)
+ fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
+ fixed (char* pString2 = &MemoryMarshal.GetReference(string2))
+ {
+ Debug.Assert(pString1 != null);
+ Debug.Assert(pString2 != null);
+ int result = Interop.Kernel32.CompareStringEx(
+ pLocaleName,
+ (uint)GetNativeCompareFlags(options),
+ pString1,
+ string1.Length,
+ pString2,
+ string2.Length,
+ null,
+ null,
+ _sortHandle);
+
+ if (result == 0)
+ {
+ throw new ArgumentException(SR.Arg_ExternalException);
+ }
+
+ // Map CompareStringEx return value to -1, 0, 1.
+ return result - 2;
+ }
+ }
+
+ private unsafe int FindString(
+ uint dwFindNLSStringFlags,
+ ReadOnlySpan<char> lpStringSource,
+ ReadOnlySpan<char> lpStringValue,
+ int* pcchFound)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(!lpStringSource.IsEmpty);
+ Debug.Assert(!lpStringValue.IsEmpty);
+
+ string localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
+
+ fixed (char* pLocaleName = localeName)
+ fixed (char* pSource = &MemoryMarshal.GetReference(lpStringSource))
+ fixed (char* pValue = &MemoryMarshal.GetReference(lpStringValue))
+ {
+ return Interop.Kernel32.FindNLSStringEx(
+ pLocaleName,
+ dwFindNLSStringFlags,
+ pSource,
+ lpStringSource.Length,
+ pValue,
+ lpStringValue.Length,
+ pcchFound,
+ null,
+ null,
+ _sortHandle);
+ }
+ }
+
+ private unsafe int FindString(
+ uint dwFindNLSStringFlags,
+ string lpStringSource,
+ int startSource,
+ int cchSource,
+ string lpStringValue,
+ int startValue,
+ int cchValue,
+ int* pcchFound)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(lpStringSource != null);
+ Debug.Assert(lpStringValue != null);
+
+ string localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
+
+ fixed (char* pLocaleName = localeName)
+ fixed (char* pSource = lpStringSource)
+ fixed (char* pValue = lpStringValue)
+ {
+ char* pS = pSource + startSource;
+ char* pV = pValue + startValue;
+
+ return Interop.Kernel32.FindNLSStringEx(
+ pLocaleName,
+ dwFindNLSStringFlags,
+ pS,
+ cchSource,
+ pV,
+ cchValue,
+ pcchFound,
+ null,
+ null,
+ _sortHandle);
+ }
+ }
+
+ internal unsafe int IndexOfCore(String source, String target, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(source != null);
+ Debug.Assert(target != null);
+ Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
+
+ if (target.Length == 0)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = 0;
+ return startIndex;
+ }
+
+ if (source.Length == 0)
+ {
+ return -1;
+ }
+
+ if ((options & CompareOptions.Ordinal) != 0)
+ {
+ int retValue = FastIndexOfString(source, target, startIndex, count, target.Length, findLastIndex: false);
+ if (retValue >= 0)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ }
+ return retValue;
+ }
+ else
+ {
+ int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, startIndex, count,
+ target, 0, target.Length, matchLengthPtr);
+ if (retValue >= 0)
+ {
+ return retValue + startIndex;
+ }
+ }
+
+ return -1;
+ }
+
+ internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(target.Length != 0);
+ Debug.Assert((options == CompareOptions.None || options == CompareOptions.IgnoreCase));
+
+ int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, target, matchLengthPtr);
+ return retValue;
+ }
+
+ private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!string.IsNullOrEmpty(source));
+ Debug.Assert(target != null);
+ Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
+
+ if (target.Length == 0)
+ return startIndex;
+
+ if ((options & CompareOptions.Ordinal) != 0)
+ {
+ return FastIndexOfString(source, target, startIndex, count, target.Length, findLastIndex: true);
+ }
+ else
+ {
+ int retValue = FindString(FIND_FROMEND | (uint)GetNativeCompareFlags(options), source, startIndex - count + 1,
+ count, target, 0, target.Length, null);
+
+ if (retValue >= 0)
+ {
+ return retValue + startIndex - (count - 1);
+ }
+ }
+
+ return -1;
+ }
+
+ private unsafe bool StartsWith(string source, string prefix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!string.IsNullOrEmpty(source));
+ Debug.Assert(!string.IsNullOrEmpty(prefix));
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ return FindString(FIND_STARTSWITH | (uint)GetNativeCompareFlags(options), source, 0, source.Length,
+ prefix, 0, prefix.Length, null) >= 0;
+ }
+
+ private unsafe bool StartsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!prefix.IsEmpty);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ return FindString(FIND_STARTSWITH | (uint)GetNativeCompareFlags(options), source, prefix, null) >= 0;
+ }
+
+ private unsafe bool EndsWith(string source, string suffix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!string.IsNullOrEmpty(source));
+ Debug.Assert(!string.IsNullOrEmpty(suffix));
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ return FindString(FIND_ENDSWITH | (uint)GetNativeCompareFlags(options), source, 0, source.Length,
+ suffix, 0, suffix.Length, null) >= 0;
+ }
+
+ private unsafe bool EndsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!suffix.IsEmpty);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ return FindString(FIND_ENDSWITH | (uint)GetNativeCompareFlags(options), source, suffix, null) >= 0;
+ }
+
+ // PAL ends here
+ [NonSerialized]
+ private IntPtr _sortHandle;
+
+ private const uint LCMAP_SORTKEY = 0x00000400;
+ private const uint LCMAP_HASH = 0x00040000;
+
+ private const int FIND_STARTSWITH = 0x00100000;
+ private const int FIND_ENDSWITH = 0x00200000;
+ private const int FIND_FROMSTART = 0x00400000;
+ private const int FIND_FROMEND = 0x00800000;
+
+ // TODO: Instead of this method could we just have upstack code call IndexOfOrdinal with ignoreCase = false?
+ private static unsafe int FastIndexOfString(string source, string target, int startIndex, int sourceCount, int targetCount, bool findLastIndex)
+ {
+ int retValue = -1;
+
+ int sourceStartIndex = findLastIndex ? startIndex - sourceCount + 1 : startIndex;
+
+ fixed (char* pSource = source, spTarget = target)
+ {
+ char* spSubSource = pSource + sourceStartIndex;
+
+ if (findLastIndex)
+ {
+ int startPattern = (sourceCount - 1) - targetCount + 1;
+ if (startPattern < 0)
+ return -1;
+
+ char patternChar0 = spTarget[0];
+ for (int ctrSrc = startPattern; ctrSrc >= 0; ctrSrc--)
+ {
+ if (spSubSource[ctrSrc] != patternChar0)
+ continue;
+
+ int ctrPat;
+ for (ctrPat = 1; ctrPat < targetCount; ctrPat++)
+ {
+ if (spSubSource[ctrSrc + ctrPat] != spTarget[ctrPat])
+ break;
+ }
+ if (ctrPat == targetCount)
+ {
+ retValue = ctrSrc;
+ break;
+ }
+ }
+
+ if (retValue >= 0)
+ {
+ retValue += startIndex - sourceCount + 1;
+ }
+ }
+ else
+ {
+ int endPattern = (sourceCount - 1) - targetCount + 1;
+ if (endPattern < 0)
+ return -1;
+
+ char patternChar0 = spTarget[0];
+ for (int ctrSrc = 0; ctrSrc <= endPattern; ctrSrc++)
+ {
+ if (spSubSource[ctrSrc] != patternChar0)
+ continue;
+ int ctrPat;
+ for (ctrPat = 1; ctrPat < targetCount; ctrPat++)
+ {
+ if (spSubSource[ctrSrc + ctrPat] != spTarget[ctrPat])
+ break;
+ }
+ if (ctrPat == targetCount)
+ {
+ retValue = ctrSrc;
+ break;
+ }
+ }
+
+ if (retValue >= 0)
+ {
+ retValue += startIndex;
+ }
+ }
+ }
+
+ return retValue;
+ }
+
+ private unsafe SortKey CreateSortKey(String source, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+
+ if (source == null) { throw new ArgumentNullException(nameof(source)); }
+
+ if ((options & ValidSortkeyCtorMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ byte [] keyData = null;
+ if (source.Length == 0)
+ {
+ keyData = Array.Empty<byte>();
+ }
+ else
+ {
+ uint flags = LCMAP_SORTKEY | (uint)GetNativeCompareFlags(options);
+
+ fixed (char *pSource = source)
+ {
+ int sortKeyLength = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
+ flags,
+ pSource, source.Length,
+ null, 0,
+ null, null, _sortHandle);
+ if (sortKeyLength == 0)
+ {
+ throw new ArgumentException(SR.Arg_ExternalException);
+ }
+
+ keyData = new byte[sortKeyLength];
+
+ fixed (byte* pBytes = keyData)
+ {
+ if (Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
+ flags,
+ pSource, source.Length,
+ pBytes, keyData.Length,
+ null, null, _sortHandle) != sortKeyLength)
+ {
+ throw new ArgumentException(SR.Arg_ExternalException);
+ }
+ }
+ }
+ }
+
+ return new SortKey(Name, source, options, keyData);
+ }
+
+ private static unsafe bool IsSortable(char* text, int length)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(text != null);
+
+ return Interop.Kernel32.IsNLSDefinedString(Interop.Kernel32.COMPARE_STRING, 0, IntPtr.Zero, text, length);
+ }
+
+ private const int COMPARE_OPTIONS_ORDINAL = 0x40000000; // Ordinal
+ private const int NORM_IGNORECASE = 0x00000001; // Ignores case. (use LINGUISTIC_IGNORECASE instead)
+ private const int NORM_IGNOREKANATYPE = 0x00010000; // Does not differentiate between Hiragana and Katakana characters. Corresponding Hiragana and Katakana will compare as equal.
+ private const int NORM_IGNORENONSPACE = 0x00000002; // Ignores nonspacing. This flag also removes Japanese accent characters. (use LINGUISTIC_IGNOREDIACRITIC instead)
+ private const int NORM_IGNORESYMBOLS = 0x00000004; // Ignores symbols.
+ private const int NORM_IGNOREWIDTH = 0x00020000; // Does not differentiate between a single-byte character and the same character as a double-byte character.
+ private const int NORM_LINGUISTIC_CASING = 0x08000000; // use linguistic rules for casing
+ private const int SORT_STRINGSORT = 0x00001000; // Treats punctuation the same as symbols.
+
+ private static int GetNativeCompareFlags(CompareOptions options)
+ {
+ // Use "linguistic casing" by default (load the culture's casing exception tables)
+ int nativeCompareFlags = NORM_LINGUISTIC_CASING;
+
+ if ((options & CompareOptions.IgnoreCase) != 0) { nativeCompareFlags |= NORM_IGNORECASE; }
+ if ((options & CompareOptions.IgnoreKanaType) != 0) { nativeCompareFlags |= NORM_IGNOREKANATYPE; }
+ if ((options & CompareOptions.IgnoreNonSpace) != 0) { nativeCompareFlags |= NORM_IGNORENONSPACE; }
+ if ((options & CompareOptions.IgnoreSymbols) != 0) { nativeCompareFlags |= NORM_IGNORESYMBOLS; }
+ if ((options & CompareOptions.IgnoreWidth) != 0) { nativeCompareFlags |= NORM_IGNOREWIDTH; }
+ if ((options & CompareOptions.StringSort) != 0) { nativeCompareFlags |= SORT_STRINGSORT; }
+
+ // TODO: Can we try for GetNativeCompareFlags to never
+ // take Ordinal or OrdinalIgnoreCase. This value is not part of Win32, we just handle it special
+ // in some places.
+ // Suffix & Prefix shouldn't use this, make sure to turn off the NORM_LINGUISTIC_CASING flag
+ if (options == CompareOptions.Ordinal) { nativeCompareFlags = COMPARE_OPTIONS_ORDINAL; }
+
+ Debug.Assert(((options & ~(CompareOptions.IgnoreCase |
+ CompareOptions.IgnoreKanaType |
+ CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreSymbols |
+ CompareOptions.IgnoreWidth |
+ CompareOptions.StringSort)) == 0) ||
+ (options == CompareOptions.Ordinal), "[CompareInfo.GetNativeCompareFlags]Expected all flags to be handled");
+
+ return nativeCompareFlags;
+ }
+
+ private unsafe SortVersion GetSortVersion()
+ {
+ Debug.Assert(!_invariantMode);
+
+ Interop.Kernel32.NlsVersionInfoEx nlsVersion = new Interop.Kernel32.NlsVersionInfoEx();
+ nlsVersion.dwNLSVersionInfoSize = sizeof(Interop.Kernel32.NlsVersionInfoEx);
+ Interop.Kernel32.GetNLSVersionEx(Interop.Kernel32.COMPARE_STRING, _sortName, &nlsVersion);
+ return new SortVersion(
+ nlsVersion.dwNLSVersion,
+ nlsVersion.dwEffectiveId == 0 ? LCID : nlsVersion.dwEffectiveId,
+ nlsVersion.guidCustomVersion);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs
new file mode 100644
index 0000000000..079b8afbc0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs
@@ -0,0 +1,1368 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+//
+// Purpose: This class implements a set of methods for comparing
+// strings.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Reflection;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Buffers;
+using System.Text;
+
+namespace System.Globalization
+{
+ [Flags]
+ public enum CompareOptions
+ {
+ None = 0x00000000,
+ IgnoreCase = 0x00000001,
+ IgnoreNonSpace = 0x00000002,
+ IgnoreSymbols = 0x00000004,
+ IgnoreKanaType = 0x00000008, // ignore kanatype
+ IgnoreWidth = 0x00000010, // ignore width
+ OrdinalIgnoreCase = 0x10000000, // This flag can not be used with other flags.
+ StringSort = 0x20000000, // use string sort method
+ Ordinal = 0x40000000, // This flag can not be used with other flags.
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public partial class CompareInfo : IDeserializationCallback
+ {
+ // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags.
+ private const CompareOptions ValidIndexMaskOffFlags =
+ ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
+
+ // Mask used to check if Compare() has the right flags.
+ private const CompareOptions ValidCompareMaskOffFlags =
+ ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
+
+ // Mask used to check if GetHashCodeOfString() has the right flags.
+ private const CompareOptions ValidHashCodeOfStringMaskOffFlags =
+ ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
+
+ // Mask used to check if we have the right flags.
+ private const CompareOptions ValidSortkeyCtorMaskOffFlags =
+ ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
+
+ // Cache the invariant CompareInfo
+ internal static readonly CompareInfo Invariant = CultureInfo.InvariantCulture.CompareInfo;
+
+ //
+ // CompareInfos have an interesting identity. They are attached to the locale that created them,
+ // ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US.
+ // The interesting part is that since haw-US doesn't have its own sort, it has to point at another
+ // locale, which is what SCOMPAREINFO does.
+ [OptionalField(VersionAdded = 2)]
+ private string m_name; // The name used to construct this CompareInfo. Do not rename (binary serialization)
+
+ [NonSerialized]
+ private string _sortName; // The name that defines our behavior
+
+ [OptionalField(VersionAdded = 3)]
+ private SortVersion m_SortVersion; // Do not rename (binary serialization)
+
+ // _invariantMode is defined for the perf reason as accessing the instance field is faster than access the static property GlobalizationMode.Invariant
+ [NonSerialized]
+ private readonly bool _invariantMode = GlobalizationMode.Invariant;
+
+ private int culture; // Do not rename (binary serialization). The fields sole purpose is to support Desktop serialization.
+
+ internal CompareInfo(CultureInfo culture)
+ {
+ m_name = culture._name;
+ InitSort(culture);
+ }
+
+ /*=================================GetCompareInfo==========================
+ **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
+ ** Warning: The assembly versioning mechanism is dead!
+ **Returns: The CompareInfo for the specified culture.
+ **Arguments:
+ ** culture the ID of the culture
+ ** assembly the assembly which contains the sorting table.
+ **Exceptions:
+ ** ArgumentNullException when the assembly is null
+ ** ArgumentException if culture is invalid.
+ ============================================================================*/
+ // Assembly constructor should be deprecated, we don't act on the assembly information any more
+ public static CompareInfo GetCompareInfo(int culture, Assembly assembly)
+ {
+ // Parameter checking.
+ if (assembly == null)
+ {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+ if (assembly != typeof(Object).Module.Assembly)
+ {
+ throw new ArgumentException(SR.Argument_OnlyMscorlib);
+ }
+
+ return GetCompareInfo(culture);
+ }
+
+ /*=================================GetCompareInfo==========================
+ **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
+ ** The purpose of this method is to provide version for CompareInfo tables.
+ **Returns: The CompareInfo for the specified culture.
+ **Arguments:
+ ** name the name of the culture
+ ** assembly the assembly which contains the sorting table.
+ **Exceptions:
+ ** ArgumentNullException when the assembly is null
+ ** ArgumentException if name is invalid.
+ ============================================================================*/
+ // Assembly constructor should be deprecated, we don't act on the assembly information any more
+ public static CompareInfo GetCompareInfo(string name, Assembly assembly)
+ {
+ if (name == null || assembly == null)
+ {
+ throw new ArgumentNullException(name == null ? nameof(name) : nameof(assembly));
+ }
+
+ if (assembly != typeof(Object).Module.Assembly)
+ {
+ throw new ArgumentException(SR.Argument_OnlyMscorlib);
+ }
+
+ return GetCompareInfo(name);
+ }
+
+ /*=================================GetCompareInfo==========================
+ **Action: Get the CompareInfo for the specified culture.
+ ** This method is provided for ease of integration with NLS-based software.
+ **Returns: The CompareInfo for the specified culture.
+ **Arguments:
+ ** culture the ID of the culture.
+ **Exceptions:
+ ** ArgumentException if culture is invalid.
+ ============================================================================*/
+ // People really shouldn't be calling LCID versions, no custom support
+ public static CompareInfo GetCompareInfo(int culture)
+ {
+ if (CultureData.IsCustomCultureId(culture))
+ {
+ // Customized culture cannot be created by the LCID.
+ throw new ArgumentException(SR.Argument_CustomCultureCannotBePassedByNumber, nameof(culture));
+ }
+
+ return CultureInfo.GetCultureInfo(culture).CompareInfo;
+ }
+
+ /*=================================GetCompareInfo==========================
+ **Action: Get the CompareInfo for the specified culture.
+ **Returns: The CompareInfo for the specified culture.
+ **Arguments:
+ ** name the name of the culture.
+ **Exceptions:
+ ** ArgumentException if name is invalid.
+ ============================================================================*/
+
+ public static CompareInfo GetCompareInfo(string name)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ return CultureInfo.GetCultureInfo(name).CompareInfo;
+ }
+
+ public static unsafe bool IsSortable(char ch)
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ return true;
+ }
+ char *pChar = &ch;
+ return IsSortable(pChar, 1);
+ }
+
+ public static unsafe bool IsSortable(string text)
+ {
+ if (text == null)
+ {
+ // A null param is invalid here.
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (text.Length == 0)
+ {
+ // A zero length string is not invalid, but it is also not sortable.
+ return (false);
+ }
+
+ if (GlobalizationMode.Invariant)
+ {
+ return true;
+ }
+
+ fixed (char *pChar = text)
+ {
+ return IsSortable(pChar, text.Length);
+ }
+ }
+
+ [OnDeserializing]
+ private void OnDeserializing(StreamingContext ctx)
+ {
+ m_name = null;
+ }
+
+ void IDeserializationCallback.OnDeserialization(object sender)
+ {
+ OnDeserialized();
+ }
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ OnDeserialized();
+ }
+
+ private void OnDeserialized()
+ {
+ // If we didn't have a name, use the LCID
+ if (m_name == null)
+ {
+ // From whidbey, didn't have a name
+ CultureInfo ci = CultureInfo.GetCultureInfo(this.culture);
+ m_name = ci._name;
+ }
+ else
+ {
+ InitSort(CultureInfo.GetCultureInfo(m_name));
+ }
+ }
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+ // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that compat any more.
+ culture = CultureInfo.GetCultureInfo(this.Name).LCID; // This is the lcid of the constructing culture (still have to dereference to get target sort)
+ Debug.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already");
+ }
+
+ ///////////////////////////----- Name -----/////////////////////////////////
+ //
+ // Returns the name of the culture (well actually, of the sort).
+ // Very important for providing a non-LCID way of identifying
+ // what the sort is.
+ //
+ // Note that this name isn't dereferenced in case the CompareInfo is a different locale
+ // which is consistent with the behaviors of earlier versions. (so if you ask for a sort
+ // and the locale's changed behavior, then you'll get changed behavior, which is like
+ // what happens for a version update)
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ public virtual string Name
+ {
+ get
+ {
+ Debug.Assert(m_name != null, "CompareInfo.Name Expected _name to be set");
+ if (m_name == "zh-CHT" || m_name == "zh-CHS")
+ {
+ return m_name;
+ }
+
+ return _sortName;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Compare
+ //
+ // Compares the two strings with the given options. Returns 0 if the
+ // two strings are equal, a number less than 0 if string1 is less
+ // than string2, and a number greater than 0 if string1 is greater
+ // than string2.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ public virtual int Compare(string string1, string string2)
+ {
+ return (Compare(string1, string2, CompareOptions.None));
+ }
+
+ public virtual int Compare(string string1, string string2, CompareOptions options)
+ {
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return String.Compare(string1, string2, StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Verify the options before we do any real comparison.
+ if ((options & CompareOptions.Ordinal) != 0)
+ {
+ if (options != CompareOptions.Ordinal)
+ {
+ throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
+ }
+
+ return String.CompareOrdinal(string1, string2);
+ }
+
+ if ((options & ValidCompareMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ //Our paradigm is that null sorts less than any other string and
+ //that two nulls sort as equal.
+ if (string1 == null)
+ {
+ if (string2 == null)
+ {
+ return (0); // Equal
+ }
+ return (-1); // null < non-null
+ }
+ if (string2 == null)
+ {
+ return (1); // non-null > null
+ }
+
+ if (_invariantMode)
+ {
+ if ((options & CompareOptions.IgnoreCase) != 0)
+ return CompareOrdinalIgnoreCase(string1, string2);
+
+ return String.CompareOrdinal(string1, string2);
+ }
+
+ return CompareString(string1.AsSpan(), string2.AsSpan(), options);
+ }
+
+ // TODO https://github.com/dotnet/coreclr/issues/13827:
+ // This method shouldn't be necessary, as we should be able to just use the overload
+ // that takes two spans. But due to this issue, that's adding significant overhead.
+ internal int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
+ {
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return CompareOrdinalIgnoreCase(string1, string2.AsSpan());
+ }
+
+ // Verify the options before we do any real comparison.
+ if ((options & CompareOptions.Ordinal) != 0)
+ {
+ if (options != CompareOptions.Ordinal)
+ {
+ throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
+ }
+
+ return string.CompareOrdinal(string1, string2.AsSpan());
+ }
+
+ if ((options & ValidCompareMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ // null sorts less than any other string.
+ if (string2 == null)
+ {
+ return 1;
+ }
+
+ if (_invariantMode)
+ {
+ return (options & CompareOptions.IgnoreCase) != 0 ?
+ CompareOrdinalIgnoreCase(string1, string2.AsSpan()) :
+ string.CompareOrdinal(string1, string2.AsSpan());
+ }
+
+ return CompareString(string1, string2, options);
+ }
+
+ internal int CompareOptionNone(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
+ {
+ // Check for empty span or span from a null string
+ if (string1.Length == 0 || string2.Length == 0)
+ return string1.Length - string2.Length;
+
+ return _invariantMode ?
+ string.CompareOrdinal(string1, string2) :
+ CompareString(string1, string2, CompareOptions.None);
+ }
+
+ internal int CompareOptionIgnoreCase(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
+ {
+ // Check for empty span or span from a null string
+ if (string1.Length == 0 || string2.Length == 0)
+ return string1.Length - string2.Length;
+
+ return _invariantMode ?
+ CompareOrdinalIgnoreCase(string1, string2) :
+ CompareString(string1, string2, CompareOptions.IgnoreCase);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Compare
+ //
+ // Compares the specified regions of the two strings with the given
+ // options.
+ // Returns 0 if the two strings are equal, a number less than 0 if
+ // string1 is less than string2, and a number greater than 0 if
+ // string1 is greater than string2.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
+ {
+ return Compare(string1, offset1, length1, string2, offset2, length2, 0);
+ }
+
+
+ public virtual int Compare(string string1, int offset1, string string2, int offset2, CompareOptions options)
+ {
+ return Compare(string1, offset1, string1 == null ? 0 : string1.Length - offset1,
+ string2, offset2, string2 == null ? 0 : string2.Length - offset2, options);
+ }
+
+
+ public virtual int Compare(string string1, int offset1, string string2, int offset2)
+ {
+ return Compare(string1, offset1, string2, offset2, 0);
+ }
+
+
+ public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options)
+ {
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ int result = String.Compare(string1, offset1, string2, offset2, length1 < length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase);
+ if ((length1 != length2) && result == 0)
+ return (length1 > length2 ? 1 : -1);
+ return (result);
+ }
+
+ // Verify inputs
+ if (length1 < 0 || length2 < 0)
+ {
+ throw new ArgumentOutOfRangeException((length1 < 0) ? nameof(length1) : nameof(length2), SR.ArgumentOutOfRange_NeedPosNum);
+ }
+ if (offset1 < 0 || offset2 < 0)
+ {
+ throw new ArgumentOutOfRangeException((offset1 < 0) ? nameof(offset1) : nameof(offset2), SR.ArgumentOutOfRange_NeedPosNum);
+ }
+ if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(string1), SR.ArgumentOutOfRange_OffsetLength);
+ }
+ if (offset2 > (string2 == null ? 0 : string2.Length) - length2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(string2), SR.ArgumentOutOfRange_OffsetLength);
+ }
+ if ((options & CompareOptions.Ordinal) != 0)
+ {
+ if (options != CompareOptions.Ordinal)
+ {
+ throw new ArgumentException(SR.Argument_CompareOptionOrdinal,
+ nameof(options));
+ }
+ }
+ else if ((options & ValidCompareMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ //
+ // Check for the null case.
+ //
+ if (string1 == null)
+ {
+ if (string2 == null)
+ {
+ return (0);
+ }
+ return (-1);
+ }
+ if (string2 == null)
+ {
+ return (1);
+ }
+
+ ReadOnlySpan<char> span1 = string1.AsSpan(offset1, length1);
+ ReadOnlySpan<char> span2 = string2.AsSpan(offset2, length2);
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return string.CompareOrdinal(span1, span2);
+ }
+
+ if (_invariantMode)
+ {
+ if ((options & CompareOptions.IgnoreCase) != 0)
+ return CompareOrdinalIgnoreCase(span1, span2);
+
+ return string.CompareOrdinal(span1, span2);
+ }
+
+ return CompareString(span1, span2, options);
+ }
+
+ //
+ // CompareOrdinalIgnoreCase compare two string ordinally with ignoring the case.
+ // it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by
+ // calling the OS.
+ //
+ internal static int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
+ {
+ Debug.Assert(indexA + lengthA <= strA.Length);
+ Debug.Assert(indexB + lengthB <= strB.Length);
+ return CompareOrdinalIgnoreCase(strA.AsSpan(indexA, lengthA), strB.AsSpan(indexB, lengthB));
+ }
+
+ internal static unsafe int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
+ {
+ int length = Math.Min(strA.Length, strB.Length);
+ int range = length;
+
+ fixed (char* ap = &MemoryMarshal.GetReference(strA))
+ fixed (char* bp = &MemoryMarshal.GetReference(strB))
+ {
+ char* a = ap;
+ char* b = bp;
+
+ // in InvariantMode we support all range and not only the ascii characters.
+ char maxChar = (char) (GlobalizationMode.Invariant ? 0xFFFF : 0x7F);
+
+ while (length != 0 && (*a <= maxChar) && (*b <= maxChar))
+ {
+ int charA = *a;
+ int charB = *b;
+
+ if (charA == charB)
+ {
+ a++; b++;
+ length--;
+ continue;
+ }
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(charA - 'a') <= 'z' - 'a') charA -= 0x20;
+ if ((uint)(charB - 'a') <= 'z' - 'a') charB -= 0x20;
+
+ // Return the (case-insensitive) difference between them.
+ if (charA != charB)
+ return charA - charB;
+
+ // Next char
+ a++; b++;
+ length--;
+ }
+
+ if (length == 0)
+ return strA.Length - strB.Length;
+
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ range -= length;
+
+ return CompareStringOrdinalIgnoreCase(a, strA.Length - range, b, strB.Length - range);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsPrefix
+ //
+ // Determines whether prefix is a prefix of string. If prefix equals
+ // String.Empty, true is returned.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual bool IsPrefix(string source, string prefix, CompareOptions options)
+ {
+ if (source == null || prefix == null)
+ {
+ throw new ArgumentNullException((source == null ? nameof(source) : nameof(prefix)),
+ SR.ArgumentNull_String);
+ }
+
+ if (prefix.Length == 0)
+ {
+ return (true);
+ }
+
+ if (source.Length == 0)
+ {
+ return false;
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return source.StartsWith(prefix, StringComparison.Ordinal);
+ }
+
+ if ((options & ValidIndexMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ if (_invariantMode)
+ {
+ return source.StartsWith(prefix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+ }
+
+ return StartsWith(source, prefix, options);
+ }
+
+ internal bool IsPrefix(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
+ {
+ Debug.Assert(prefix.Length != 0);
+ Debug.Assert(source.Length != 0);
+ Debug.Assert((options & ValidIndexMaskOffFlags) == 0);
+ Debug.Assert(!_invariantMode);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ return StartsWith(source, prefix, options);
+ }
+
+ public virtual bool IsPrefix(string source, string prefix)
+ {
+ return (IsPrefix(source, prefix, 0));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsSuffix
+ //
+ // Determines whether suffix is a suffix of string. If suffix equals
+ // String.Empty, true is returned.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual bool IsSuffix(string source, string suffix, CompareOptions options)
+ {
+ if (source == null || suffix == null)
+ {
+ throw new ArgumentNullException((source == null ? nameof(source) : nameof(suffix)),
+ SR.ArgumentNull_String);
+ }
+
+ if (suffix.Length == 0)
+ {
+ return (true);
+ }
+
+ if (source.Length == 0)
+ {
+ return false;
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return source.EndsWith(suffix, StringComparison.Ordinal);
+ }
+
+ if ((options & ValidIndexMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ if (_invariantMode)
+ {
+ return source.EndsWith(suffix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+ }
+
+ return EndsWith(source, suffix, options);
+ }
+
+ internal bool IsSuffix(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
+ {
+ Debug.Assert(suffix.Length != 0);
+ Debug.Assert(source.Length != 0);
+ Debug.Assert((options & ValidIndexMaskOffFlags) == 0);
+ Debug.Assert(!_invariantMode);
+ Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
+
+ return EndsWith(source, suffix, options);
+ }
+
+
+ public virtual bool IsSuffix(string source, string suffix)
+ {
+ return (IsSuffix(source, suffix, 0));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IndexOf
+ //
+ // Returns the first index where value is found in string. The
+ // search starts from startIndex and ends at endIndex. Returns -1 if
+ // the specified value is not found. If value equals String.Empty,
+ // startIndex is returned. Throws IndexOutOfRange if startIndex or
+ // endIndex is less than zero or greater than the length of string.
+ // Throws ArgumentException if value is null.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public virtual int IndexOf(string source, char value)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ return IndexOf(source, value, 0, source.Length, CompareOptions.None);
+ }
+
+
+ public virtual int IndexOf(string source, string value)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ return IndexOf(source, value, 0, source.Length, CompareOptions.None);
+ }
+
+
+ public virtual int IndexOf(string source, char value, CompareOptions options)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ return IndexOf(source, value, 0, source.Length, options);
+ }
+
+
+ public virtual int IndexOf(string source, string value, CompareOptions options)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ return IndexOf(source, value, 0, source.Length, options);
+ }
+
+ public virtual int IndexOf(string source, char value, int startIndex)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
+ }
+
+ public virtual int IndexOf(string source, string value, int startIndex)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
+ }
+
+ public virtual int IndexOf(string source, char value, int startIndex, CompareOptions options)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ return IndexOf(source, value, startIndex, source.Length - startIndex, options);
+ }
+
+
+ public virtual int IndexOf(string source, string value, int startIndex, CompareOptions options)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ return IndexOf(source, value, startIndex, source.Length - startIndex, options);
+ }
+
+
+ public virtual int IndexOf(string source, char value, int startIndex, int count)
+ {
+ return IndexOf(source, value, startIndex, count, CompareOptions.None);
+ }
+
+
+ public virtual int IndexOf(string source, string value, int startIndex, int count)
+ {
+ return IndexOf(source, value, startIndex, count, CompareOptions.None);
+ }
+
+ public unsafe virtual int IndexOf(string source, char value, int startIndex, int count, CompareOptions options)
+ {
+ // Validate inputs
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ if (startIndex < 0 || startIndex > source.Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ if (count < 0 || startIndex > source.Length - count)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ if (source.Length == 0)
+ {
+ return -1;
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.IndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Validate CompareOptions
+ // Ordinal can't be selected with other flags
+ if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+
+ if (_invariantMode)
+ return IndexOfOrdinal(source, new string(value, 1), startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
+
+ return IndexOfCore(source, new string(value, 1), startIndex, count, options, null);
+ }
+
+ public unsafe virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
+ {
+ // Validate inputs
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (startIndex > source.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here.
+ // We return 0 if both source and value are empty strings for Everett compatibility too.
+ if (source.Length == 0)
+ {
+ if (value.Length == 0)
+ {
+ return 0;
+ }
+ return -1;
+ }
+
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (count < 0 || startIndex > source.Length - count)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
+ }
+
+ // Validate CompareOptions
+ // Ordinal can't be selected with other flags
+ if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+
+ if (_invariantMode)
+ return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
+
+ return IndexOfCore(source, value, startIndex, count, options, null);
+ }
+
+ internal int IndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!value.IsEmpty);
+ return IndexOfOrdinalCore(source, value, ignoreCase);
+ }
+
+ internal unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!value.IsEmpty);
+ return IndexOfCore(source, value, options, null);
+ }
+
+ // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
+ // and the caller is passing a valid matchLengthPtr pointer.
+ internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(source != null);
+ Debug.Assert(value != null);
+ Debug.Assert(startIndex >= 0);
+ Debug.Assert(matchLengthPtr != null);
+ *matchLengthPtr = 0;
+
+ if (source.Length == 0)
+ {
+ if (value.Length == 0)
+ {
+ return 0;
+ }
+ return -1;
+ }
+
+ if (startIndex >= source.Length)
+ {
+ return -1;
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
+ if (res >= 0)
+ {
+ *matchLengthPtr = value.Length;
+ }
+ return res;
+ }
+
+ if (_invariantMode)
+ {
+ int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
+ if (res >= 0)
+ {
+ *matchLengthPtr = value.Length;
+ }
+ return res;
+ }
+
+ return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr);
+ }
+
+ internal int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
+ {
+ if (_invariantMode)
+ {
+ return InvariantIndexOf(source, value, startIndex, count, ignoreCase);
+ }
+
+ return IndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // LastIndexOf
+ //
+ // Returns the last index where value is found in string. The
+ // search starts from startIndex and ends at endIndex. Returns -1 if
+ // the specified value is not found. If value equals String.Empty,
+ // endIndex is returned. Throws IndexOutOfRange if startIndex or
+ // endIndex is less than zero or greater than the length of string.
+ // Throws ArgumentException if value is null.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public virtual int LastIndexOf(String source, char value)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ // Can't start at negative index, so make sure we check for the length == 0 case.
+ return LastIndexOf(source, value, source.Length - 1, source.Length, CompareOptions.None);
+ }
+
+
+ public virtual int LastIndexOf(string source, string value)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ // Can't start at negative index, so make sure we check for the length == 0 case.
+ return LastIndexOf(source, value, source.Length - 1,
+ source.Length, CompareOptions.None);
+ }
+
+
+ public virtual int LastIndexOf(string source, char value, CompareOptions options)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ // Can't start at negative index, so make sure we check for the length == 0 case.
+ return LastIndexOf(source, value, source.Length - 1,
+ source.Length, options);
+ }
+
+ public virtual int LastIndexOf(string source, string value, CompareOptions options)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ // Can't start at negative index, so make sure we check for the length == 0 case.
+ return LastIndexOf(source, value, source.Length - 1, source.Length, options);
+ }
+
+ public virtual int LastIndexOf(string source, char value, int startIndex)
+ {
+ return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
+ }
+
+
+ public virtual int LastIndexOf(string source, string value, int startIndex)
+ {
+ return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
+ }
+
+ public virtual int LastIndexOf(string source, char value, int startIndex, CompareOptions options)
+ {
+ return LastIndexOf(source, value, startIndex, startIndex + 1, options);
+ }
+
+
+ public virtual int LastIndexOf(string source, string value, int startIndex, CompareOptions options)
+ {
+ return LastIndexOf(source, value, startIndex, startIndex + 1, options);
+ }
+
+
+ public virtual int LastIndexOf(string source, char value, int startIndex, int count)
+ {
+ return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
+ }
+
+
+ public virtual int LastIndexOf(string source, string value, int startIndex, int count)
+ {
+ return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
+ }
+
+
+ public virtual int LastIndexOf(string source, char value, int startIndex, int count, CompareOptions options)
+ {
+ // Verify Arguments
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ // Validate CompareOptions
+ // Ordinal can't be selected with other flags
+ if ((options & ValidIndexMaskOffFlags) != 0 &&
+ (options != CompareOptions.Ordinal) &&
+ (options != CompareOptions.OrdinalIgnoreCase))
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+
+ // Special case for 0 length input strings
+ if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
+ return -1;
+
+ // Make sure we're not out of range
+ if (startIndex < 0 || startIndex > source.Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ // Make sure that we allow startIndex == source.Length
+ if (startIndex == source.Length)
+ {
+ startIndex--;
+ if (count > 0)
+ count--;
+ }
+
+ // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
+ if (count < 0 || startIndex - count + 1 < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
+ }
+
+ if (_invariantMode)
+ return InvariantLastIndexOf(source, new string(value, 1), startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
+
+ return LastIndexOfCore(source, value.ToString(), startIndex, count, options);
+ }
+
+
+ public virtual int LastIndexOf(string source, string value, int startIndex, int count, CompareOptions options)
+ {
+ // Verify Arguments
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ // Validate CompareOptions
+ // Ordinal can't be selected with other flags
+ if ((options & ValidIndexMaskOffFlags) != 0 &&
+ (options != CompareOptions.Ordinal) &&
+ (options != CompareOptions.OrdinalIgnoreCase))
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+
+ // Special case for 0 length input strings
+ if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
+ return (value.Length == 0) ? 0 : -1;
+
+ // Make sure we're not out of range
+ if (startIndex < 0 || startIndex > source.Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ // Make sure that we allow startIndex == source.Length
+ if (startIndex == source.Length)
+ {
+ startIndex--;
+ if (count > 0)
+ count--;
+
+ // If we are looking for nothing, just return 0
+ if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0)
+ return startIndex;
+ }
+
+ // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
+ if (count < 0 || startIndex - count + 1 < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return LastIndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
+ }
+
+ if (_invariantMode)
+ return InvariantLastIndexOf(source, value, startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
+
+ return LastIndexOfCore(source, value, startIndex, count, options);
+ }
+
+ internal int LastIndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
+ {
+ if (_invariantMode)
+ {
+ return InvariantLastIndexOf(source, value, startIndex, count, ignoreCase);
+ }
+
+ return LastIndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetSortKey
+ //
+ // Gets the SortKey for the given string with the given options.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual SortKey GetSortKey(string source, CompareOptions options)
+ {
+ if (_invariantMode)
+ return InvariantCreateSortKey(source, options);
+
+ return CreateSortKey(source, options);
+ }
+
+
+ public virtual SortKey GetSortKey(string source)
+ {
+ if (_invariantMode)
+ return InvariantCreateSortKey(source, CompareOptions.None);
+
+ return CreateSortKey(source, CompareOptions.None);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same CompareInfo as the current
+ // instance.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override bool Equals(Object value)
+ {
+ CompareInfo that = value as CompareInfo;
+
+ if (that != null)
+ {
+ return this.Name == that.Name;
+ }
+
+ return (false);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // CompareInfo. The hash code is guaranteed to be the same for
+ // CompareInfo A and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override int GetHashCode()
+ {
+ return (this.Name.GetHashCode());
+ }
+
+ internal static unsafe int GetIgnoreCaseHash(string source)
+ {
+ Debug.Assert(source != null, "source must not be null");
+
+ // Do not allocate on the stack if string is empty
+ if (source.Length == 0)
+ {
+ return source.GetHashCode();
+ }
+
+ char[] borrowedArr = null;
+ Span<char> span = source.Length <= 255 ?
+ stackalloc char[255] :
+ (borrowedArr = ArrayPool<char>.Shared.Rent(source.Length));
+
+ int charsWritten = source.AsSpan().ToUpperInvariant(span);
+
+ // Slice the array to the size returned by ToUpperInvariant.
+ int hash = Marvin.ComputeHash32(MemoryMarshal.AsBytes(span.Slice(0, charsWritten)), Marvin.DefaultSeed);
+
+ // Return the borrowed array if necessary.
+ if (borrowedArr != null)
+ {
+ ArrayPool<char>.Shared.Return(borrowedArr);
+ }
+
+ return hash;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCodeOfString
+ //
+ // This internal method allows a method that allows the equivalent of creating a Sortkey for a
+ // string from CompareInfo, and generate a hashcode value from it. It is not very convenient
+ // to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
+ //
+ // The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
+ // the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
+ // treat the string the same way, this implementation will treat them differently (the same way that
+ // Sortkey does at the moment).
+ //
+ // This method will never be made public itself, but public consumers of it could be created, e.g.:
+ //
+ // string.GetHashCode(CultureInfo)
+ // string.GetHashCode(CompareInfo)
+ // string.GetHashCode(CultureInfo, CompareOptions)
+ // string.GetHashCode(CompareInfo, CompareOptions)
+ // etc.
+ //
+ // (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ internal int GetHashCodeOfString(string source, CompareOptions options)
+ {
+ //
+ // Parameter validation
+ //
+ if (null == source)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if ((options & ValidHashCodeOfStringMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+
+ if (_invariantMode)
+ {
+ return ((options & CompareOptions.IgnoreCase) != 0) ? GetIgnoreCaseHash(source) : source.GetHashCode();
+ }
+
+ return GetHashCodeOfStringCore(source, options);
+ }
+
+ public virtual int GetHashCode(string source, CompareOptions options)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return source.GetHashCode();
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return GetIgnoreCaseHash(source);
+ }
+
+ //
+ // GetHashCodeOfString does more parameters validation. basically will throw when
+ // having Ordinal, OrdinalIgnoreCase and StringSort
+ //
+
+ return GetHashCodeOfString(source, options);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns a string describing the
+ // CompareInfo.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override string ToString()
+ {
+ return ("CompareInfo - " + this.Name);
+ }
+
+ public SortVersion Version
+ {
+ get
+ {
+ if (m_SortVersion == null)
+ {
+ if (_invariantMode)
+ {
+ m_SortVersion = new SortVersion(0, CultureInfo.LOCALE_INVARIANT, new Guid(0, 0, 0, 0, 0, 0, 0,
+ (byte) (CultureInfo.LOCALE_INVARIANT >> 24),
+ (byte) ((CultureInfo.LOCALE_INVARIANT & 0x00FF0000) >> 16),
+ (byte) ((CultureInfo.LOCALE_INVARIANT & 0x0000FF00) >> 8),
+ (byte) (CultureInfo.LOCALE_INVARIANT & 0xFF)));
+ }
+ else
+ {
+ m_SortVersion = GetSortVersion();
+ }
+ }
+
+ return m_SortVersion;
+ }
+ }
+
+ public int LCID
+ {
+ get
+ {
+ return CultureInfo.GetCultureInfo(Name).LCID;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Unix.cs
new file mode 100644
index 0000000000..3fce527929
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Unix.cs
@@ -0,0 +1,430 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+
+namespace System.Globalization
+{
+ internal partial class CultureData
+ {
+ // ICU constants
+ const int ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY = 100; // max size of keyword or value
+ const int ICU_ULOC_FULLNAME_CAPACITY = 157; // max size of locale name
+ const string ICU_COLLATION_KEYWORD = "@collation=";
+
+
+ /// <summary>
+ /// This method uses the sRealName field (which is initialized by the constructor before this is called) to
+ /// initialize the rest of the state of CultureData based on the underlying OS globalization library.
+ /// </summary>
+ private unsafe bool InitCultureData()
+ {
+ Debug.Assert(_sRealName != null);
+
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ string alternateSortName = string.Empty;
+ string realNameBuffer = _sRealName;
+
+ // Basic validation
+ if (realNameBuffer.Contains('@'))
+ {
+ return false; // don't allow ICU variants to come in directly
+ }
+
+ // Replace _ (alternate sort) with @collation= for ICU
+ int index = realNameBuffer.IndexOf('_');
+ if (index > 0)
+ {
+ if (index >= (realNameBuffer.Length - 1) // must have characters after _
+ || realNameBuffer.Substring(index + 1).Contains('_')) // only one _ allowed
+ {
+ return false; // fail
+ }
+ alternateSortName = realNameBuffer.Substring(index + 1);
+ realNameBuffer = realNameBuffer.Substring(0, index) + ICU_COLLATION_KEYWORD + alternateSortName;
+ }
+
+ // Get the locale name from ICU
+ if (!GetLocaleName(realNameBuffer, out _sWindowsName))
+ {
+ return false; // fail
+ }
+
+ // Replace the ICU collation keyword with an _
+ index = _sWindowsName.IndexOf(ICU_COLLATION_KEYWORD, StringComparison.Ordinal);
+ if (index >= 0)
+ {
+ _sName = _sWindowsName.Substring(0, index) + "_" + alternateSortName;
+ }
+ else
+ {
+ _sName = _sWindowsName;
+ }
+ _sRealName = _sName;
+
+ _iLanguage = this.ILANGUAGE;
+ if (_iLanguage == 0)
+ {
+ _iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED;
+ }
+
+ _bNeutral = (this.SISO3166CTRYNAME.Length == 0);
+
+ _sSpecificCulture = _bNeutral ? LocaleData.GetSpecificCultureName(_sRealName) : _sRealName;
+
+ // Remove the sort from sName unless custom culture
+ if (index>0 && !_bNeutral && !IsCustomCultureId(_iLanguage))
+ {
+ _sName = _sWindowsName.Substring(0, index);
+ }
+ return true;
+ }
+
+ internal static bool GetLocaleName(string localeName, out string windowsName)
+ {
+ // Get the locale name from ICU
+ StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY);
+ if (!Interop.Globalization.GetLocaleName(localeName, sb, sb.Capacity))
+ {
+ StringBuilderCache.Release(sb);
+ windowsName = null;
+ return false; // fail
+ }
+
+ // Success - use the locale name returned which may be different than realNameBuffer (casing)
+ windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls
+ return true;
+ }
+
+ internal static bool GetDefaultLocaleName(out string windowsName)
+ {
+ // Get the default (system) locale name from ICU
+ StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY);
+ if (!Interop.Globalization.GetDefaultLocaleName(sb, sb.Capacity))
+ {
+ StringBuilderCache.Release(sb);
+ windowsName = null;
+ return false; // fail
+ }
+
+ // Success - use the locale name returned which may be different than realNameBuffer (casing)
+ windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls
+ return true;
+ }
+
+ private string GetLocaleInfo(LocaleStringData type)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo] Expected _sWindowsName to be populated already");
+ return GetLocaleInfo(_sWindowsName, type);
+ }
+
+ // For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the
+ // "windows" name, which can be specific for downlevel (< windows 7) os's.
+ private string GetLocaleInfo(string localeName, LocaleStringData type)
+ {
+ Debug.Assert(localeName != null, "[CultureData.GetLocaleInfo] Expected localeName to be not be null");
+
+ switch (type)
+ {
+ case LocaleStringData.NegativeInfinitySymbol:
+ // not an equivalent in ICU; prefix the PositiveInfinitySymbol with NegativeSign
+ return GetLocaleInfo(localeName, LocaleStringData.NegativeSign) +
+ GetLocaleInfo(localeName, LocaleStringData.PositiveInfinitySymbol);
+ }
+
+ StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
+
+ bool result = Interop.Globalization.GetLocaleInfoString(localeName, (uint)type, sb, sb.Capacity);
+ if (!result)
+ {
+ // Failed, just use empty string
+ StringBuilderCache.Release(sb);
+ Debug.Fail("[CultureData.GetLocaleInfo(LocaleStringData)] Failed");
+ return String.Empty;
+ }
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
+ private int GetLocaleInfo(LocaleNumberData type)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleNumberData)] Expected _sWindowsName to be populated already");
+
+ switch (type)
+ {
+ case LocaleNumberData.CalendarType:
+ // returning 0 will cause the first supported calendar to be returned, which is the preferred calendar
+ return 0;
+ }
+
+
+ int value = 0;
+ bool result = Interop.Globalization.GetLocaleInfoInt(_sWindowsName, (uint)type, ref value);
+ if (!result)
+ {
+ // Failed, just use 0
+ Debug.Fail("[CultureData.GetLocaleInfo(LocaleNumberData)] failed");
+ }
+
+ return value;
+ }
+
+ private int[] GetLocaleInfo(LocaleGroupingData type)
+ {
+ Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleGroupingData)] Expected _sWindowsName to be populated already");
+
+ int primaryGroupingSize = 0;
+ int secondaryGroupingSize = 0;
+ bool result = Interop.Globalization.GetLocaleInfoGroupingSizes(_sWindowsName, (uint)type, ref primaryGroupingSize, ref secondaryGroupingSize);
+ if (!result)
+ {
+ Debug.Fail("[CultureData.GetLocaleInfo(LocaleGroupingData type)] failed");
+ }
+
+ if (secondaryGroupingSize == 0)
+ {
+ return new int[] { primaryGroupingSize };
+ }
+
+ return new int[] { primaryGroupingSize, secondaryGroupingSize };
+ }
+
+ private string GetTimeFormatString()
+ {
+ return GetTimeFormatString(false);
+ }
+
+ private string GetTimeFormatString(bool shortFormat)
+ {
+ Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatString(bool shortFormat)] Expected _sWindowsName to be populated already");
+
+ StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
+
+ bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, sb, sb.Capacity);
+ if (!result)
+ {
+ // Failed, just use empty string
+ StringBuilderCache.Release(sb);
+ Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed");
+ return String.Empty;
+ }
+
+ return ConvertIcuTimeFormatString(StringBuilderCache.GetStringAndRelease(sb));
+ }
+
+ private int GetFirstDayOfWeek()
+ {
+ return this.GetLocaleInfo(LocaleNumberData.FirstDayOfWeek);
+ }
+
+ private String[] GetTimeFormats()
+ {
+ string format = GetTimeFormatString(false);
+ return new string[] { format };
+ }
+
+ private String[] GetShortTimeFormats()
+ {
+ string format = GetTimeFormatString(true);
+ return new string[] { format };
+ }
+
+ private static CultureData GetCultureDataFromRegionName(String regionName)
+ {
+ // no support to lookup by region name, other than the hard-coded list in CultureData
+ return null;
+ }
+
+ private static string GetLanguageDisplayName(string cultureName)
+ {
+ return new CultureInfo(cultureName)._cultureData.GetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName);
+ }
+
+ private static string GetRegionDisplayName(string isoCountryCode)
+ {
+ // use the fallback which is to return NativeName
+ return null;
+ }
+
+ private static CultureInfo GetUserDefaultCulture()
+ {
+ return CultureInfo.GetUserDefaultCulture();
+ }
+
+ private static string ConvertIcuTimeFormatString(string icuFormatString)
+ {
+ StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY);
+ bool amPmAdded = false;
+
+ for (int i = 0; i < icuFormatString.Length; i++)
+ {
+ switch(icuFormatString[i])
+ {
+ case ':':
+ case '.':
+ case 'H':
+ case 'h':
+ case 'm':
+ case 's':
+ sb.Append(icuFormatString[i]);
+ break;
+
+ case ' ':
+ case '\u00A0':
+ // Convert nonbreaking spaces into regular spaces
+ sb.Append(' ');
+ break;
+
+ case 'a': // AM/PM
+ if (!amPmAdded)
+ {
+ amPmAdded = true;
+ sb.Append("tt");
+ }
+ break;
+
+ }
+ }
+
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
+ private static string LCIDToLocaleName(int culture)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ return LocaleData.LCIDToLocaleName(culture);
+ }
+
+ private static int LocaleNameToLCID(string cultureName)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ int lcid = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.Lcid);
+ return lcid == -1 ? CultureInfo.LOCALE_CUSTOM_UNSPECIFIED : lcid;
+ }
+
+ private static int GetAnsiCodePage(string cultureName)
+ {
+ int ansiCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.AnsiCodePage);
+ return ansiCodePage == -1 ? CultureData.Invariant.IDEFAULTANSICODEPAGE : ansiCodePage;
+ }
+
+ private static int GetOemCodePage(string cultureName)
+ {
+ int oemCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.OemCodePage);
+ return oemCodePage == -1 ? CultureData.Invariant.IDEFAULTOEMCODEPAGE : oemCodePage;
+ }
+
+ private static int GetMacCodePage(string cultureName)
+ {
+ int macCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.MacCodePage);
+ return macCodePage == -1 ? CultureData.Invariant.IDEFAULTMACCODEPAGE : macCodePage;
+ }
+
+ private static int GetEbcdicCodePage(string cultureName)
+ {
+ int ebcdicCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.EbcdicCodePage);
+ return ebcdicCodePage == -1 ? CultureData.Invariant.IDEFAULTEBCDICCODEPAGE : ebcdicCodePage;
+ }
+
+ private static int GetGeoId(string cultureName)
+ {
+ int geoId = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.GeoId);
+ return geoId == -1 ? CultureData.Invariant.IGEOID : geoId;
+ }
+
+ private static int GetDigitSubstitution(string cultureName)
+ {
+ int digitSubstitution = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.DigitSubstitution);
+ return digitSubstitution == -1 ? (int) DigitShapes.None : digitSubstitution;
+ }
+
+ private static string GetThreeLetterWindowsLanguageName(string cultureName)
+ {
+ string langName = LocaleData.GetThreeLetterWindowsLangageName(cultureName);
+ return langName == null ? "ZZZ" /* default lang name */ : langName;
+ }
+
+ private static CultureInfo[] EnumCultures(CultureTypes types)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ if ((types & (CultureTypes.NeutralCultures | CultureTypes.SpecificCultures)) == 0)
+ {
+ return Array.Empty<CultureInfo>();
+ }
+
+ int bufferLength = Interop.Globalization.GetLocales(null, 0);
+ if (bufferLength <= 0)
+ {
+ return Array.Empty<CultureInfo>();
+ }
+
+ Char [] chars = new Char[bufferLength];
+
+ bufferLength = Interop.Globalization.GetLocales(chars, bufferLength);
+ if (bufferLength <= 0)
+ {
+ return Array.Empty<CultureInfo>();
+ }
+
+ bool enumNeutrals = (types & CultureTypes.NeutralCultures) != 0;
+ bool enumSpecificss = (types & CultureTypes.SpecificCultures) != 0;
+
+ List<CultureInfo> list = new List<CultureInfo>();
+ if (enumNeutrals)
+ {
+ list.Add(CultureInfo.InvariantCulture);
+ }
+
+ int index = 0;
+ while (index < bufferLength)
+ {
+ int length = (int) chars[index++];
+ if (index + length <= bufferLength)
+ {
+ CultureInfo ci = CultureInfo.GetCultureInfo(new String(chars, index, length));
+ if ((enumNeutrals && ci.IsNeutralCulture) || (enumSpecificss && !ci.IsNeutralCulture))
+ {
+ list.Add(ci);
+ }
+ }
+
+ index += length;
+ }
+
+ return list.ToArray();
+ }
+
+ private static string GetConsoleFallbackName(string cultureName)
+ {
+ return LocaleData.GetConsoleUICulture(cultureName);
+ }
+
+ internal bool IsFramework // not applicable on Linux based systems
+ {
+ get { return false; }
+ }
+
+ internal bool IsWin32Installed // not applicable on Linux based systems
+ {
+ get { return false; }
+ }
+
+ internal bool IsReplacementCulture // not applicable on Linux based systems
+ {
+ get { return false; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs
new file mode 100644
index 0000000000..393f983bba
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs
@@ -0,0 +1,774 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Internal.Runtime.CompilerServices;
+
+#if ENABLE_WINRT
+using Internal.Runtime.Augments;
+#endif
+
+namespace System.Globalization
+{
+ internal partial class CultureData
+ {
+ private const uint LOCALE_NOUSEROVERRIDE = 0x80000000;
+ private const uint LOCALE_RETURN_NUMBER = 0x20000000;
+ private const uint LOCALE_SISO3166CTRYNAME = 0x0000005A;
+
+ private const uint TIME_NOSECONDS = 0x00000002;
+
+ /// <summary>
+ /// Check with the OS to see if this is a valid culture.
+ /// If so we populate a limited number of fields. If its not valid we return false.
+ ///
+ /// The fields we populate:
+ ///
+ /// sWindowsName -- The name that windows thinks this culture is, ie:
+ /// en-US if you pass in en-US
+ /// de-DE_phoneb if you pass in de-DE_phoneb
+ /// fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
+ /// fj if you pass in fj (neutral, post-Windows 7 machine)
+ ///
+ /// sRealName -- The name you used to construct the culture, in pretty form
+ /// en-US if you pass in EN-us
+ /// en if you pass in en
+ /// de-DE_phoneb if you pass in de-DE_phoneb
+ ///
+ /// sSpecificCulture -- The specific culture for this culture
+ /// en-US for en-US
+ /// en-US for en
+ /// de-DE_phoneb for alt sort
+ /// fj-FJ for fj (neutral)
+ ///
+ /// sName -- The IETF name of this culture (ie: no sort info, could be neutral)
+ /// en-US if you pass in en-US
+ /// en if you pass in en
+ /// de-DE if you pass in de-DE_phoneb
+ ///
+ /// bNeutral -- TRUE if it is a neutral locale
+ ///
+ /// For a neutral we just populate the neutral name, but we leave the windows name pointing to the
+ /// windows locale that's going to provide data for us.
+ /// </summary>
+ private unsafe bool InitCultureData()
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ const uint LOCALE_ILANGUAGE = 0x00000001;
+ const uint LOCALE_INEUTRAL = 0x00000071;
+ const uint LOCALE_SNAME = 0x0000005c;
+
+ int result;
+ string realNameBuffer = _sRealName;
+ char* pBuffer = stackalloc char[LOCALE_NAME_MAX_LENGTH];
+
+ result = GetLocaleInfoEx(realNameBuffer, LOCALE_SNAME, pBuffer, LOCALE_NAME_MAX_LENGTH);
+
+ // Did it fail?
+ if (result == 0)
+ {
+ return false;
+ }
+
+ // It worked, note that the name is the locale name, so use that (even for neutrals)
+ // We need to clean up our "real" name, which should look like the windows name right now
+ // so overwrite the input with the cleaned up name
+ _sRealName = new string(pBuffer, 0, result - 1);
+ realNameBuffer = _sRealName;
+
+ // Check for neutrality, don't expect to fail
+ // (buffer has our name in it, so we don't have to do the gc. stuff)
+
+ result = GetLocaleInfoEx(realNameBuffer, LOCALE_INEUTRAL | LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char));
+ if (result == 0)
+ {
+ return false;
+ }
+
+ // Remember our neutrality
+ _bNeutral = *((uint*)pBuffer) != 0;
+
+ // Note: Parents will be set dynamically
+
+ // Start by assuming the windows name will be the same as the specific name since windows knows
+ // about specifics on all versions. Only for downlevel Neutral locales does this have to change.
+ _sWindowsName = realNameBuffer;
+
+ // Neutrals and non-neutrals are slightly different
+ if (_bNeutral)
+ {
+ // Neutral Locale
+
+ // IETF name looks like neutral name
+ _sName = realNameBuffer;
+
+ // Specific locale name is whatever ResolveLocaleName (win7+) returns.
+ // (Buffer has our name in it, and we can recycle that because windows resolves it before writing to the buffer)
+ result = Interop.Kernel32.ResolveLocaleName(realNameBuffer, pBuffer, LOCALE_NAME_MAX_LENGTH);
+
+ // 0 is failure, 1 is invariant (""), which we expect
+ if (result < 1)
+ {
+ return false;
+ }
+ // We found a locale name, so use it.
+ // In vista this should look like a sort name (de-DE_phoneb) or a specific culture (en-US) and be in the "pretty" form
+ _sSpecificCulture = new string(pBuffer, 0, result - 1);
+ }
+ else
+ {
+ // Specific Locale
+
+ // Specific culture's the same as the locale name since we know its not neutral
+ // On mac we'll use this as well, even for neutrals. There's no obvious specific
+ // culture to use and this isn't exposed, but behaviorally this is correct on mac.
+ // Note that specifics include the sort name (de-DE_phoneb)
+ _sSpecificCulture = realNameBuffer;
+
+ _sName = realNameBuffer;
+
+ // We need the IETF name (sname)
+ // If we aren't an alt sort locale then this is the same as the windows name.
+ // If we are an alt sort locale then this is the same as the part before the _ in the windows name
+ // This is for like de-DE_phoneb and es-ES_tradnl that hsouldn't have the _ part
+
+ result = GetLocaleInfoEx(realNameBuffer, LOCALE_ILANGUAGE | LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char));
+ if (result == 0)
+ {
+ return false;
+ }
+
+ _iLanguage = *((int*)pBuffer);
+
+ if (!IsCustomCultureId(_iLanguage))
+ {
+ // not custom locale
+ int index = realNameBuffer.IndexOf('_');
+ if (index > 0 && index < realNameBuffer.Length)
+ {
+ _sName = realNameBuffer.Substring(0, index);
+ }
+ }
+ }
+
+ // It succeeded.
+ return true;
+ }
+
+ // Wrappers around the GetLocaleInfoEx APIs which handle marshalling the returned
+ // data as either and Int or string.
+ internal static unsafe string GetLocaleInfoEx(string localeName, uint field)
+ {
+ // REVIEW: Determine the maximum size for the buffer
+ const int BUFFER_SIZE = 530;
+
+ char* pBuffer = stackalloc char[BUFFER_SIZE];
+ int resultCode = GetLocaleInfoEx(localeName, field, pBuffer, BUFFER_SIZE);
+ if (resultCode > 0)
+ {
+ return new string(pBuffer);
+ }
+
+ return null;
+ }
+
+ internal static unsafe int GetLocaleInfoExInt(string localeName, uint field)
+ {
+ const uint LOCALE_RETURN_NUMBER = 0x20000000;
+ field |= LOCALE_RETURN_NUMBER;
+ int value = 0;
+ GetLocaleInfoEx(localeName, field, (char*) &value, sizeof(int));
+ return value;
+ }
+
+ internal static unsafe int GetLocaleInfoEx(string lpLocaleName, uint lcType, char* lpLCData, int cchData)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ return Interop.Kernel32.GetLocaleInfoEx(lpLocaleName, lcType, lpLCData, cchData);
+ }
+
+ private string GetLocaleInfo(LocaleStringData type)
+ {
+ Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfo] Expected _sWindowsName to be populated by already");
+ return GetLocaleInfo(_sWindowsName, type);
+ }
+
+ // For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the
+ // "windows" name, which can be specific for downlevel (< windows 7) os's.
+ private string GetLocaleInfo(string localeName, LocaleStringData type)
+ {
+ uint lctype = (uint)type;
+
+ return GetLocaleInfoFromLCType(localeName, lctype, UseUserOverride);
+ }
+
+ private int GetLocaleInfo(LocaleNumberData type)
+ {
+ uint lctype = (uint)type;
+
+ // Fix lctype if we don't want overrides
+ if (!UseUserOverride)
+ {
+ lctype |= LOCALE_NOUSEROVERRIDE;
+ }
+
+ // Ask OS for data, note that we presume it returns success, so we have to know that
+ // sWindowsName is valid before calling.
+ Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already");
+ return GetLocaleInfoExInt(_sWindowsName, lctype);
+ }
+
+ private int[] GetLocaleInfo(LocaleGroupingData type)
+ {
+ return ConvertWin32GroupString(GetLocaleInfoFromLCType(_sWindowsName, (uint)type, UseUserOverride));
+ }
+
+ private string GetTimeFormatString()
+ {
+ const uint LOCALE_STIMEFORMAT = 0x00001003;
+
+ return ReescapeWin32String(GetLocaleInfoFromLCType(_sWindowsName, LOCALE_STIMEFORMAT, UseUserOverride));
+ }
+
+ private int GetFirstDayOfWeek()
+ {
+ Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already");
+
+ const uint LOCALE_IFIRSTDAYOFWEEK = 0x0000100C;
+
+ int result = GetLocaleInfoExInt(_sWindowsName, LOCALE_IFIRSTDAYOFWEEK | (!UseUserOverride ? LOCALE_NOUSEROVERRIDE : 0));
+
+ // Win32 and .NET disagree on the numbering for days of the week, so we have to convert.
+ return ConvertFirstDayOfWeekMonToSun(result);
+ }
+
+ private string[] GetTimeFormats()
+ {
+ // Note that this gets overrides for us all the time
+ Debug.Assert(_sWindowsName != null, "[CultureData.DoEnumTimeFormats] Expected _sWindowsName to be populated by already");
+ string[] result = ReescapeWin32Strings(nativeEnumTimeFormats(_sWindowsName, 0, UseUserOverride));
+
+ return result;
+ }
+
+ private string[] GetShortTimeFormats()
+ {
+ // Note that this gets overrides for us all the time
+ Debug.Assert(_sWindowsName != null, "[CultureData.DoEnumShortTimeFormats] Expected _sWindowsName to be populated by already");
+ string[] result = ReescapeWin32Strings(nativeEnumTimeFormats(_sWindowsName, TIME_NOSECONDS, UseUserOverride));
+
+ return result;
+ }
+
+ // Enumerate all system cultures and then try to find out which culture has
+ // region name match the requested region name
+ private static CultureData GetCultureDataFromRegionName(string regionName)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(regionName != null);
+
+ const uint LOCALE_SUPPLEMENTAL = 0x00000002;
+ const uint LOCALE_SPECIFICDATA = 0x00000020;
+
+ EnumLocaleData context = new EnumLocaleData();
+ context.cultureName = null;
+ context.regionName = regionName;
+
+ unsafe
+ {
+ Interop.Kernel32.EnumSystemLocalesEx(EnumSystemLocalesProc, LOCALE_SPECIFICDATA | LOCALE_SUPPLEMENTAL, Unsafe.AsPointer(ref context), IntPtr.Zero);
+ }
+
+ if (context.cultureName != null)
+ {
+ // we got a matched culture
+ return GetCultureData(context.cultureName, true);
+ }
+
+ return null;
+ }
+
+ private string GetLanguageDisplayName(string cultureName)
+ {
+#if ENABLE_WINRT
+ return WinRTInterop.Callbacks.GetLanguageDisplayName(cultureName);
+#else
+ // Usually the UI culture shouldn't be different than what we got from WinRT except
+ // if DefaultThreadCurrentUICulture was set
+ CultureInfo ci;
+
+ if (CultureInfo.DefaultThreadCurrentUICulture != null &&
+ ((ci = GetUserDefaultCulture()) != null) &&
+ !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name))
+ {
+ return SNATIVEDISPLAYNAME;
+ }
+ else
+ {
+ return GetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName);
+ }
+#endif // ENABLE_WINRT
+ }
+
+ private string GetRegionDisplayName(string isoCountryCode)
+ {
+#if ENABLE_WINRT
+ return WinRTInterop.Callbacks.GetRegionDisplayName(isoCountryCode);
+#else
+ // If the current UI culture matching the OS UI language, we'll get the display name from the OS.
+ // otherwise, we use the native name as we don't carry resources for the region display names anyway.
+ if (CultureInfo.CurrentUICulture.Name.Equals(CultureInfo.UserDefaultUICulture.Name))
+ {
+ return GetLocaleInfo(LocaleStringData.LocalizedCountryName);
+ }
+
+ return SNATIVECOUNTRY;
+#endif // ENABLE_WINRT
+ }
+
+ private static CultureInfo GetUserDefaultCulture()
+ {
+#if ENABLE_WINRT
+ return (CultureInfo)WinRTInterop.Callbacks.GetUserDefaultCulture();
+#else
+ return CultureInfo.GetUserDefaultCulture();
+#endif // ENABLE_WINRT
+ }
+
+ // PAL methods end here.
+
+ private static string GetLocaleInfoFromLCType(string localeName, uint lctype, bool useUserOveride)
+ {
+ Debug.Assert(localeName != null, "[CultureData.GetLocaleInfoFromLCType] Expected localeName to be not be null");
+
+ // Fix lctype if we don't want overrides
+ if (!useUserOveride)
+ {
+ lctype |= LOCALE_NOUSEROVERRIDE;
+ }
+
+ // Ask OS for data
+ string result = GetLocaleInfoEx(localeName, lctype);
+ if (result == null)
+ {
+ // Failed, just use empty string
+ result = string.Empty;
+ }
+
+ return result;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Reescape a Win32 style quote string as a NLS+ style quoted string
+ //
+ // This is also the escaping style used by custom culture data files
+ //
+ // NLS+ uses \ to escape the next character, whether in a quoted string or
+ // not, so we always have to change \ to \\.
+ //
+ // NLS+ uses \' to escape a quote inside a quoted string so we have to change
+ // '' to \' (if inside a quoted string)
+ //
+ // We don't build the stringbuilder unless we find something to change
+ ////////////////////////////////////////////////////////////////////////////
+ internal static string ReescapeWin32String(string str)
+ {
+ // If we don't have data, then don't try anything
+ if (str == null)
+ return null;
+
+ StringBuilder result = null;
+
+ bool inQuote = false;
+ for (int i = 0; i < str.Length; i++)
+ {
+ // Look for quote
+ if (str[i] == '\'')
+ {
+ // Already in quote?
+ if (inQuote)
+ {
+ // See another single quote. Is this '' of 'fred''s' or '''', or is it an ending quote?
+ if (i + 1 < str.Length && str[i + 1] == '\'')
+ {
+ // Found another ', so we have ''. Need to add \' instead.
+ // 1st make sure we have our stringbuilder
+ if (result == null)
+ result = new StringBuilder(str, 0, i, str.Length * 2);
+
+ // Append a \' and keep going (so we don't turn off quote mode)
+ result.Append("\\'");
+ i++;
+ continue;
+ }
+
+ // Turning off quote mode, fall through to add it
+ inQuote = false;
+ }
+ else
+ {
+ // Found beginning quote, fall through to add it
+ inQuote = true;
+ }
+ }
+ // Is there a single \ character?
+ else if (str[i] == '\\')
+ {
+ // Found a \, need to change it to \\
+ // 1st make sure we have our stringbuilder
+ if (result == null)
+ result = new StringBuilder(str, 0, i, str.Length * 2);
+
+ // Append our \\ to the string & continue
+ result.Append("\\\\");
+ continue;
+ }
+
+ // If we have a builder we need to add our character
+ if (result != null)
+ result.Append(str[i]);
+ }
+
+ // Unchanged string? , just return input string
+ if (result == null)
+ return str;
+
+ // String changed, need to use the builder
+ return result.ToString();
+ }
+
+ internal static string[] ReescapeWin32Strings(string[] array)
+ {
+ if (array != null)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ array[i] = ReescapeWin32String(array[i]);
+ }
+ }
+
+ return array;
+ }
+
+ // If we get a group from windows, then its in 3;0 format with the 0 backwards
+ // of how NLS+ uses it (ie: if the string has a 0, then the int[] shouldn't and vice versa)
+ // EXCEPT in the case where the list only contains 0 in which NLS and NLS+ have the same meaning.
+ private static int[] ConvertWin32GroupString(string win32Str)
+ {
+ // None of these cases make any sense
+ if (win32Str == null || win32Str.Length == 0)
+ {
+ return (new int[] { 3 });
+ }
+
+ if (win32Str[0] == '0')
+ {
+ return (new int[] { 0 });
+ }
+
+ // Since its in n;n;n;n;n format, we can always get the length quickly
+ int[] values;
+ if (win32Str[win32Str.Length - 1] == '0')
+ {
+ // Trailing 0 gets dropped. 1;0 -> 1
+ values = new int[(win32Str.Length / 2)];
+ }
+ else
+ {
+ // Need extra space for trailing zero 1 -> 1;0
+ values = new int[(win32Str.Length / 2) + 2];
+ values[values.Length - 1] = 0;
+ }
+
+ int i;
+ int j;
+ for (i = 0, j = 0; i < win32Str.Length && j < values.Length; i += 2, j++)
+ {
+ // Note that this # shouldn't ever be zero, 'cause 0 is only at end
+ // But we'll test because its registry that could be anything
+ if (win32Str[i] < '1' || win32Str[i] > '9')
+ return new int[] { 3 };
+
+ values[j] = (int)(win32Str[i] - '0');
+ }
+
+ return (values);
+ }
+
+ private static int ConvertFirstDayOfWeekMonToSun(int iTemp)
+ {
+ // Convert Mon-Sun to Sun-Sat format
+ iTemp++;
+ if (iTemp > 6)
+ {
+ // Wrap Sunday and convert invalid data to Sunday
+ iTemp = 0;
+ }
+ return iTemp;
+ }
+
+
+ // Context for EnumCalendarInfoExEx callback.
+ private class EnumLocaleData
+ {
+ public string regionName;
+ public string cultureName;
+ }
+
+ // EnumSystemLocaleEx callback.
+ // [NativeCallable(CallingConvention = CallingConvention.StdCall)]
+ private static unsafe Interop.BOOL EnumSystemLocalesProc(char* lpLocaleString, uint flags, void* contextHandle)
+ {
+ ref EnumLocaleData context = ref Unsafe.As<byte, EnumLocaleData>(ref *(byte*)contextHandle);
+ try
+ {
+ string cultureName = new string(lpLocaleString);
+ string regionName = GetLocaleInfoEx(cultureName, LOCALE_SISO3166CTRYNAME);
+ if (regionName != null && regionName.Equals(context.regionName, StringComparison.OrdinalIgnoreCase))
+ {
+ context.cultureName = cultureName;
+ return Interop.BOOL.FALSE; // we found a match, then stop the enumeration
+ }
+
+ return Interop.BOOL.TRUE;
+ }
+ catch (Exception)
+ {
+ return Interop.BOOL.FALSE;
+ }
+ }
+
+ // EnumSystemLocaleEx callback.
+ // [NativeCallable(CallingConvention = CallingConvention.StdCall)]
+ private static unsafe Interop.BOOL EnumAllSystemLocalesProc(char* lpLocaleString, uint flags, void* contextHandle)
+ {
+ ref EnumData context = ref Unsafe.As<byte, EnumData>(ref *(byte*)contextHandle);
+ try
+ {
+ context.strings.Add(new string(lpLocaleString));
+ return Interop.BOOL.TRUE;
+ }
+ catch (Exception)
+ {
+ return Interop.BOOL.FALSE;
+ }
+ }
+
+ // Context for EnumTimeFormatsEx callback.
+ private struct EnumData
+ {
+ public List<string> strings;
+ }
+
+ // EnumTimeFormatsEx callback itself.
+ // [NativeCallable(CallingConvention = CallingConvention.StdCall)]
+ private static unsafe Interop.BOOL EnumTimeCallback(char* lpTimeFormatString, void* lParam)
+ {
+ ref EnumData context = ref Unsafe.As<byte, EnumData>(ref *(byte*)lParam);
+ try
+ {
+ context.strings.Add(new string(lpTimeFormatString));
+ return Interop.BOOL.TRUE;
+ }
+ catch (Exception)
+ {
+ return Interop.BOOL.FALSE;
+ }
+ }
+
+ private static unsafe string[] nativeEnumTimeFormats(string localeName, uint dwFlags, bool useUserOverride)
+ {
+ const uint LOCALE_SSHORTTIME = 0x00000079;
+ const uint LOCALE_STIMEFORMAT = 0x00001003;
+
+ EnumData data = new EnumData();
+ data.strings = new List<string>();
+
+ // Now call the enumeration API. Work is done by our callback function
+ Interop.Kernel32.EnumTimeFormatsEx(EnumTimeCallback, localeName, (uint)dwFlags, Unsafe.AsPointer(ref data));
+
+ if (data.strings.Count > 0)
+ {
+ // Now we need to allocate our stringarray and populate it
+ string[] results = data.strings.ToArray();
+
+ if (!useUserOverride && data.strings.Count > 1)
+ {
+ // Since there is no "NoUserOverride" aware EnumTimeFormatsEx, we always get an override
+ // The override is the first entry if it is overriden.
+ // We can check if we have overrides by checking the GetLocaleInfo with no override
+ // If we do have an override, we don't know if it is a user defined override or if the
+ // user has just selected one of the predefined formats so we can't just remove it
+ // but we can move it down.
+ uint lcType = (dwFlags == TIME_NOSECONDS) ? LOCALE_SSHORTTIME : LOCALE_STIMEFORMAT;
+ string timeFormatNoUserOverride = GetLocaleInfoFromLCType(localeName, lcType, useUserOverride);
+ if (timeFormatNoUserOverride != "")
+ {
+ string firstTimeFormat = results[0];
+ if (timeFormatNoUserOverride != firstTimeFormat)
+ {
+ results[0] = results[1];
+ results[1] = firstTimeFormat;
+ }
+ }
+ }
+
+ return results;
+ }
+
+ return null;
+ }
+
+ private static int LocaleNameToLCID(string cultureName)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ return Interop.Kernel32.LocaleNameToLCID(cultureName, Interop.Kernel32.LOCALE_ALLOW_NEUTRAL_NAMES);
+ }
+
+ private static unsafe string LCIDToLocaleName(int culture)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ char *pBuffer = stackalloc char[Interop.Kernel32.LOCALE_NAME_MAX_LENGTH + 1]; // +1 for the null termination
+ int length = Interop.Kernel32.LCIDToLocaleName(culture, pBuffer, Interop.Kernel32.LOCALE_NAME_MAX_LENGTH + 1, Interop.Kernel32.LOCALE_ALLOW_NEUTRAL_NAMES);
+
+ if (length > 0)
+ {
+ return new string(pBuffer);
+ }
+
+ return null;
+ }
+
+ private int GetAnsiCodePage(string cultureName)
+ {
+ return GetLocaleInfo(LocaleNumberData.AnsiCodePage);
+ }
+
+ private int GetOemCodePage(string cultureName)
+ {
+ return GetLocaleInfo(LocaleNumberData.OemCodePage);
+ }
+
+ private int GetMacCodePage(string cultureName)
+ {
+ return GetLocaleInfo(LocaleNumberData.MacCodePage);
+ }
+
+ private int GetEbcdicCodePage(string cultureName)
+ {
+ return GetLocaleInfo(LocaleNumberData.EbcdicCodePage);
+ }
+
+ private int GetGeoId(string cultureName)
+ {
+ return GetLocaleInfo(LocaleNumberData.GeoId);
+ }
+
+ private int GetDigitSubstitution(string cultureName)
+ {
+ return GetLocaleInfo(LocaleNumberData.DigitSubstitution);
+ }
+
+ private string GetThreeLetterWindowsLanguageName(string cultureName)
+ {
+ return GetLocaleInfo(cultureName, LocaleStringData.AbbreviatedWindowsLanguageName);
+ }
+
+ private static CultureInfo[] EnumCultures(CultureTypes types)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ uint flags = 0;
+
+#pragma warning disable 618
+ if ((types & (CultureTypes.FrameworkCultures | CultureTypes.InstalledWin32Cultures | CultureTypes.ReplacementCultures)) != 0)
+ {
+ flags |= Interop.Kernel32.LOCALE_NEUTRALDATA | Interop.Kernel32.LOCALE_SPECIFICDATA;
+ }
+#pragma warning restore 618
+
+ if ((types & CultureTypes.NeutralCultures) != 0)
+ {
+ flags |= Interop.Kernel32.LOCALE_NEUTRALDATA;
+ }
+
+ if ((types & CultureTypes.SpecificCultures) != 0)
+ {
+ flags |= Interop.Kernel32.LOCALE_SPECIFICDATA;
+ }
+
+ if ((types & CultureTypes.UserCustomCulture) != 0)
+ {
+ flags |= Interop.Kernel32.LOCALE_SUPPLEMENTAL;
+ }
+
+ if ((types & CultureTypes.ReplacementCultures) != 0)
+ {
+ flags |= Interop.Kernel32.LOCALE_SUPPLEMENTAL;
+ }
+
+ EnumData context = new EnumData();
+ context.strings = new List<string>();
+
+ unsafe
+ {
+ Interop.Kernel32.EnumSystemLocalesEx(EnumAllSystemLocalesProc, flags, Unsafe.AsPointer(ref context), IntPtr.Zero);
+ }
+
+ CultureInfo [] cultures = new CultureInfo[context.strings.Count];
+ for (int i = 0; i < cultures.Length; i++)
+ {
+ cultures[i] = new CultureInfo(context.strings[i]);
+ }
+
+ return cultures;
+ }
+
+ private string GetConsoleFallbackName(string cultureName)
+ {
+ return GetLocaleInfo(cultureName, LocaleStringData.ConsoleFallbackName);
+ }
+
+ internal bool IsFramework
+ {
+ get { return false; }
+ }
+
+ internal bool IsWin32Installed
+ {
+ get { return true; }
+ }
+
+ internal bool IsReplacementCulture
+ {
+ get
+ {
+ EnumData context = new EnumData();
+ context.strings = new List<string>();
+
+ unsafe
+ {
+ Interop.Kernel32.EnumSystemLocalesEx(EnumAllSystemLocalesProc, Interop.Kernel32.LOCALE_REPLACEMENT, Unsafe.AsPointer(ref context), IntPtr.Zero);
+ }
+
+ for (int i=0; i<context.strings.Count; i++)
+ {
+ if (string.Compare(context.strings[i], _sWindowsName, StringComparison.OrdinalIgnoreCase) == 0)
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CultureData.cs b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.cs
new file mode 100644
index 0000000000..fda239c518
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.cs
@@ -0,0 +1,2574 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Threading;
+
+namespace System.Globalization
+{
+#if CORERT
+ using StringStringDictionary = LowLevelDictionary<string, string>;
+ using StringCultureDataDictionary = LowLevelDictionary<string, CultureData>;
+ using LcidToCultureNameDictionary = LowLevelDictionary<int, string>;
+#else
+ using StringStringDictionary = Dictionary<string, string>;
+ using StringCultureDataDictionary = Dictionary<string, CultureData>;
+ using LcidToCultureNameDictionary = Dictionary<int, string>;
+#endif
+
+ //
+ // List of culture data
+ // Note the we cache overrides.
+ // Note that localized names (resource names) aren't available from here.
+ //
+
+ //
+ // Our names are a tad confusing.
+ //
+ // sWindowsName -- The name that windows thinks this culture is, ie:
+ // en-US if you pass in en-US
+ // de-DE_phoneb if you pass in de-DE_phoneb
+ // fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
+ // fj if you pass in fj (neutral, post-Windows 7 machine)
+ //
+ // sRealName -- The name you used to construct the culture, in pretty form
+ // en-US if you pass in EN-us
+ // en if you pass in en
+ // de-DE_phoneb if you pass in de-DE_phoneb
+ //
+ // sSpecificCulture -- The specific culture for this culture
+ // en-US for en-US
+ // en-US for en
+ // de-DE_phoneb for alt sort
+ // fj-FJ for fj (neutral)
+ //
+ // sName -- The IETF name of this culture (ie: no sort info, could be neutral)
+ // en-US if you pass in en-US
+ // en if you pass in en
+ // de-DE if you pass in de-DE_phoneb
+ //
+ internal partial class CultureData
+ {
+ private const int LOCALE_NAME_MAX_LENGTH = 85;
+ private const int undef = -1;
+
+ // Override flag
+ private string _sRealName; // Name you passed in (ie: en-US, en, or de-DE_phoneb)
+ private string _sWindowsName; // Name OS thinks the object is (ie: de-DE_phoneb, or en-US (even if en was passed in))
+
+ // Identity
+ private string _sName; // locale name (ie: en-us, NO sort info, but could be neutral)
+ private string _sParent; // Parent name (which may be a custom locale/culture)
+ private string _sLocalizedDisplayName; // Localized pretty name for this locale
+ private string _sEnglishDisplayName; // English pretty name for this locale
+ private string _sNativeDisplayName; // Native pretty name for this locale
+ private string _sSpecificCulture; // The culture name to be used in CultureInfo.CreateSpecificCulture(), en-US form if neutral, sort name if sort
+
+ // Language
+ private string _sISO639Language; // ISO 639 Language Name
+ private string _sISO639Language2; // ISO 639 Language Name
+ private string _sLocalizedLanguage; // Localized name for this language
+ private string _sEnglishLanguage; // English name for this language
+ private string _sNativeLanguage; // Native name of this language
+ private string _sAbbrevLang; // abbreviated language name (Windows Language Name) ex: ENU
+ private string _sConsoleFallbackName; // The culture name for the console fallback UI culture
+ private int _iInputLanguageHandle=undef;// input language handle
+
+ // Region
+ private string _sRegionName; // (RegionInfo)
+ private string _sLocalizedCountry; // localized country name
+ private string _sEnglishCountry; // english country name (RegionInfo)
+ private string _sNativeCountry; // native country name
+ private string _sISO3166CountryName; // ISO 3166 (RegionInfo), ie: US
+ private string _sISO3166CountryName2; // 3 char ISO 3166 country name 2 2(RegionInfo) ex: USA (ISO)
+ private int _iGeoId = undef; // GeoId
+
+ // Numbers
+ private string _sPositiveSign; // (user can override) positive sign
+ private string _sNegativeSign; // (user can override) negative sign
+ // (nfi populates these 5, don't have to be = undef)
+ private int _iDigits; // (user can override) number of fractional digits
+ private int _iNegativeNumber; // (user can override) negative number format
+ private int[] _waGrouping; // (user can override) grouping of digits
+ private string _sDecimalSeparator; // (user can override) decimal separator
+ private string _sThousandSeparator; // (user can override) thousands separator
+ private string _sNaN; // Not a Number
+ private string _sPositiveInfinity; // + Infinity
+ private string _sNegativeInfinity; // - Infinity
+
+ // Percent
+ private int _iNegativePercent = undef; // Negative Percent (0-3)
+ private int _iPositivePercent = undef; // Positive Percent (0-11)
+ private string _sPercent; // Percent (%) symbol
+ private string _sPerMille; // PerMille symbol
+
+ // Currency
+ private string _sCurrency; // (user can override) local monetary symbol
+ private string _sIntlMonetarySymbol; // international monetary symbol (RegionInfo)
+ private string _sEnglishCurrency; // English name for this currency
+ private string _sNativeCurrency; // Native name for this currency
+ // (nfi populates these 4, don't have to be = undef)
+ private int _iCurrencyDigits; // (user can override) # local monetary fractional digits
+ private int _iCurrency; // (user can override) positive currency format
+ private int _iNegativeCurrency; // (user can override) negative currency format
+ private int[] _waMonetaryGrouping; // (user can override) monetary grouping of digits
+ private string _sMonetaryDecimal; // (user can override) monetary decimal separator
+ private string _sMonetaryThousand; // (user can override) monetary thousands separator
+
+ // Misc
+ private int _iMeasure = undef; // (user can override) system of measurement 0=metric, 1=US (RegionInfo)
+ private string _sListSeparator; // (user can override) list separator
+
+ // Time
+ private string _sAM1159; // (user can override) AM designator
+ private string _sPM2359; // (user can override) PM designator
+ private string _sTimeSeparator;
+ private volatile string[] _saLongTimes; // (user can override) time format
+ private volatile string[] _saShortTimes; // short time format
+ private volatile string[] _saDurationFormats; // time duration format
+
+ // Calendar specific data
+ private int _iFirstDayOfWeek = undef; // (user can override) first day of week (gregorian really)
+ private int _iFirstWeekOfYear = undef; // (user can override) first week of year (gregorian really)
+ private volatile CalendarId[] _waCalendars; // all available calendar type(s). The first one is the default calendar
+
+ // Store for specific data about each calendar
+ private CalendarData[] _calendars; // Store for specific calendar data
+
+ // Text information
+ private int _iReadingLayout = undef; // Reading layout data
+ // 0 - Left to right (eg en-US)
+ // 1 - Right to left (eg arabic locales)
+ // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
+ // 3 - Vertical top to bottom with columns proceeding to the right
+
+ // CoreCLR depends on this even though its not exposed publicly.
+
+ private int _iDefaultAnsiCodePage = undef; // default ansi code page ID (ACP)
+ private int _iDefaultOemCodePage = undef; // default oem code page ID (OCP or OEM)
+ private int _iDefaultMacCodePage = undef; // default macintosh code page
+ private int _iDefaultEbcdicCodePage = undef; // default EBCDIC code page
+
+ private int _iLanguage; // locale ID (0409) - NO sort information
+ private bool _bUseOverrides; // use user overrides?
+ private bool _bNeutral; // Flags for the culture (ie: neutral or not right now)
+
+ // Region Name to Culture Name mapping table
+ // (In future would be nice to be in registry or something)
+
+ //Using a property so we avoid creating the dictionary until we need it
+ private static StringStringDictionary RegionNames
+ {
+ get
+ {
+ if (s_RegionNames == null)
+ {
+ StringStringDictionary regionNames = new StringStringDictionary(211 /* prime */);
+
+ regionNames.Add("029", "en-029");
+ regionNames.Add("AE", "ar-AE");
+ regionNames.Add("AF", "prs-AF");
+ regionNames.Add("AL", "sq-AL");
+ regionNames.Add("AM", "hy-AM");
+ regionNames.Add("AR", "es-AR");
+ regionNames.Add("AT", "de-AT");
+ regionNames.Add("AU", "en-AU");
+ regionNames.Add("AZ", "az-Cyrl-AZ");
+ regionNames.Add("BA", "bs-Latn-BA");
+ regionNames.Add("BD", "bn-BD");
+ regionNames.Add("BE", "nl-BE");
+ regionNames.Add("BG", "bg-BG");
+ regionNames.Add("BH", "ar-BH");
+ regionNames.Add("BN", "ms-BN");
+ regionNames.Add("BO", "es-BO");
+ regionNames.Add("BR", "pt-BR");
+ regionNames.Add("BY", "be-BY");
+ regionNames.Add("BZ", "en-BZ");
+ regionNames.Add("CA", "en-CA");
+ regionNames.Add("CH", "it-CH");
+ regionNames.Add("CL", "es-CL");
+ regionNames.Add("CN", "zh-CN");
+ regionNames.Add("CO", "es-CO");
+ regionNames.Add("CR", "es-CR");
+ regionNames.Add("CS", "sr-Cyrl-CS");
+ regionNames.Add("CZ", "cs-CZ");
+ regionNames.Add("DE", "de-DE");
+ regionNames.Add("DK", "da-DK");
+ regionNames.Add("DO", "es-DO");
+ regionNames.Add("DZ", "ar-DZ");
+ regionNames.Add("EC", "es-EC");
+ regionNames.Add("EE", "et-EE");
+ regionNames.Add("EG", "ar-EG");
+ regionNames.Add("ES", "es-ES");
+ regionNames.Add("ET", "am-ET");
+ regionNames.Add("FI", "fi-FI");
+ regionNames.Add("FO", "fo-FO");
+ regionNames.Add("FR", "fr-FR");
+ regionNames.Add("GB", "en-GB");
+ regionNames.Add("GE", "ka-GE");
+ regionNames.Add("GL", "kl-GL");
+ regionNames.Add("GR", "el-GR");
+ regionNames.Add("GT", "es-GT");
+ regionNames.Add("HK", "zh-HK");
+ regionNames.Add("HN", "es-HN");
+ regionNames.Add("HR", "hr-HR");
+ regionNames.Add("HU", "hu-HU");
+ regionNames.Add("ID", "id-ID");
+ regionNames.Add("IE", "en-IE");
+ regionNames.Add("IL", "he-IL");
+ regionNames.Add("IN", "hi-IN");
+ regionNames.Add("IQ", "ar-IQ");
+ regionNames.Add("IR", "fa-IR");
+ regionNames.Add("IS", "is-IS");
+ regionNames.Add("IT", "it-IT");
+ regionNames.Add("IV", "");
+ regionNames.Add("JM", "en-JM");
+ regionNames.Add("JO", "ar-JO");
+ regionNames.Add("JP", "ja-JP");
+ regionNames.Add("KE", "sw-KE");
+ regionNames.Add("KG", "ky-KG");
+ regionNames.Add("KH", "km-KH");
+ regionNames.Add("KR", "ko-KR");
+ regionNames.Add("KW", "ar-KW");
+ regionNames.Add("KZ", "kk-KZ");
+ regionNames.Add("LA", "lo-LA");
+ regionNames.Add("LB", "ar-LB");
+ regionNames.Add("LI", "de-LI");
+ regionNames.Add("LK", "si-LK");
+ regionNames.Add("LT", "lt-LT");
+ regionNames.Add("LU", "lb-LU");
+ regionNames.Add("LV", "lv-LV");
+ regionNames.Add("LY", "ar-LY");
+ regionNames.Add("MA", "ar-MA");
+ regionNames.Add("MC", "fr-MC");
+ regionNames.Add("ME", "sr-Latn-ME");
+ regionNames.Add("MK", "mk-MK");
+ regionNames.Add("MN", "mn-MN");
+ regionNames.Add("MO", "zh-MO");
+ regionNames.Add("MT", "mt-MT");
+ regionNames.Add("MV", "dv-MV");
+ regionNames.Add("MX", "es-MX");
+ regionNames.Add("MY", "ms-MY");
+ regionNames.Add("NG", "ig-NG");
+ regionNames.Add("NI", "es-NI");
+ regionNames.Add("NL", "nl-NL");
+ regionNames.Add("NO", "nn-NO");
+ regionNames.Add("NP", "ne-NP");
+ regionNames.Add("NZ", "en-NZ");
+ regionNames.Add("OM", "ar-OM");
+ regionNames.Add("PA", "es-PA");
+ regionNames.Add("PE", "es-PE");
+ regionNames.Add("PH", "en-PH");
+ regionNames.Add("PK", "ur-PK");
+ regionNames.Add("PL", "pl-PL");
+ regionNames.Add("PR", "es-PR");
+ regionNames.Add("PT", "pt-PT");
+ regionNames.Add("PY", "es-PY");
+ regionNames.Add("QA", "ar-QA");
+ regionNames.Add("RO", "ro-RO");
+ regionNames.Add("RS", "sr-Latn-RS");
+ regionNames.Add("RU", "ru-RU");
+ regionNames.Add("RW", "rw-RW");
+ regionNames.Add("SA", "ar-SA");
+ regionNames.Add("SE", "sv-SE");
+ regionNames.Add("SG", "zh-SG");
+ regionNames.Add("SI", "sl-SI");
+ regionNames.Add("SK", "sk-SK");
+ regionNames.Add("SN", "wo-SN");
+ regionNames.Add("SV", "es-SV");
+ regionNames.Add("SY", "ar-SY");
+ regionNames.Add("TH", "th-TH");
+ regionNames.Add("TJ", "tg-Cyrl-TJ");
+ regionNames.Add("TM", "tk-TM");
+ regionNames.Add("TN", "ar-TN");
+ regionNames.Add("TR", "tr-TR");
+ regionNames.Add("TT", "en-TT");
+ regionNames.Add("TW", "zh-TW");
+ regionNames.Add("UA", "uk-UA");
+ regionNames.Add("US", "en-US");
+ regionNames.Add("UY", "es-UY");
+ regionNames.Add("UZ", "uz-Cyrl-UZ");
+ regionNames.Add("VE", "es-VE");
+ regionNames.Add("VN", "vi-VN");
+ regionNames.Add("YE", "ar-YE");
+ regionNames.Add("ZA", "af-ZA");
+ regionNames.Add("ZW", "en-ZW");
+
+ s_RegionNames = regionNames;
+ }
+
+ return s_RegionNames;
+ }
+ }
+
+ // Cache of regions we've already looked up
+ private static volatile StringCultureDataDictionary s_cachedRegions;
+ private static volatile StringStringDictionary s_RegionNames;
+
+ internal static CultureData GetCultureDataForRegion(string cultureName, bool useUserOverride)
+ {
+ // First do a shortcut for Invariant
+ if (string.IsNullOrEmpty(cultureName))
+ {
+ return CultureData.Invariant;
+ }
+
+ //
+ // First check if GetCultureData() can find it (ie: its a real culture)
+ //
+ CultureData retVal = GetCultureData(cultureName, useUserOverride);
+ if (retVal != null && (retVal.IsNeutralCulture == false)) return retVal;
+
+ //
+ // Not a specific culture, perhaps it's region-only name
+ // (Remember this isn't a core clr path where that's not supported)
+ //
+
+ // If it was neutral remember that so that RegionInfo() can throw the right exception
+ CultureData neutral = retVal;
+
+ // Try the hash table next
+ string hashName = AnsiToLower(useUserOverride ? cultureName : cultureName + '*');
+ StringCultureDataDictionary tempHashTable = s_cachedRegions;
+ if (tempHashTable == null)
+ {
+ // No table yet, make a new one
+ tempHashTable = new StringCultureDataDictionary();
+ }
+ else
+ {
+ // Check the hash table
+ lock (s_lock)
+ {
+ tempHashTable.TryGetValue(hashName, out retVal);
+ }
+ if (retVal != null)
+ {
+ return retVal;
+ }
+ }
+
+ //
+ // Not found in the hash table, look it up the hard way
+ //
+
+ // If not a valid mapping from the registry we'll have to try the hard coded table
+ if (retVal == null || (retVal.IsNeutralCulture == true))
+ {
+ // Not a valid mapping, try the hard coded table
+ string name;
+ if (RegionNames.TryGetValue(cultureName, out name))
+ {
+ // Make sure we can get culture data for it
+ retVal = GetCultureData(name, useUserOverride);
+ }
+ }
+
+ // If not found in the hard coded table we'll have to find a culture that works for us
+ if (!GlobalizationMode.Invariant && (retVal == null || (retVal.IsNeutralCulture == true)))
+ {
+ retVal = GetCultureDataFromRegionName(cultureName);
+ }
+
+ // If we found one we can use, then cache it for next time
+ if (retVal != null && (retVal.IsNeutralCulture == false))
+ {
+ // first add it to the cache
+ lock (s_lock)
+ {
+ tempHashTable[hashName] = retVal;
+ }
+
+ // Copy the hashtable to the corresponding member variables. This will potentially overwrite
+ // new tables simultaneously created by a new thread, but maximizes thread safety.
+ s_cachedRegions = tempHashTable;
+ }
+ else
+ {
+ // Unable to find a matching culture/region, return null or neutral
+ // (regionInfo throws a more specific exception on neutrals)
+ retVal = neutral;
+ }
+
+ // Return the found culture to use, null, or the neutral culture.
+ return retVal;
+ }
+
+ // Clear our internal caches
+ internal static void ClearCachedData()
+ {
+ s_cachedCultures = null;
+ s_cachedRegions = null;
+ }
+
+ internal static CultureInfo[] GetCultures(CultureTypes types)
+ {
+ // Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete
+#pragma warning disable 618
+ // Validate flags
+ if ((int)types <= 0 || ((int)types & (int)~(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures |
+ CultureTypes.InstalledWin32Cultures | CultureTypes.UserCustomCulture |
+ CultureTypes.ReplacementCultures | CultureTypes.WindowsOnlyCultures |
+ CultureTypes.FrameworkCultures)) != 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(types),
+ SR.Format(SR.ArgumentOutOfRange_Range, CultureTypes.NeutralCultures, CultureTypes.FrameworkCultures));
+ }
+
+ // We have deprecated CultureTypes.FrameworkCultures.
+ // When this enum is used, we will enumerate Whidbey framework cultures (for compatibility).
+
+ // We have deprecated CultureTypes.WindowsOnlyCultures.
+ // When this enum is used, we will return an empty array for this enum.
+ if ((types & CultureTypes.WindowsOnlyCultures) != 0)
+ {
+ // Remove the enum as it is an no-op.
+ types &= (~CultureTypes.WindowsOnlyCultures);
+ }
+
+ if (GlobalizationMode.Invariant)
+ {
+ // in invariant mode we always return invariant culture only from the enumeration
+ return new CultureInfo[1] { new CultureInfo("") };
+ }
+
+#pragma warning restore 618
+ return EnumCultures(types);
+ }
+
+ private static CultureData CreateCultureWithInvariantData()
+ {
+ // Make a new culturedata
+ CultureData invariant = new CultureData();
+
+ // Basics
+ // Note that we override the resources since this IS NOT supposed to change (by definition)
+ invariant._bUseOverrides = false;
+ invariant._sRealName = ""; // Name you passed in (ie: en-US, en, or de-DE_phoneb)
+ invariant._sWindowsName = ""; // Name OS thinks the object is (ie: de-DE_phoneb, or en-US (even if en was passed in))
+
+ // Identity
+ invariant._sName = ""; // locale name (ie: en-us)
+ invariant._sParent = ""; // Parent name (which may be a custom locale/culture)
+ invariant._bNeutral = false; // Flags for the culture (ie: neutral or not right now)
+ invariant._sEnglishDisplayName = "Invariant Language (Invariant Country)"; // English pretty name for this locale
+ invariant._sNativeDisplayName = "Invariant Language (Invariant Country)"; // Native pretty name for this locale
+ invariant._sSpecificCulture = ""; // The culture name to be used in CultureInfo.CreateSpecificCulture()
+
+ // Language
+ invariant._sISO639Language = "iv"; // ISO 639 Language Name
+ invariant._sISO639Language2 = "ivl"; // 3 char ISO 639 lang name 2
+ invariant._sLocalizedLanguage = "Invariant Language"; // Display name for this Language
+ invariant._sEnglishLanguage = "Invariant Language"; // English name for this language
+ invariant._sNativeLanguage = "Invariant Language"; // Native name of this language
+ invariant._sAbbrevLang = "IVL"; // abbreviated language name (Windows Language Name)
+ invariant._sConsoleFallbackName = ""; // The culture name for the console fallback UI culture
+ invariant._iInputLanguageHandle = 0x07F; // input language handle
+
+ // Region
+ invariant._sRegionName = "IV"; // (RegionInfo)
+ invariant._sEnglishCountry = "Invariant Country"; // english country name (RegionInfo)
+ invariant._sNativeCountry = "Invariant Country"; // native country name (Windows Only)
+ invariant._sISO3166CountryName = "IV"; // (RegionInfo), ie: US
+ invariant._sISO3166CountryName2 = "ivc"; // 3 char ISO 3166 country name 2 2(RegionInfo)
+ invariant._iGeoId = 244; // GeoId (Windows Only)
+
+ // Numbers
+ invariant._sPositiveSign = "+"; // positive sign
+ invariant._sNegativeSign = "-"; // negative sign
+ invariant._iDigits = 2; // number of fractional digits
+ invariant._iNegativeNumber = 1; // negative number format
+ invariant._waGrouping = new int[] { 3 }; // grouping of digits
+ invariant._sDecimalSeparator = "."; // decimal separator
+ invariant._sThousandSeparator = ","; // thousands separator
+ invariant._sNaN = "NaN"; // Not a Number
+ invariant._sPositiveInfinity = "Infinity"; // + Infinity
+ invariant._sNegativeInfinity = "-Infinity"; // - Infinity
+
+ // Percent
+ invariant._iNegativePercent = 0; // Negative Percent (0-3)
+ invariant._iPositivePercent = 0; // Positive Percent (0-11)
+ invariant._sPercent = "%"; // Percent (%) symbol
+ invariant._sPerMille = "\x2030"; // PerMille symbol
+
+ // Currency
+ invariant._sCurrency = "\x00a4"; // local monetary symbol: for international monetary symbol
+ invariant._sIntlMonetarySymbol = "XDR"; // international monetary symbol (RegionInfo)
+ invariant._sEnglishCurrency = "International Monetary Fund"; // English name for this currency (Windows Only)
+ invariant._sNativeCurrency = "International Monetary Fund"; // Native name for this currency (Windows Only)
+ invariant._iCurrencyDigits = 2; // # local monetary fractional digits
+ invariant._iCurrency = 0; // positive currency format
+ invariant._iNegativeCurrency = 0; // negative currency format
+ invariant._waMonetaryGrouping = new int[] { 3 }; // monetary grouping of digits
+ invariant._sMonetaryDecimal = "."; // monetary decimal separator
+ invariant._sMonetaryThousand = ","; // monetary thousands separator
+
+ // Misc
+ invariant._iMeasure = 0; // system of measurement 0=metric, 1=US (RegionInfo)
+ invariant._sListSeparator = ","; // list separator
+
+ // Time
+ invariant._sTimeSeparator = ":";
+ invariant._sAM1159 = "AM"; // AM designator
+ invariant._sPM2359 = "PM"; // PM designator
+ invariant._saLongTimes = new string[] { "HH:mm:ss" }; // time format
+ invariant._saShortTimes = new string[] { "HH:mm", "hh:mm tt", "H:mm", "h:mm tt" }; // short time format
+ invariant._saDurationFormats = new string[] { "HH:mm:ss" }; // time duration format
+
+
+ // Calendar specific data
+ invariant._iFirstDayOfWeek = 0; // first day of week
+ invariant._iFirstWeekOfYear = 0; // first week of year
+ invariant._waCalendars = new CalendarId[] { CalendarId.GREGORIAN }; // all available calendar type(s). The first one is the default calendar
+
+ // Store for specific data about each calendar
+ invariant._calendars = new CalendarData[CalendarData.MAX_CALENDARS];
+ invariant._calendars[0] = CalendarData.Invariant;
+
+ // Text information
+ invariant._iReadingLayout = 0;
+
+ // These are desktop only, not coreclr
+
+ invariant._iLanguage = CultureInfo.LOCALE_INVARIANT; // locale ID (0409) - NO sort information
+ invariant._iDefaultAnsiCodePage = 1252; // default ansi code page ID (ACP)
+ invariant._iDefaultOemCodePage = 437; // default oem code page ID (OCP or OEM)
+ invariant._iDefaultMacCodePage = 10000; // default macintosh code page
+ invariant._iDefaultEbcdicCodePage = 037; // default EBCDIC code page
+
+ if (GlobalizationMode.Invariant)
+ {
+ invariant._sLocalizedDisplayName = invariant._sNativeDisplayName;
+ invariant._sLocalizedCountry = invariant._sNativeCountry;
+ }
+
+ return invariant;
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+ // Build our invariant information
+ //
+ // We need an invariant instance, which we build hard-coded
+ /////////////////////////////////////////////////////////////////////////
+ internal static CultureData Invariant
+ {
+ get
+ {
+ if (s_Invariant == null)
+ {
+ // Remember it
+ s_Invariant = CreateCultureWithInvariantData();
+ }
+ return s_Invariant;
+ }
+ }
+ private volatile static CultureData s_Invariant;
+
+ ///////////////
+ // Constructors //
+ ///////////////
+ // Cache of cultures we've already looked up
+ private static volatile StringCultureDataDictionary s_cachedCultures;
+ private static readonly object s_lock = new object();
+
+ internal static CultureData GetCultureData(string cultureName, bool useUserOverride)
+ {
+ // First do a shortcut for Invariant
+ if (string.IsNullOrEmpty(cultureName))
+ {
+ return CultureData.Invariant;
+ }
+
+ // Try the hash table first
+ string hashName = AnsiToLower(useUserOverride ? cultureName : cultureName + '*');
+ StringCultureDataDictionary tempHashTable = s_cachedCultures;
+ if (tempHashTable == null)
+ {
+ // No table yet, make a new one
+ tempHashTable = new StringCultureDataDictionary();
+ }
+ else
+ {
+ // Check the hash table
+ bool ret;
+ CultureData retVal;
+ lock (s_lock)
+ {
+ ret = tempHashTable.TryGetValue(hashName, out retVal);
+ }
+ if (ret && retVal != null)
+ {
+ return retVal;
+ }
+ }
+
+ // Not found in the hash table, need to see if we can build one that works for us
+ CultureData culture = CreateCultureData(cultureName, useUserOverride);
+ if (culture == null)
+ {
+ return null;
+ }
+
+ // Found one, add it to the cache
+ lock (s_lock)
+ {
+ tempHashTable[hashName] = culture;
+ }
+
+ // Copy the hashtable to the corresponding member variables. This will potentially overwrite
+ // new tables simultaneously created by a new thread, but maximizes thread safety.
+ s_cachedCultures = tempHashTable;
+
+ return culture;
+ }
+
+ private static string NormalizeCultureName(string name, out bool isNeutralName)
+ {
+ isNeutralName = true;
+ int i = 0;
+
+ if (name.Length > LOCALE_NAME_MAX_LENGTH)
+ {
+ // Theoretically we shouldn't hit this exception.
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidId, nameof(name)));
+ }
+
+ Span<char> normalizedName = stackalloc char[name.Length];
+
+ bool changed = false;
+
+ while (i < name.Length && name[i] != '-' && name[i] != '_')
+ {
+ if (name[i] >= 'A' && name[i] <= 'Z')
+ {
+ // lowercase characters before '-'
+ normalizedName[i] = (char) (((int)name[i]) + 0x20);
+ changed = true;
+ }
+ else
+ {
+ normalizedName[i] = name[i];
+ }
+ i++;
+ }
+
+ if (i < name.Length)
+ {
+ // this is not perfect to detect the non neutral cultures but it is good enough when we are running in invariant mode
+ isNeutralName = false;
+ }
+
+ while (i < name.Length)
+ {
+ if (name[i] >= 'a' && name[i] <= 'z')
+ {
+ normalizedName[i] = (char) (((int)name[i]) - 0x20);
+ changed = true;
+ }
+ else
+ {
+ normalizedName[i] = name[i];
+ }
+ i++;
+ }
+
+ if (changed)
+ return new string(normalizedName);
+
+ return name;
+ }
+
+ private static CultureData CreateCultureData(string cultureName, bool useUserOverride)
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ if (cultureName.Length > LOCALE_NAME_MAX_LENGTH || !CultureInfo.VerifyCultureName(cultureName, false))
+ {
+ return null;
+ }
+ CultureData cd = CreateCultureWithInvariantData();
+ cd._bUseOverrides = useUserOverride;
+ cd._sName = NormalizeCultureName(cultureName, out cd._bNeutral);
+ cd._sRealName = cd._sName;
+ cd._sWindowsName = cd._sName;
+ cd._iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED;
+
+ return cd;
+ }
+
+ CultureData culture = new CultureData();
+ culture._bUseOverrides = useUserOverride;
+ culture._sRealName = cultureName;
+
+ // Ask native code if that one's real
+ if (culture.InitCultureData() == false)
+ {
+ if (culture.InitCompatibilityCultureData() == false)
+ {
+ return null;
+ }
+ }
+
+ return culture;
+ }
+
+ private bool InitCompatibilityCultureData()
+ {
+ // for compatibility handle the deprecated ids: zh-chs, zh-cht
+ string cultureName = _sRealName;
+
+ string fallbackCultureName;
+ string realCultureName;
+ switch (AnsiToLower(cultureName))
+ {
+ case "zh-chs":
+ fallbackCultureName = "zh-Hans";
+ realCultureName = "zh-CHS";
+ break;
+ case "zh-cht":
+ fallbackCultureName = "zh-Hant";
+ realCultureName = "zh-CHT";
+ break;
+ default:
+ return false;
+ }
+
+ _sRealName = fallbackCultureName;
+ if (InitCultureData() == false)
+ {
+ return false;
+ }
+ // fixup our data
+ _sName = realCultureName; // the name that goes back to the user
+ _sParent = fallbackCultureName;
+
+ return true;
+ }
+
+ // We'd rather people use the named version since this doesn't allow custom locales
+ internal static CultureData GetCultureData(int culture, bool bUseUserOverride)
+ {
+ string localeName = null;
+ CultureData retVal = null;
+
+ if (culture == CultureInfo.LOCALE_INVARIANT)
+ return Invariant;
+
+ if (GlobalizationMode.Invariant)
+ {
+ // LCID is not supported in the InvariantMode
+ throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
+ }
+
+ // Convert the lcid to a name, then use that
+ // Note that this will return neutral names (unlike Vista native API)
+ localeName = LCIDToLocaleName(culture);
+
+ if (!string.IsNullOrEmpty(localeName))
+ {
+ // Valid name, use it
+ retVal = GetCultureData(localeName, bUseUserOverride);
+ }
+
+ // If not successful, throw
+ if (retVal == null)
+ throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
+
+ // Return the one we found
+ return retVal;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // All the accessors
+ //
+ // Accessors for our data object items
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ ///////////
+ // Identity //
+ ///////////
+
+ // The real name used to construct the locale (ie: de-DE_phoneb)
+ internal string CultureName
+ {
+ get
+ {
+ Debug.Assert(_sRealName != null, "[CultureData.CultureName] Expected _sRealName to be populated by already");
+ // since windows doesn't know about zh-CHS and zh-CHT,
+ // we leave sRealName == zh-Hanx but we still need to
+ // pretend that it was zh-CHX.
+ switch (_sName)
+ {
+ case "zh-CHS":
+ case "zh-CHT":
+ return _sName;
+ }
+ return _sRealName;
+ }
+ }
+
+ // Are overrides enabled?
+ internal bool UseUserOverride
+ {
+ get
+ {
+ return _bUseOverrides;
+ }
+ }
+
+ // locale name (ie: de-DE, NO sort information)
+ internal string SNAME
+ {
+ get
+ {
+ if (_sName == null)
+ {
+ _sName = string.Empty;
+ }
+ return _sName;
+ }
+ }
+
+ // Parent name (which may be a custom locale/culture)
+ internal string SPARENT
+ {
+ get
+ {
+ if (_sParent == null)
+ {
+ // Ask using the real name, so that we get parents of neutrals
+ _sParent = GetLocaleInfo(_sRealName, LocaleStringData.ParentName);
+ }
+ return _sParent;
+ }
+ }
+
+ // Localized pretty name for this locale (ie: Inglis (estados Unitos))
+ internal string SLOCALIZEDDISPLAYNAME
+ {
+ get
+ {
+ if (_sLocalizedDisplayName == null)
+ {
+ if (this.IsSupplementalCustomCulture)
+ {
+ if (this.IsNeutralCulture)
+ {
+ _sLocalizedDisplayName = this.SNATIVELANGUAGE;
+ }
+ else
+ {
+ _sLocalizedDisplayName = this.SNATIVEDISPLAYNAME;
+ }
+ }
+ else
+ {
+ try
+ {
+ const string ZH_CHT = "zh-CHT";
+ const string ZH_CHS = "zh-CHS";
+
+ if (SNAME.Equals(ZH_CHT, StringComparison.OrdinalIgnoreCase))
+ {
+ _sLocalizedDisplayName = GetLanguageDisplayName("zh-Hant");
+ }
+ else if (SNAME.Equals(ZH_CHS, StringComparison.OrdinalIgnoreCase))
+ {
+ _sLocalizedDisplayName = GetLanguageDisplayName("zh-Hans");
+ }
+ else
+ {
+ _sLocalizedDisplayName = GetLanguageDisplayName(SNAME);
+ }
+ }
+ catch (Exception)
+ {
+ // do nothing
+ }
+ }
+ // If it hasn't been found (Windows 8 and up), fallback to the system
+ if (string.IsNullOrEmpty(_sLocalizedDisplayName))
+ {
+ // If its neutral use the language name
+ if (this.IsNeutralCulture)
+ {
+ _sLocalizedDisplayName = this.SLOCALIZEDLANGUAGE;
+ }
+ else
+ {
+ // Usually the UI culture shouldn't be different than what we got from WinRT except
+ // if DefaultThreadCurrentUICulture was set
+ CultureInfo ci;
+
+ if (CultureInfo.DefaultThreadCurrentUICulture != null &&
+ ((ci = GetUserDefaultCulture()) != null) &&
+ !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name))
+ {
+ _sLocalizedDisplayName = this.SNATIVEDISPLAYNAME;
+ }
+ else
+ {
+ _sLocalizedDisplayName = GetLocaleInfo(LocaleStringData.LocalizedDisplayName);
+ }
+ }
+ }
+ }
+
+ return _sLocalizedDisplayName;
+ }
+ }
+
+ // English pretty name for this locale (ie: English (United States))
+ internal string SENGDISPLAYNAME
+ {
+ get
+ {
+ if (_sEnglishDisplayName == null)
+ {
+ // If its neutral use the language name
+ if (this.IsNeutralCulture)
+ {
+ _sEnglishDisplayName = this.SENGLISHLANGUAGE;
+ // differentiate the legacy display names
+ switch (_sName)
+ {
+ case "zh-CHS":
+ case "zh-CHT":
+ _sEnglishDisplayName += " Legacy";
+ break;
+ }
+ }
+ else
+ {
+ _sEnglishDisplayName = GetLocaleInfo(LocaleStringData.EnglishDisplayName);
+
+ // if it isn't found build one:
+ if (string.IsNullOrEmpty(_sEnglishDisplayName))
+ {
+ // Our existing names mostly look like:
+ // "English" + "United States" -> "English (United States)"
+ // "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
+ if (this.SENGLISHLANGUAGE[this.SENGLISHLANGUAGE.Length - 1] == ')')
+ {
+ // "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
+ _sEnglishDisplayName =
+ this.SENGLISHLANGUAGE.Substring(0, _sEnglishLanguage.Length - 1) +
+ ", " + this.SENGCOUNTRY + ")";
+ }
+ else
+ {
+ // "English" + "United States" -> "English (United States)"
+ _sEnglishDisplayName = this.SENGLISHLANGUAGE + " (" + this.SENGCOUNTRY + ")";
+ }
+ }
+ }
+ }
+ return _sEnglishDisplayName;
+ }
+ }
+
+ // Native pretty name for this locale (ie: Deutsch (Deutschland))
+ internal string SNATIVEDISPLAYNAME
+ {
+ get
+ {
+ if (_sNativeDisplayName == null)
+ {
+ // If its neutral use the language name
+ if (this.IsNeutralCulture)
+ {
+ _sNativeDisplayName = this.SNATIVELANGUAGE;
+ // differentiate the legacy display names
+ switch (_sName)
+ {
+ case "zh-CHS":
+ _sNativeDisplayName += " \u65E7\u7248";
+ break;
+ case "zh-CHT":
+ _sNativeDisplayName += " \u820A\u7248";
+ break;
+ }
+ }
+ else
+ {
+ _sNativeDisplayName = GetLocaleInfo(LocaleStringData.NativeDisplayName);
+
+ // if it isn't found build one:
+ if (string.IsNullOrEmpty(_sNativeDisplayName))
+ {
+ // These should primarily be "Deutsch (Deutschland)" type names
+ _sNativeDisplayName = this.SNATIVELANGUAGE + " (" + this.SNATIVECOUNTRY + ")";
+ }
+ }
+ }
+ return _sNativeDisplayName;
+ }
+ }
+
+ // The culture name to be used in CultureInfo.CreateSpecificCulture()
+ internal string SSPECIFICCULTURE
+ {
+ get
+ {
+ // This got populated during the culture initialization
+ Debug.Assert(_sSpecificCulture != null, "[CultureData.SSPECIFICCULTURE] Expected this.sSpecificCulture to be populated by culture data initialization already");
+ return _sSpecificCulture;
+ }
+ }
+
+ /////////////
+ // Language //
+ /////////////
+
+ // iso 639 language name, ie: en
+ internal string SISO639LANGNAME
+ {
+ get
+ {
+ if (_sISO639Language == null)
+ {
+ _sISO639Language = GetLocaleInfo(LocaleStringData.Iso639LanguageTwoLetterName);
+ }
+ return _sISO639Language;
+ }
+ }
+
+ // iso 639 language name, ie: eng
+ internal string SISO639LANGNAME2
+ {
+ get
+ {
+ if (_sISO639Language2 == null)
+ {
+ _sISO639Language2 = GetLocaleInfo(LocaleStringData.Iso639LanguageThreeLetterName);
+ }
+ return _sISO639Language2;
+ }
+ }
+
+ // abbreviated windows language name (ie: enu) (non-standard, avoid this)
+ internal string SABBREVLANGNAME
+ {
+ get
+ {
+ if (_sAbbrevLang == null)
+ {
+ _sAbbrevLang = GetThreeLetterWindowsLanguageName(_sRealName);
+ }
+ return _sAbbrevLang;
+ }
+ }
+
+ // Localized name for this language (Windows Only) ie: Inglis
+ // This is only valid for Windows 8 and higher neutrals:
+ internal string SLOCALIZEDLANGUAGE
+ {
+ get
+ {
+ if (_sLocalizedLanguage == null)
+ {
+ // Usually the UI culture shouldn't be different than what we got from WinRT except
+ // if DefaultThreadCurrentUICulture was set
+ CultureInfo ci;
+
+ if (CultureInfo.DefaultThreadCurrentUICulture != null &&
+ ((ci = GetUserDefaultCulture()) != null) &&
+ !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name))
+ {
+ _sLocalizedLanguage = SNATIVELANGUAGE;
+ }
+ else
+ {
+ _sLocalizedLanguage = GetLocaleInfo(LocaleStringData.LocalizedLanguageName);
+ }
+ }
+
+ return _sLocalizedLanguage;
+ }
+ }
+
+ // English name for this language (Windows Only) ie: German
+ internal string SENGLISHLANGUAGE
+ {
+ get
+ {
+ if (_sEnglishLanguage == null)
+ {
+ _sEnglishLanguage = GetLocaleInfo(LocaleStringData.EnglishLanguageName);
+ }
+ return _sEnglishLanguage;
+ }
+ }
+
+ // Native name of this language (Windows Only) ie: Deutsch
+ internal string SNATIVELANGUAGE
+ {
+ get
+ {
+ if (_sNativeLanguage == null)
+ {
+ _sNativeLanguage = GetLocaleInfo(LocaleStringData.NativeLanguageName);
+ }
+ return _sNativeLanguage;
+ }
+ }
+
+ ///////////
+ // Region //
+ ///////////
+
+ // region name (eg US)
+ internal string SREGIONNAME
+ {
+ get
+ {
+ if (_sRegionName == null)
+ {
+ _sRegionName = GetLocaleInfo(LocaleStringData.Iso3166CountryName);
+ }
+ return _sRegionName;
+ }
+ }
+
+ internal int IGEOID
+ {
+ get
+ {
+ if (_iGeoId == undef)
+ {
+ _iGeoId = GetGeoId(_sRealName);
+ }
+ return _iGeoId;
+ }
+ }
+
+ // localized name for the country
+ internal string SLOCALIZEDCOUNTRY
+ {
+ get
+ {
+ if (_sLocalizedCountry == null)
+ {
+ try
+ {
+ _sLocalizedCountry = GetRegionDisplayName(SISO3166CTRYNAME);
+ }
+ catch (Exception)
+ {
+ // do nothing. we'll fallback
+ }
+
+ if (_sLocalizedCountry == null)
+ {
+ _sLocalizedCountry = SNATIVECOUNTRY;
+ }
+ }
+ return _sLocalizedCountry;
+ }
+ }
+
+ // english country name (RegionInfo) ie: Germany
+ internal string SENGCOUNTRY
+ {
+ get
+ {
+ if (_sEnglishCountry == null)
+ {
+ _sEnglishCountry = GetLocaleInfo(LocaleStringData.EnglishCountryName);
+ }
+ return _sEnglishCountry;
+ }
+ }
+
+ // native country name (RegionInfo) ie: Deutschland
+ internal string SNATIVECOUNTRY
+ {
+ get
+ {
+ if (_sNativeCountry == null)
+ {
+ _sNativeCountry = GetLocaleInfo(LocaleStringData.NativeCountryName);
+ }
+ return _sNativeCountry;
+ }
+ }
+
+ // ISO 3166 Country Name
+ internal string SISO3166CTRYNAME
+ {
+ get
+ {
+ if (_sISO3166CountryName == null)
+ {
+ _sISO3166CountryName = GetLocaleInfo(LocaleStringData.Iso3166CountryName);
+ }
+ return _sISO3166CountryName;
+ }
+ }
+
+ // 3 letter ISO 3166 country code
+ internal string SISO3166CTRYNAME2
+ {
+ get
+ {
+ if (_sISO3166CountryName2 == null)
+ {
+ _sISO3166CountryName2 = GetLocaleInfo(LocaleStringData.Iso3166CountryName2);
+ }
+ return _sISO3166CountryName2;
+ }
+ }
+
+ internal int IINPUTLANGUAGEHANDLE
+ {
+ get
+ {
+ if (_iInputLanguageHandle == undef)
+ {
+ if (IsSupplementalCustomCulture)
+ {
+ _iInputLanguageHandle = 0x0409;
+ }
+ else
+ {
+ // Input Language is same as LCID for built-in cultures
+ _iInputLanguageHandle = this.ILANGUAGE;
+ }
+ }
+ return _iInputLanguageHandle;
+ }
+ }
+
+ // Console fallback name (ie: locale to use for console apps for unicode-only locales)
+ internal string SCONSOLEFALLBACKNAME
+ {
+ get
+ {
+ if (_sConsoleFallbackName == null)
+ {
+ _sConsoleFallbackName = GetConsoleFallbackName(_sRealName);
+ }
+ return _sConsoleFallbackName;
+ }
+ }
+
+ // (user can override) grouping of digits
+ internal int[] WAGROUPING
+ {
+ get
+ {
+ if (_waGrouping == null)
+ {
+ _waGrouping = GetLocaleInfo(LocaleGroupingData.Digit);
+ }
+ return _waGrouping;
+ }
+ }
+
+
+ // internal string _sDecimalSeparator ; // (user can override) decimal separator
+ // internal string _sThousandSeparator ; // (user can override) thousands separator
+
+ // Not a Number
+ internal string SNAN
+ {
+ get
+ {
+ if (_sNaN == null)
+ {
+ _sNaN = GetLocaleInfo(LocaleStringData.NaNSymbol);
+ }
+ return _sNaN;
+ }
+ }
+
+ // + Infinity
+ internal string SPOSINFINITY
+ {
+ get
+ {
+ if (_sPositiveInfinity == null)
+ {
+ _sPositiveInfinity = GetLocaleInfo(LocaleStringData.PositiveInfinitySymbol);
+ }
+ return _sPositiveInfinity;
+ }
+ }
+
+ // - Infinity
+ internal string SNEGINFINITY
+ {
+ get
+ {
+ if (_sNegativeInfinity == null)
+ {
+ _sNegativeInfinity = GetLocaleInfo(LocaleStringData.NegativeInfinitySymbol);
+ }
+ return _sNegativeInfinity;
+ }
+ }
+
+
+ ////////////
+ // Percent //
+ ///////////
+
+ // Negative Percent (0-3)
+ internal int INEGATIVEPERCENT
+ {
+ get
+ {
+ if (_iNegativePercent == undef)
+ {
+ // Note that <= Windows Vista this is synthesized by native code
+ _iNegativePercent = GetLocaleInfo(LocaleNumberData.NegativePercentFormat);
+ }
+ return _iNegativePercent;
+ }
+ }
+
+ // Positive Percent (0-11)
+ internal int IPOSITIVEPERCENT
+ {
+ get
+ {
+ if (_iPositivePercent == undef)
+ {
+ // Note that <= Windows Vista this is synthesized by native code
+ _iPositivePercent = GetLocaleInfo(LocaleNumberData.PositivePercentFormat);
+ }
+ return _iPositivePercent;
+ }
+ }
+
+ // Percent (%) symbol
+ internal string SPERCENT
+ {
+ get
+ {
+ if (_sPercent == null)
+ {
+ _sPercent = GetLocaleInfo(LocaleStringData.PercentSymbol);
+ }
+ return _sPercent;
+ }
+ }
+
+ // PerMille symbol
+ internal string SPERMILLE
+ {
+ get
+ {
+ if (_sPerMille == null)
+ {
+ _sPerMille = GetLocaleInfo(LocaleStringData.PerMilleSymbol);
+ }
+ return _sPerMille;
+ }
+ }
+
+ /////////////
+ // Currency //
+ /////////////
+
+ // (user can override) local monetary symbol, eg: $
+ internal string SCURRENCY
+ {
+ get
+ {
+ if (_sCurrency == null)
+ {
+ _sCurrency = GetLocaleInfo(LocaleStringData.MonetarySymbol);
+ }
+ return _sCurrency;
+ }
+ }
+
+ // international monetary symbol (RegionInfo), eg: USD
+ internal string SINTLSYMBOL
+ {
+ get
+ {
+ if (_sIntlMonetarySymbol == null)
+ {
+ _sIntlMonetarySymbol = GetLocaleInfo(LocaleStringData.Iso4217MonetarySymbol);
+ }
+ return _sIntlMonetarySymbol;
+ }
+ }
+
+ // English name for this currency (RegionInfo), eg: US Dollar
+ internal string SENGLISHCURRENCY
+ {
+ get
+ {
+ if (_sEnglishCurrency == null)
+ {
+ _sEnglishCurrency = GetLocaleInfo(LocaleStringData.CurrencyEnglishName);
+ }
+ return _sEnglishCurrency;
+ }
+ }
+
+ // Native name for this currency (RegionInfo), eg: Schweiz Frank
+ internal string SNATIVECURRENCY
+ {
+ get
+ {
+ if (_sNativeCurrency == null)
+ {
+ _sNativeCurrency = GetLocaleInfo(LocaleStringData.CurrencyNativeName);
+ }
+ return _sNativeCurrency;
+ }
+ }
+
+ // internal int iCurrencyDigits ; // (user can override) # local monetary fractional digits
+ // internal int iCurrency ; // (user can override) positive currency format
+ // internal int iNegativeCurrency ; // (user can override) negative currency format
+
+ // (user can override) monetary grouping of digits
+ internal int[] WAMONGROUPING
+ {
+ get
+ {
+ if (_waMonetaryGrouping == null)
+ {
+ _waMonetaryGrouping = GetLocaleInfo(LocaleGroupingData.Monetary);
+ }
+ return _waMonetaryGrouping;
+ }
+ }
+
+ // (user can override) system of measurement 0=metric, 1=US (RegionInfo)
+ internal int IMEASURE
+ {
+ get
+ {
+ if (_iMeasure == undef)
+ {
+ _iMeasure = GetLocaleInfo(LocaleNumberData.MeasurementSystem);
+ }
+ return _iMeasure;
+ }
+ }
+
+ // (user can override) list Separator
+ internal string SLIST
+ {
+ get
+ {
+ if (_sListSeparator == null)
+ {
+ _sListSeparator = GetLocaleInfo(LocaleStringData.ListSeparator);
+ }
+ return _sListSeparator;
+ }
+ }
+
+
+ ////////////////////////////
+ // Calendar/Time (Gregorian) //
+ ////////////////////////////
+
+ // (user can override) AM designator
+ internal string SAM1159
+ {
+ get
+ {
+ if (_sAM1159 == null)
+ {
+ _sAM1159 = GetLocaleInfo(LocaleStringData.AMDesignator);
+ }
+ return _sAM1159;
+ }
+ }
+
+ // (user can override) PM designator
+ internal string SPM2359
+ {
+ get
+ {
+ if (_sPM2359 == null)
+ {
+ _sPM2359 = GetLocaleInfo(LocaleStringData.PMDesignator);
+ }
+ return _sPM2359;
+ }
+ }
+
+ // (user can override) time format
+ internal string[] LongTimes
+ {
+ get
+ {
+ if (_saLongTimes == null)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ string[] longTimes = GetTimeFormats();
+ if (longTimes == null || longTimes.Length == 0)
+ {
+ _saLongTimes = Invariant._saLongTimes;
+ }
+ else
+ {
+ _saLongTimes = longTimes;
+ }
+ }
+ return _saLongTimes;
+ }
+ }
+
+ // short time format
+ // Short times (derived from long times format)
+ // TODO: NLS Arrowhead - On Windows 7 we should have short times so this isn't necessary
+ internal string[] ShortTimes
+ {
+ get
+ {
+ if (_saShortTimes == null)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ // Try to get the short times from the OS/culture.dll
+ string[] shortTimes = null;
+ shortTimes = GetShortTimeFormats();
+
+ if (shortTimes == null || shortTimes.Length == 0)
+ {
+ //
+ // If we couldn't find short times, then compute them from long times
+ // (eg: CORECLR on < Win7 OS & fallback for missing culture.dll)
+ //
+ shortTimes = DeriveShortTimesFromLong();
+ }
+
+ /* The above logic doesn't make sense on Mac, since the OS can provide us a "short time pattern".
+ * currently this is the 4th element in the array returned by LongTimes. We'll add this to our array
+ * if it doesn't exist.
+ */
+ shortTimes = AdjustShortTimesForMac(shortTimes);
+
+ // Found short times, use them
+ _saShortTimes = shortTimes;
+ }
+ return _saShortTimes;
+ }
+ }
+
+ private string[] AdjustShortTimesForMac(string[] shortTimes)
+ {
+ return shortTimes;
+ }
+
+ private string[] DeriveShortTimesFromLong()
+ {
+ // Our logic is to look for h,H,m,s,t. If we find an s, then we check the string
+ // between it and the previous marker, if any. If its a short, unescaped separator,
+ // then we don't retain that part.
+ // We then check after the ss and remove anything before the next h,H,m,t...
+ string[] longTimes = LongTimes;
+ string[] shortTimes = new string[longTimes.Length];
+
+ for (int i = 0; i < longTimes.Length; i++)
+ {
+ shortTimes[i] = StripSecondsFromPattern(longTimes[i]);
+ }
+ return shortTimes;
+ }
+
+ private static string StripSecondsFromPattern(string time)
+ {
+ bool bEscape = false;
+ int iLastToken = -1;
+
+ // Find the seconds
+ for (int j = 0; j < time.Length; j++)
+ {
+ // Change escape mode?
+ if (time[j] == '\'')
+ {
+ // Continue
+ bEscape = !bEscape;
+ continue;
+ }
+
+ // See if there was a single \
+ if (time[j] == '\\')
+ {
+ // Skip next char
+ j++;
+ continue;
+ }
+
+ if (bEscape)
+ {
+ continue;
+ }
+
+ switch (time[j])
+ {
+ // Check for seconds
+ case 's':
+ // Found seconds, see if there was something unescaped and short between
+ // the last marker and the seconds. Windows says separator can be a
+ // maximum of three characters (without null)
+ // If 1st or last characters were ', then ignore it
+ if ((j - iLastToken) <= 4 && (j - iLastToken) > 1 &&
+ (time[iLastToken + 1] != '\'') &&
+ (time[j - 1] != '\''))
+ {
+ // There was something there we want to remember
+ if (iLastToken >= 0)
+ {
+ j = iLastToken + 1;
+ }
+ }
+
+ bool containsSpace;
+ int endIndex = GetIndexOfNextTokenAfterSeconds(time, j, out containsSpace);
+
+ string sep;
+
+ if (containsSpace)
+ {
+ sep = " ";
+ }
+ else
+ {
+ sep = "";
+ }
+
+ time = time.Substring(0, j) + sep + time.Substring(endIndex);
+ break;
+ case 'm':
+ case 'H':
+ case 'h':
+ iLastToken = j;
+ break;
+ }
+ }
+ return time;
+ }
+
+ private static int GetIndexOfNextTokenAfterSeconds(string time, int index, out bool containsSpace)
+ {
+ bool bEscape = false;
+ containsSpace = false;
+ for (; index < time.Length; index++)
+ {
+ switch (time[index])
+ {
+ case '\'':
+ bEscape = !bEscape;
+ continue;
+ case '\\':
+ index++;
+ if (time[index] == ' ')
+ {
+ containsSpace = true;
+ }
+ continue;
+ case ' ':
+ containsSpace = true;
+ break;
+ case 't':
+ case 'm':
+ case 'H':
+ case 'h':
+ if (bEscape)
+ {
+ continue;
+ }
+ return index;
+ }
+ }
+ containsSpace = false;
+ return index;
+ }
+
+ // (user can override) first day of week
+ internal int IFIRSTDAYOFWEEK
+ {
+ get
+ {
+ if (_iFirstDayOfWeek == undef)
+ {
+ _iFirstDayOfWeek = GetFirstDayOfWeek();
+ }
+ return _iFirstDayOfWeek;
+ }
+ }
+
+ // (user can override) first week of year
+ internal int IFIRSTWEEKOFYEAR
+ {
+ get
+ {
+ if (_iFirstWeekOfYear == undef)
+ {
+ _iFirstWeekOfYear = GetLocaleInfo(LocaleNumberData.FirstWeekOfYear);
+ }
+ return _iFirstWeekOfYear;
+ }
+ }
+
+ // (user can override default only) short date format
+ internal string[] ShortDates(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saShortDates;
+ }
+
+ // (user can override default only) long date format
+ internal string[] LongDates(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saLongDates;
+ }
+
+ // (user can override) date year/month format.
+ internal string[] YearMonths(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saYearMonths;
+ }
+
+ // day names
+ internal string[] DayNames(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saDayNames;
+ }
+
+ // abbreviated day names
+ internal string[] AbbreviatedDayNames(CalendarId calendarId)
+ {
+ // Get abbreviated day names for this calendar from the OS if necessary
+ return GetCalendar(calendarId).saAbbrevDayNames;
+ }
+
+ // The super short day names
+ internal string[] SuperShortDayNames(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saSuperShortDayNames;
+ }
+
+ // month names
+ internal string[] MonthNames(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saMonthNames;
+ }
+
+ // Genitive month names
+ internal string[] GenitiveMonthNames(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saMonthGenitiveNames;
+ }
+
+ // month names
+ internal string[] AbbreviatedMonthNames(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saAbbrevMonthNames;
+ }
+
+ // Genitive month names
+ internal string[] AbbreviatedGenitiveMonthNames(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
+ }
+
+ // Leap year month names
+ // Note: This only applies to Hebrew, and it basically adds a "1" to the 6th month name
+ // the non-leap names skip the 7th name in the normal month name array
+ internal string[] LeapYearMonthNames(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).saLeapYearMonthNames;
+ }
+
+ // month/day format (single string, no override)
+ internal string MonthDay(CalendarId calendarId)
+ {
+ return GetCalendar(calendarId).sMonthDay;
+ }
+
+
+
+ /////////////
+ // Calendars //
+ /////////////
+
+ // all available calendar type(s), The first one is the default calendar.
+ internal CalendarId[] CalendarIds
+ {
+ get
+ {
+ if (_waCalendars == null)
+ {
+ // We pass in an array of ints, and native side fills it up with count calendars.
+ // We then have to copy that list to a new array of the right size.
+ // Default calendar should be first
+ CalendarId[] calendars = new CalendarId[23];
+ Debug.Assert(_sWindowsName != null, "[CultureData.CalendarIds] Expected _sWindowsName to be populated by already");
+ int count = CalendarData.GetCalendars(_sWindowsName, _bUseOverrides, calendars);
+
+ // See if we had a calendar to add.
+ if (count == 0)
+ {
+ // Failed for some reason, just grab Gregorian from Invariant
+ _waCalendars = Invariant._waCalendars;
+ }
+ else
+ {
+ // The OS may not return calendar 4 for zh-TW, but we've always allowed it.
+ // TODO: Is this hack necessary long-term?
+ if (_sWindowsName == "zh-TW")
+ {
+ bool found = false;
+
+ // Do we need to insert calendar 4?
+ for (int i = 0; i < count; i++)
+ {
+ // Stop if we found calendar four
+ if (calendars[i] == CalendarId.TAIWAN)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ // If not found then insert it
+ if (!found)
+ {
+ // Insert it as the 2nd calendar
+ count++;
+ // Copy them from the 2nd position to the end, -1 for skipping 1st, -1 for one being added.
+ Array.Copy(calendars, 1, calendars, 2, 23 - 1 - 1);
+ calendars[1] = CalendarId.TAIWAN;
+ }
+ }
+
+ // It worked, remember the list
+ CalendarId[] temp = new CalendarId[count];
+ Array.Copy(calendars, temp, count);
+
+ // Want 1st calendar to be default
+ // Prior to Vista the enumeration didn't have default calendar first
+ if (temp.Length > 1)
+ {
+ CalendarId i = (CalendarId)GetLocaleInfo(LocaleNumberData.CalendarType);
+ if (temp[1] == i)
+ {
+ temp[1] = temp[0];
+ temp[0] = i;
+ }
+ }
+
+ _waCalendars = temp;
+ }
+ }
+
+ return _waCalendars;
+ }
+ }
+
+ // Native calendar names. index of optional calendar - 1, empty if no optional calendar at that number
+ internal string CalendarName(CalendarId calendarId)
+ {
+ // Get the calendar
+ return GetCalendar(calendarId).sNativeName;
+ }
+
+ internal CalendarData GetCalendar(CalendarId calendarId)
+ {
+ Debug.Assert(calendarId > 0 && calendarId <= CalendarId.LAST_CALENDAR,
+ "[CultureData.GetCalendar] Expect calendarId to be in a valid range");
+
+ // arrays are 0 based, calendarIds are 1 based
+ int calendarIndex = (int)calendarId - 1;
+
+ // Have to have calendars
+ if (_calendars == null)
+ {
+ _calendars = new CalendarData[CalendarData.MAX_CALENDARS];
+ }
+
+ // we need the following local variable to avoid returning null
+ // when another thread creates a new array of CalendarData (above)
+ // right after we insert the newly created CalendarData (below)
+ CalendarData calendarData = _calendars[calendarIndex];
+ // Make sure that calendar has data
+ if (calendarData == null)
+ {
+ Debug.Assert(_sWindowsName != null, "[CultureData.GetCalendar] Expected _sWindowsName to be populated by already");
+ calendarData = new CalendarData(_sWindowsName, calendarId, this.UseUserOverride);
+ _calendars[calendarIndex] = calendarData;
+ }
+
+ return calendarData;
+ }
+
+ ///////////////////
+ // Text Information //
+ ///////////////////
+
+ // IsRightToLeft
+ internal bool IsRightToLeft
+ {
+ get
+ {
+ // Returns one of the following 4 reading layout values:
+ // 0 - Left to right (eg en-US)
+ // 1 - Right to left (eg arabic locales)
+ // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
+ // 3 - Vertical top to bottom with columns proceeding to the right
+ return (this.IREADINGLAYOUT == 1);
+ }
+ }
+
+ // IREADINGLAYOUT
+ // Returns one of the following 4 reading layout values:
+ // 0 - Left to right (eg en-US)
+ // 1 - Right to left (eg arabic locales)
+ // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
+ // 3 - Vertical top to bottom with columns proceeding to the right
+ //
+ // If exposed as a public API, we'd have an enum with those 4 values
+ private int IREADINGLAYOUT
+ {
+ get
+ {
+ if (_iReadingLayout == undef)
+ {
+ Debug.Assert(_sRealName != null, "[CultureData.IsRightToLeft] Expected _sRealName to be populated by already");
+ _iReadingLayout = GetLocaleInfo(LocaleNumberData.ReadingLayout);
+ }
+
+ return (_iReadingLayout);
+ }
+ }
+
+ // The TextInfo name never includes that alternate sort and is always specific
+ // For customs, it uses the SortLocale (since the textinfo is not exposed in Win7)
+ // en -> en-US
+ // en-US -> en-US
+ // fj (custom neutral) -> en-US (assuming that en-US is the sort locale for fj)
+ // fj_FJ (custom specific) -> en-US (assuming that en-US is the sort locale for fj-FJ)
+ // es-ES_tradnl -> es-ES
+ internal string STEXTINFO // Text info name to use for text information
+ {
+ get
+ {
+ // Note: Custom cultures might point at another culture's textinfo, however windows knows how
+ // to redirect it to the desired textinfo culture, so this is OK.
+ Debug.Assert(_sRealName != null, "[CultureData.STEXTINFO] Expected _sRealName to be populated by already");
+ return (_sRealName);
+ }
+ }
+
+ // Compare info name (including sorting key) to use if custom
+ internal string SCOMPAREINFO
+ {
+ get
+ {
+ Debug.Assert(_sRealName != null, "[CultureData.SCOMPAREINFO] Expected _sRealName to be populated by already");
+ return (_sRealName);
+ }
+ }
+
+ internal bool IsSupplementalCustomCulture
+ {
+ get
+ {
+ return IsCustomCultureId(this.ILANGUAGE);
+ }
+ }
+
+ internal int IDEFAULTANSICODEPAGE // default ansi code page ID (ACP)
+ {
+ get
+ {
+ if (_iDefaultAnsiCodePage == undef)
+ {
+ _iDefaultAnsiCodePage = GetAnsiCodePage(_sRealName);
+ }
+ return _iDefaultAnsiCodePage;
+ }
+ }
+
+ internal int IDEFAULTOEMCODEPAGE // default oem code page ID (OCP or OEM)
+ {
+ get
+ {
+ if (_iDefaultOemCodePage == undef)
+ {
+ _iDefaultOemCodePage = GetOemCodePage(_sRealName);
+ }
+ return _iDefaultOemCodePage;
+ }
+ }
+
+ internal int IDEFAULTMACCODEPAGE // default macintosh code page
+ {
+ get
+ {
+ if (_iDefaultMacCodePage == undef)
+ {
+ _iDefaultMacCodePage = GetMacCodePage(_sRealName);
+ }
+ return _iDefaultMacCodePage;
+ }
+ }
+
+ internal int IDEFAULTEBCDICCODEPAGE // default EBCDIC code page
+ {
+ get
+ {
+ if (_iDefaultEbcdicCodePage == undef)
+ {
+ _iDefaultEbcdicCodePage = GetEbcdicCodePage(_sRealName);
+ }
+ return _iDefaultEbcdicCodePage;
+ }
+ }
+
+ internal int ILANGUAGE
+ {
+ get
+ {
+ if (_iLanguage == 0)
+ {
+ Debug.Assert(_sRealName != null, "[CultureData.ILANGUAGE] Expected this.sRealName to be populated already");
+ _iLanguage = LocaleNameToLCID(_sRealName);
+ }
+ return _iLanguage;
+ }
+ }
+
+ internal bool IsNeutralCulture
+ {
+ get
+ {
+ // InitCultureData told us if we're neutral or not
+ return _bNeutral;
+ }
+ }
+
+ internal bool IsInvariantCulture
+ {
+ get
+ {
+ return string.IsNullOrEmpty(this.SNAME);
+ }
+ }
+
+ // Get an instance of our default calendar
+ internal Calendar DefaultCalendar
+ {
+ get
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ return CultureInfo.GetCalendarInstance(CalendarIds[0]);
+ }
+
+ CalendarId defaultCalId = (CalendarId)GetLocaleInfo(LocaleNumberData.CalendarType);
+
+ if (defaultCalId == 0)
+ {
+ defaultCalId = this.CalendarIds[0];
+ }
+
+ return CultureInfo.GetCalendarInstance(defaultCalId);
+ }
+ }
+
+ // All of our era names
+ internal string[] EraNames(CalendarId calendarId)
+ {
+ Debug.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
+
+ return this.GetCalendar(calendarId).saEraNames;
+ }
+
+ internal string[] AbbrevEraNames(CalendarId calendarId)
+ {
+ Debug.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
+
+ return this.GetCalendar(calendarId).saAbbrevEraNames;
+ }
+
+ internal string[] AbbreviatedEnglishEraNames(CalendarId calendarId)
+ {
+ Debug.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
+
+ return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
+ }
+
+ //// string array DEFAULTS
+ //// Note: GetDTFIOverrideValues does the user overrides for these, so we don't have to.
+
+
+ // Time separator (derived from time format)
+ internal string TimeSeparator
+ {
+ get
+ {
+ if (_sTimeSeparator == null)
+ {
+ string longTimeFormat = GetTimeFormatString();
+ if (string.IsNullOrEmpty(longTimeFormat))
+ {
+ longTimeFormat = LongTimes[0];
+ }
+
+ // Compute STIME from time format
+ _sTimeSeparator = GetTimeSeparator(longTimeFormat);
+ }
+ return _sTimeSeparator;
+ }
+ }
+
+ // Date separator (derived from short date format)
+ internal string DateSeparator(CalendarId calendarId)
+ {
+ return GetDateSeparator(ShortDates(calendarId)[0]);
+ }
+
+ //////////////////////////////////////
+ // Helper Functions to get derived properties //
+ //////////////////////////////////////
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Unescape a NLS style quote string
+ //
+ // This removes single quotes:
+ // 'fred' -> fred
+ // 'fred -> fred
+ // fred' -> fred
+ // fred's -> freds
+ //
+ // This removes the first \ of escaped characters:
+ // fred\'s -> fred's
+ // a\\b -> a\b
+ // a\b -> ab
+ //
+ // We don't build the stringbuilder unless we find a ' or a \. If we find a ' or a \, we
+ // always build a stringbuilder because we need to remove the ' or \.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private static string UnescapeNlsString(string str, int start, int end)
+ {
+ Debug.Assert(str != null);
+ Debug.Assert(start >= 0);
+ Debug.Assert(end >= 0);
+ StringBuilder result = null;
+
+ for (int i = start; i < str.Length && i <= end; i++)
+ {
+ switch (str[i])
+ {
+ case '\'':
+ if (result == null)
+ {
+ result = new StringBuilder(str, start, i - start, str.Length);
+ }
+ break;
+ case '\\':
+ if (result == null)
+ {
+ result = new StringBuilder(str, start, i - start, str.Length);
+ }
+ ++i;
+ if (i < str.Length)
+ {
+ result.Append(str[i]);
+ }
+ break;
+ default:
+ if (result != null)
+ {
+ result.Append(str[i]);
+ }
+ break;
+ }
+ }
+
+ if (result == null)
+ return (str.Substring(start, end - start + 1));
+
+ return (result.ToString());
+ }
+
+ private static string GetTimeSeparator(string format)
+ {
+ // Time format separator (ie: : in 12:39:00)
+ //
+ // We calculate this from the provided time format
+ //
+
+ //
+ // Find the time separator so that we can pretend we know STIME.
+ //
+ return GetSeparator(format, "Hhms");
+ }
+
+ private static string GetDateSeparator(string format)
+ {
+ // Date format separator (ie: / in 9/1/03)
+ //
+ // We calculate this from the provided short date
+ //
+
+ //
+ // Find the date separator so that we can pretend we know SDATE.
+ //
+ return GetSeparator(format, "dyM");
+ }
+
+ private static string GetSeparator(string format, string timeParts)
+ {
+ int index = IndexOfTimePart(format, 0, timeParts);
+
+ if (index != -1)
+ {
+ // Found a time part, find out when it changes
+ char cTimePart = format[index];
+
+ do
+ {
+ index++;
+ } while (index < format.Length && format[index] == cTimePart);
+
+ int separatorStart = index;
+
+ // Now we need to find the end of the separator
+ if (separatorStart < format.Length)
+ {
+ int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
+ if (separatorEnd != -1)
+ {
+ // From [separatorStart, count) is our string, except we need to unescape
+ return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
+ }
+ }
+ }
+
+ return string.Empty;
+ }
+
+ private static int IndexOfTimePart(string format, int startIndex, string timeParts)
+ {
+ Debug.Assert(startIndex >= 0, "startIndex cannot be negative");
+ Debug.Assert(timeParts.IndexOfAny(new char[] { '\'', '\\' }) == -1, "timeParts cannot include quote characters");
+ bool inQuote = false;
+ for (int i = startIndex; i < format.Length; ++i)
+ {
+ // See if we have a time Part
+ if (!inQuote && timeParts.IndexOf(format[i]) != -1)
+ {
+ return i;
+ }
+ switch (format[i])
+ {
+ case '\\':
+ if (i + 1 < format.Length)
+ {
+ ++i;
+ switch (format[i])
+ {
+ case '\'':
+ case '\\':
+ break;
+ default:
+ --i; //backup since we will move over this next
+ break;
+ }
+ }
+ break;
+ case '\'':
+ inQuote = !inQuote;
+ break;
+ }
+ }
+
+ return -1;
+ }
+
+ internal static bool IsCustomCultureId(int cultureId)
+ {
+ return (cultureId == CultureInfo.LOCALE_CUSTOM_DEFAULT || cultureId == CultureInfo.LOCALE_CUSTOM_UNSPECIFIED);
+ }
+
+ internal void GetNFIValues(NumberFormatInfo nfi)
+ {
+ if (GlobalizationMode.Invariant || this.IsInvariantCulture)
+ {
+ // FUTURE: NumberFormatInfo already has default values for many of these fields. Can we not do this?
+ nfi.positiveSign = _sPositiveSign;
+ nfi.negativeSign = _sNegativeSign;
+
+ nfi.numberGroupSeparator = _sThousandSeparator;
+ nfi.numberDecimalSeparator = _sDecimalSeparator;
+ nfi.numberDecimalDigits = _iDigits;
+ nfi.numberNegativePattern = _iNegativeNumber;
+
+ nfi.currencySymbol = _sCurrency;
+ nfi.currencyGroupSeparator = _sMonetaryThousand;
+ nfi.currencyDecimalSeparator = _sMonetaryDecimal;
+ nfi.currencyDecimalDigits = _iCurrencyDigits;
+ nfi.currencyNegativePattern = _iNegativeCurrency;
+ nfi.currencyPositivePattern = _iCurrency;
+ }
+ else
+ {
+ Debug.Assert(_sWindowsName != null, "[CultureData.GetNFIValues] Expected _sWindowsName to be populated by already");
+ // String values
+ nfi.positiveSign = GetLocaleInfo(LocaleStringData.PositiveSign);
+ nfi.negativeSign = GetLocaleInfo(LocaleStringData.NegativeSign);
+
+ nfi.numberDecimalSeparator = GetLocaleInfo(LocaleStringData.DecimalSeparator);
+ nfi.numberGroupSeparator = GetLocaleInfo(LocaleStringData.ThousandSeparator);
+ nfi.currencyGroupSeparator = GetLocaleInfo(LocaleStringData.MonetaryThousandSeparator);
+ nfi.currencyDecimalSeparator = GetLocaleInfo(LocaleStringData.MonetaryDecimalSeparator);
+ nfi.currencySymbol = GetLocaleInfo(LocaleStringData.MonetarySymbol);
+
+ // Numeric values
+ nfi.numberDecimalDigits = GetLocaleInfo(LocaleNumberData.FractionalDigitsCount);
+ nfi.currencyDecimalDigits = GetLocaleInfo(LocaleNumberData.MonetaryFractionalDigitsCount);
+ nfi.currencyPositivePattern = GetLocaleInfo(LocaleNumberData.PositiveMonetaryNumberFormat);
+ nfi.currencyNegativePattern = GetLocaleInfo(LocaleNumberData.NegativeMonetaryNumberFormat);
+ nfi.numberNegativePattern = GetLocaleInfo(LocaleNumberData.NegativeNumberFormat);
+
+ // LOCALE_SNATIVEDIGITS (array of 10 single character strings).
+ string digits = GetLocaleInfo(LocaleStringData.Digits);
+ nfi.nativeDigits = new string[10];
+ for (int i = 0; i < nfi.nativeDigits.Length; i++)
+ {
+ nfi.nativeDigits[i] = new string(digits[i], 1);
+ }
+
+ nfi.digitSubstitution = GetDigitSubstitution(_sRealName);
+ }
+
+ //
+ // Gather additional data
+ //
+ nfi.numberGroupSizes = this.WAGROUPING;
+ nfi.currencyGroupSizes = this.WAMONGROUPING;
+
+ // prefer the cached value since these do not have user overrides
+ nfi.percentNegativePattern = this.INEGATIVEPERCENT;
+ nfi.percentPositivePattern = this.IPOSITIVEPERCENT;
+ nfi.percentSymbol = this.SPERCENT;
+ nfi.perMilleSymbol = this.SPERMILLE;
+
+ nfi.negativeInfinitySymbol = this.SNEGINFINITY;
+ nfi.positiveInfinitySymbol = this.SPOSINFINITY;
+ nfi.nanSymbol = this.SNAN;
+
+ //
+ // We don't have percent values, so use the number values
+ //
+ nfi.percentDecimalDigits = nfi.numberDecimalDigits;
+ nfi.percentDecimalSeparator = nfi.numberDecimalSeparator;
+ nfi.percentGroupSizes = nfi.numberGroupSizes;
+ nfi.percentGroupSeparator = nfi.numberGroupSeparator;
+
+ //
+ // Clean up a few odd values
+ //
+
+ // Windows usually returns an empty positive sign, but we like it to be "+"
+ if (nfi.positiveSign == null || nfi.positiveSign.Length == 0) nfi.positiveSign = "+";
+
+ //Special case for Italian. The currency decimal separator in the control panel is the empty string. When the user
+ //specifies C4 as the currency format, this results in the number apparently getting multiplied by 10000 because the
+ //decimal point doesn't show up. We'll just hack this here because our default currency format will never use nfi.
+ if (nfi.currencyDecimalSeparator == null || nfi.currencyDecimalSeparator.Length == 0)
+ {
+ nfi.currencyDecimalSeparator = nfi.numberDecimalSeparator;
+ }
+ }
+
+ // Helper
+ // This is ONLY used for caching names and shouldn't be used for anything else
+ internal static string AnsiToLower(string testString)
+ {
+ int index = 0;
+
+ while (index<testString.Length && (testString[index]<'A' || testString[index]>'Z' ))
+ {
+ index++;
+ }
+ if (index >= testString.Length)
+ {
+ return testString; // we didn't really change the string
+ }
+
+ StringBuilder sb = new StringBuilder(testString.Length);
+ for (int i=0; i<index; i++)
+ {
+ sb.Append(testString[i]);
+ }
+
+ sb.Append((char) (testString[index] -'A' + 'a'));
+
+ for (int ich = index+1; ich < testString.Length; ich++)
+ {
+ char ch = testString[ich];
+ sb.Append(ch <= 'Z' && ch >= 'A' ? (char)(ch - 'A' + 'a') : ch);
+ }
+
+ return (sb.ToString());
+ }
+
+ /// <remarks>
+ /// The numeric values of the enum members match their Win32 counterparts. The CultureData Win32 PAL implementation
+ /// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
+ /// </remarks>
+ private enum LocaleStringData : uint
+ {
+ /// <summary>localized name of locale, eg "German (Germany)" in UI language (corresponds to LOCALE_SLOCALIZEDDISPLAYNAME)</summary>
+ LocalizedDisplayName = 0x00000002,
+ /// <summary>Display name (language + country usually) in English, eg "German (Germany)" (corresponds to LOCALE_SENGLISHDISPLAYNAME)</summary>
+ EnglishDisplayName = 0x00000072,
+ /// <summary>Display name in native locale language, eg "Deutsch (Deutschland) (corresponds to LOCALE_SNATIVEDISPLAYNAME)</summary>
+ NativeDisplayName = 0x00000073,
+ /// <summary>Language Display Name for a language, eg "German" in UI language (corresponds to LOCALE_SLOCALIZEDLANGUAGENAME)</summary>
+ LocalizedLanguageName = 0x0000006f,
+ /// <summary>English name of language, eg "German" (corresponds to LOCALE_SENGLISHLANGUAGENAME)</summary>
+ EnglishLanguageName = 0x00001001,
+ /// <summary>native name of language, eg "Deutsch" (corresponds to LOCALE_SNATIVELANGUAGENAME)</summary>
+ NativeLanguageName = 0x00000004,
+ /// <summary>localized name of country, eg "Germany" in UI language (corresponds to LOCALE_SLOCALIZEDCOUNTRYNAME)</summary>
+ LocalizedCountryName = 0x00000006,
+ /// <summary>English name of country, eg "Germany" (corresponds to LOCALE_SENGLISHCOUNTRYNAME)</summary>
+ EnglishCountryName = 0x00001002,
+ /// <summary>native name of country, eg "Deutschland" (corresponds to LOCALE_SNATIVECOUNTRYNAME)</summary>
+ NativeCountryName = 0x00000008,
+ /// <summary>abbreviated language name (corresponds to LOCALE_SABBREVLANGNAME)</summary>
+ AbbreviatedWindowsLanguageName = 0x00000003,
+ /// <summary>list item separator (corresponds to LOCALE_SLIST)</summary>
+ ListSeparator = 0x0000000C,
+ /// <summary>decimal separator (corresponds to LOCALE_SDECIMAL)</summary>
+ DecimalSeparator = 0x0000000E,
+ /// <summary>thousand separator (corresponds to LOCALE_STHOUSAND)</summary>
+ ThousandSeparator = 0x0000000F,
+ /// <summary>digit grouping (corresponds to LOCALE_SGROUPING)</summary>
+ Digits = 0x00000013,
+ /// <summary>local monetary symbol (corresponds to LOCALE_SCURRENCY)</summary>
+ MonetarySymbol = 0x00000014,
+ /// <summary>English currency name (corresponds to LOCALE_SENGCURRNAME)</summary>
+ CurrencyEnglishName = 0x00001007,
+ /// <summary>Native currency name (corresponds to LOCALE_SNATIVECURRNAME)</summary>
+ CurrencyNativeName = 0x00001008,
+ /// <summary>uintl monetary symbol (corresponds to LOCALE_SINTLSYMBOL)</summary>
+ Iso4217MonetarySymbol = 0x00000015,
+ /// <summary>monetary decimal separator (corresponds to LOCALE_SMONDECIMALSEP)</summary>
+ MonetaryDecimalSeparator = 0x00000016,
+ /// <summary>monetary thousand separator (corresponds to LOCALE_SMONTHOUSANDSEP)</summary>
+ MonetaryThousandSeparator = 0x00000017,
+ /// <summary>AM designator (corresponds to LOCALE_S1159)</summary>
+ AMDesignator = 0x00000028,
+ /// <summary>PM designator (corresponds to LOCALE_S2359)</summary>
+ PMDesignator = 0x00000029,
+ /// <summary>positive sign (corresponds to LOCALE_SPOSITIVESIGN)</summary>
+ PositiveSign = 0x00000050,
+ /// <summary>negative sign (corresponds to LOCALE_SNEGATIVESIGN)</summary>
+ NegativeSign = 0x00000051,
+ /// <summary>ISO abbreviated language name (corresponds to LOCALE_SISO639LANGNAME)</summary>
+ Iso639LanguageTwoLetterName = 0x00000059,
+ /// <summary>ISO abbreviated country name (corresponds to LOCALE_SISO639LANGNAME2)</summary>
+ Iso639LanguageThreeLetterName = 0x00000067,
+ /// <summary>ISO abbreviated language name (corresponds to LOCALE_SISO639LANGNAME)</summary>
+ Iso639LanguageName = 0x00000059,
+ /// <summary>ISO abbreviated country name (corresponds to LOCALE_SISO3166CTRYNAME)</summary>
+ Iso3166CountryName = 0x0000005A,
+ /// <summary>3 letter ISO country code (corresponds to LOCALE_SISO3166CTRYNAME2)</summary>
+ Iso3166CountryName2 = 0x00000068, // 3 character ISO country name
+ /// <summary>Not a Number (corresponds to LOCALE_SNAN)</summary>
+ NaNSymbol = 0x00000069,
+ /// <summary>+ Infinity (corresponds to LOCALE_SPOSINFINITY)</summary>
+ PositiveInfinitySymbol = 0x0000006a,
+ /// <summary>- Infinity (corresponds to LOCALE_SNEGINFINITY)</summary>
+ NegativeInfinitySymbol = 0x0000006b,
+ /// <summary>Fallback name for resources (corresponds to LOCALE_SPARENT)</summary>
+ ParentName = 0x0000006d,
+ /// <summary>Fallback name for within the console (corresponds to LOCALE_SCONSOLEFALLBACKNAME)</summary>
+ ConsoleFallbackName = 0x0000006e,
+ /// <summary>Returns the percent symbol (corresponds to LOCALE_SPERCENT)</summary>
+ PercentSymbol = 0x00000076,
+ /// <summary>Returns the permille (U+2030) symbol (corresponds to LOCALE_SPERMILLE)</summary>
+ PerMilleSymbol = 0x00000077
+ }
+
+ /// <remarks>
+ /// The numeric values of the enum members match their Win32 counterparts. The CultureData Win32 PAL implementation
+ /// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
+ /// </remarks>
+ private enum LocaleGroupingData : uint
+ {
+ /// <summary>digit grouping (corresponds to LOCALE_SGROUPING)</summary>
+ Digit = 0x00000010,
+ /// <summary>monetary grouping (corresponds to LOCALE_SMONGROUPING)</summary>
+ Monetary = 0x00000018,
+ }
+
+ /// <remarks>
+ /// The numeric values of the enum members match their Win32 counterparts. The CultureData Win32 PAL implementation
+ /// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
+ /// </remarks>
+ private enum LocaleNumberData : uint
+ {
+ /// <summary>language id (corresponds to LOCALE_ILANGUAGE)</summary>
+ LanguageId = 0x00000001,
+ /// <summary>geographical location id, (corresponds to LOCALE_IGEOID)</summary>
+ GeoId = 0x0000005B,
+ /// <summary>0 = context, 1 = none, 2 = national (corresponds to LOCALE_IDIGITSUBSTITUTION)</summary>
+ DigitSubstitution = 0x00001014,
+ /// <summary>0 = metric, 1 = US (corresponds to LOCALE_IMEASURE)</summary>
+ MeasurementSystem = 0x0000000D,
+ /// <summary>number of fractional digits (corresponds to LOCALE_IDIGITS)</summary>
+ FractionalDigitsCount = 0x00000011,
+ /// <summary>negative number mode (corresponds to LOCALE_INEGNUMBER)</summary>
+ NegativeNumberFormat = 0x00001010,
+ /// <summary># local monetary digits (corresponds to LOCALE_ICURRDIGITS)</summary>
+ MonetaryFractionalDigitsCount = 0x00000019,
+ /// <summary>positive currency mode (corresponds to LOCALE_ICURRENCY)</summary>
+ PositiveMonetaryNumberFormat = 0x0000001B,
+ /// <summary>negative currency mode (corresponds to LOCALE_INEGCURR)</summary>
+ NegativeMonetaryNumberFormat = 0x0000001C,
+ /// <summary>type of calendar specifier (corresponds to LOCALE_ICALENDARTYPE)</summary>
+ CalendarType = 0x00001009,
+ /// <summary>first day of week specifier (corresponds to LOCALE_IFIRSTDAYOFWEEK)</summary>
+ FirstDayOfWeek = 0x0000100C,
+ /// <summary>first week of year specifier (corresponds to LOCALE_IFIRSTWEEKOFYEAR)</summary>
+ FirstWeekOfYear = 0x0000100D,
+ /// <summary>
+ /// Returns one of the following 4 reading layout values:
+ /// 0 - Left to right (eg en-US)
+ /// 1 - Right to left (eg arabic locales)
+ /// 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
+ /// 3 - Vertical top to bottom with columns proceeding to the right
+ /// (corresponds to LOCALE_IREADINGLAYOUT)
+ /// </summary>
+ ReadingLayout = 0x00000070,
+ /// <summary>Returns 0-11 for the negative percent format (corresponds to LOCALE_INEGATIVEPERCENT)</summary>
+ NegativePercentFormat = 0x00000074,
+ /// <summary>Returns 0-3 for the positive percent format (corresponds to LOCALE_IPOSITIVEPERCENT)</summary>
+ PositivePercentFormat = 0x00000075,
+ /// <summary>default ansi code page (corresponds to LOCALE_IDEFAULTCODEPAGE)</summary>
+ OemCodePage = 0x0000000B,
+ /// <summary>default ansi code page (corresponds to LOCALE_IDEFAULTANSICODEPAGE)</summary>
+ AnsiCodePage = 0x00001004,
+ /// <summary>default mac code page (corresponds to LOCALE_IDEFAULTMACCODEPAGE)</summary>
+ MacCodePage = 0x00001011,
+ /// <summary>default ebcdic code page (corresponds to LOCALE_IDEFAULTEBCDICCODEPAGE)</summary>
+ EbcdicCodePage = 0x00001012,
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CultureNotFoundException.cs b/src/System.Private.CoreLib/shared/System/Globalization/CultureNotFoundException.cs
new file mode 100644
index 0000000000..10e8b1f836
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CultureNotFoundException.cs
@@ -0,0 +1,121 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Globalization
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class CultureNotFoundException : ArgumentException
+ {
+ private string _invalidCultureName; // unrecognized culture name
+ private int? _invalidCultureId; // unrecognized culture Lcid
+
+ public CultureNotFoundException()
+ : base(DefaultMessage)
+ {
+ }
+
+ public CultureNotFoundException(String message)
+ : base(message)
+ {
+ }
+
+ public CultureNotFoundException(String paramName, String message)
+ : base(message, paramName)
+ {
+ }
+
+ public CultureNotFoundException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ public CultureNotFoundException(String paramName, string invalidCultureName, String message)
+ : base(message, paramName)
+ {
+ _invalidCultureName = invalidCultureName;
+ }
+
+ public CultureNotFoundException(String message, string invalidCultureName, Exception innerException)
+ : base(message, innerException)
+ {
+ _invalidCultureName = invalidCultureName;
+ }
+
+ public CultureNotFoundException(string message, int invalidCultureId, Exception innerException)
+ : base(message, innerException)
+ {
+ _invalidCultureId = invalidCultureId;
+ }
+
+ public CultureNotFoundException(string paramName, int invalidCultureId, string message)
+ : base(message, paramName)
+ {
+ _invalidCultureId = invalidCultureId;
+ }
+
+ protected CultureNotFoundException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ _invalidCultureId = (int?)info.GetValue("InvalidCultureId", typeof(int?));
+ _invalidCultureName = (string)info.GetValue("InvalidCultureName", typeof(string));
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("InvalidCultureId", _invalidCultureId, typeof(int?));
+ info.AddValue("InvalidCultureName", _invalidCultureName, typeof(string));
+ }
+
+ public virtual Nullable<int> InvalidCultureId
+ {
+ get { return _invalidCultureId; }
+ }
+
+ public virtual string InvalidCultureName
+ {
+ get { return _invalidCultureName; }
+ }
+
+ private static String DefaultMessage
+ {
+ get
+ {
+ return SR.Argument_CultureNotSupported;
+ }
+ }
+
+ private String FormatedInvalidCultureId
+ {
+ get
+ {
+ return InvalidCultureId != null ?
+ String.Format(CultureInfo.InvariantCulture, "{0} (0x{0:x4})", (int)InvalidCultureId) :
+ InvalidCultureName;
+ }
+ }
+
+ public override String Message
+ {
+ get
+ {
+ String s = base.Message;
+ if (_invalidCultureId != null || _invalidCultureName != null)
+ {
+ String valueMessage = SR.Format(SR.Argument_CultureInvalidIdentifier, FormatedInvalidCultureId);
+ if (s == null)
+ {
+ return valueMessage;
+ }
+
+ return s + Environment.NewLine + valueMessage;
+ }
+ return s;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CultureTypes.cs b/src/System.Private.CoreLib/shared/System/Globalization/CultureTypes.cs
new file mode 100644
index 0000000000..f52ac82a83
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/CultureTypes.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// The enumeration constants used in CultureInfo.GetCultures().
+// On Linux platforms, the only enum values used there is NeutralCultures and SpecificCultures
+// the rest are obsolete or not valid on Linux
+
+namespace System.Globalization
+{
+ [Flags]
+ public enum CultureTypes
+ {
+ NeutralCultures = 0x0001, // Neutral cultures are cultures like "en", "de", "zh", etc, for enumeration this includes ALL neutrals regardless of other flags
+ SpecificCultures = 0x0002, // Non-netural cultuers. Examples are "en-us", "zh-tw", etc., for enumeration this includes ALL specifics regardless of other flags
+ InstalledWin32Cultures = 0x0004, // Win32 installed cultures in the system and exists in the framework too., this is effectively all cultures
+
+ AllCultures = NeutralCultures | SpecificCultures | InstalledWin32Cultures,
+
+ UserCustomCulture = 0x0008, // User defined custom culture
+ ReplacementCultures = 0x0010, // User defined replacement custom culture.
+ [Obsolete("This value has been deprecated. Please use other values in CultureTypes.")]
+ WindowsOnlyCultures = 0x0020, // this will always return empty list.
+ [Obsolete("This value has been deprecated. Please use other values in CultureTypes.")]
+ FrameworkCultures = 0x0040, // will return only the v2 cultures marked as Framework culture.
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs
new file mode 100644
index 0000000000..092ad0365d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs
@@ -0,0 +1,1393 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ /*
+ Customized format patterns:
+ P.S. Format in the table below is the internal number format used to display the pattern.
+
+ Patterns Format Description Example
+ ========= ========== ===================================== ========
+ "h" "0" hour (12-hour clock)w/o leading zero 3
+ "hh" "00" hour (12-hour clock)with leading zero 03
+ "hh*" "00" hour (12-hour clock)with leading zero 03
+
+ "H" "0" hour (24-hour clock)w/o leading zero 8
+ "HH" "00" hour (24-hour clock)with leading zero 08
+ "HH*" "00" hour (24-hour clock) 08
+
+ "m" "0" minute w/o leading zero
+ "mm" "00" minute with leading zero
+ "mm*" "00" minute with leading zero
+
+ "s" "0" second w/o leading zero
+ "ss" "00" second with leading zero
+ "ss*" "00" second with leading zero
+
+ "f" "0" second fraction (1 digit)
+ "ff" "00" second fraction (2 digit)
+ "fff" "000" second fraction (3 digit)
+ "ffff" "0000" second fraction (4 digit)
+ "fffff" "00000" second fraction (5 digit)
+ "ffffff" "000000" second fraction (6 digit)
+ "fffffff" "0000000" second fraction (7 digit)
+
+ "F" "0" second fraction (up to 1 digit)
+ "FF" "00" second fraction (up to 2 digit)
+ "FFF" "000" second fraction (up to 3 digit)
+ "FFFF" "0000" second fraction (up to 4 digit)
+ "FFFFF" "00000" second fraction (up to 5 digit)
+ "FFFFFF" "000000" second fraction (up to 6 digit)
+ "FFFFFFF" "0000000" second fraction (up to 7 digit)
+
+ "t" first character of AM/PM designator A
+ "tt" AM/PM designator AM
+ "tt*" AM/PM designator PM
+
+ "d" "0" day w/o leading zero 1
+ "dd" "00" day with leading zero 01
+ "ddd" short weekday name (abbreviation) Mon
+ "dddd" full weekday name Monday
+ "dddd*" full weekday name Monday
+
+
+ "M" "0" month w/o leading zero 2
+ "MM" "00" month with leading zero 02
+ "MMM" short month name (abbreviation) Feb
+ "MMMM" full month name Febuary
+ "MMMM*" full month name Febuary
+
+ "y" "0" two digit year (year % 100) w/o leading zero 0
+ "yy" "00" two digit year (year % 100) with leading zero 00
+ "yyy" "D3" year 2000
+ "yyyy" "D4" year 2000
+ "yyyyy" "D5" year 2000
+ ...
+
+ "z" "+0;-0" timezone offset w/o leading zero -8
+ "zz" "+00;-00" timezone offset with leading zero -08
+ "zzz" "+00;-00" for hour offset, "00" for minute offset full timezone offset -07:30
+ "zzz*" "+00;-00" for hour offset, "00" for minute offset full timezone offset -08:00
+
+ "K" -Local "zzz", e.g. -08:00
+ -Utc "'Z'", representing UTC
+ -Unspecified ""
+ -DateTimeOffset "zzzzz" e.g -07:30:15
+
+ "g*" the current era name A.D.
+
+ ":" time separator : -- DEPRECATED - Insert separator directly into pattern (eg: "H.mm.ss")
+ "/" date separator /-- DEPRECATED - Insert separator directly into pattern (eg: "M-dd-yyyy")
+ "'" quoted string 'ABC' will insert ABC into the formatted string.
+ '"' quoted string "ABC" will insert ABC into the formatted string.
+ "%" used to quote a single pattern characters E.g.The format character "%y" is to print two digit year.
+ "\" escaped character E.g. '\d' insert the character 'd' into the format string.
+ other characters insert the character into the format string.
+
+ Pre-defined format characters:
+ (U) to indicate Universal time is used.
+ (G) to indicate Gregorian calendar is used.
+
+ Format Description Real format Example
+ ========= ================================= ====================== =======================
+ "d" short date culture-specific 10/31/1999
+ "D" long data culture-specific Sunday, October 31, 1999
+ "f" full date (long date + short time) culture-specific Sunday, October 31, 1999 2:00 AM
+ "F" full date (long date + long time) culture-specific Sunday, October 31, 1999 2:00:00 AM
+ "g" general date (short date + short time) culture-specific 10/31/1999 2:00 AM
+ "G" general date (short date + long time) culture-specific 10/31/1999 2:00:00 AM
+ "m"/"M" Month/Day date culture-specific October 31
+(G) "o"/"O" Round Trip XML "yyyy-MM-ddTHH:mm:ss.fffffffK" 1999-10-31 02:00:00.0000000Z
+(G) "r"/"R" RFC 1123 date, "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'" Sun, 31 Oct 1999 10:00:00 GMT
+(G) "s" Sortable format, based on ISO 8601. "yyyy-MM-dd'T'HH:mm:ss" 1999-10-31T02:00:00
+ ('T' for local time)
+ "t" short time culture-specific 2:00 AM
+ "T" long time culture-specific 2:00:00 AM
+(G) "u" Universal time with sortable format, "yyyy'-'MM'-'dd HH':'mm':'ss'Z'" 1999-10-31 10:00:00Z
+ based on ISO 8601.
+(U) "U" Universal time with full culture-specific Sunday, October 31, 1999 10:00:00 AM
+ (long date + long time) format
+ "y"/"Y" Year/Month day culture-specific October, 1999
+
+ */
+
+ //This class contains only static members and does not require the serializable attribute.
+ internal static
+ class DateTimeFormat
+ {
+ internal const int MaxSecondsFractionDigits = 7;
+ internal static readonly TimeSpan NullOffset = TimeSpan.MinValue;
+
+ internal static char[] allStandardFormats =
+ {
+ 'd', 'D', 'f', 'F', 'g', 'G',
+ 'm', 'M', 'o', 'O', 'r', 'R',
+ 's', 't', 'T', 'u', 'U', 'y', 'Y',
+ };
+
+ internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
+ internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz";
+
+ private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
+
+ internal static readonly DateTimeFormatInfo InvariantFormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
+ internal static readonly string[] InvariantAbbreviatedMonthNames = InvariantFormatInfo.AbbreviatedMonthNames;
+ internal static readonly string[] InvariantAbbreviatedDayNames = InvariantFormatInfo.AbbreviatedDayNames;
+ internal const string Gmt = "GMT";
+
+ internal static String[] fixedNumberFormats = new String[] {
+ "0",
+ "00",
+ "000",
+ "0000",
+ "00000",
+ "000000",
+ "0000000",
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Format the positive integer value to a string and perfix with assigned
+ // length of leading zero.
+ //
+ // Parameters:
+ // value: The value to format
+ // len: The maximum length for leading zero.
+ // If the digits of the value is greater than len, no leading zero is added.
+ //
+ // Notes:
+ // The function can format to Int32.MaxValue.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static void FormatDigits(StringBuilder outputBuffer, int value, int len)
+ {
+ Debug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0");
+ FormatDigits(outputBuffer, value, len, false);
+ }
+
+ internal static unsafe void FormatDigits(StringBuilder outputBuffer, int value, int len, bool overrideLengthLimit)
+ {
+ Debug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0");
+
+ // Limit the use of this function to be two-digits, so that we have the same behavior
+ // as RTM bits.
+ if (!overrideLengthLimit && len > 2)
+ {
+ len = 2;
+ }
+
+ char* buffer = stackalloc char[16];
+ char* p = buffer + 16;
+ int n = value;
+ do
+ {
+ *--p = (char)(n % 10 + '0');
+ n /= 10;
+ } while ((n != 0) && (p > buffer));
+
+ int digits = (int)(buffer + 16 - p);
+
+ //If the repeat count is greater than 0, we're trying
+ //to emulate the "00" format, so we have to prepend
+ //a zero if the string only has one character.
+ while ((digits < len) && (p > buffer))
+ {
+ *--p = '0';
+ digits++;
+ }
+ outputBuffer.Append(p, digits);
+ }
+
+ private static void HebrewFormatDigits(StringBuilder outputBuffer, int digits)
+ {
+ outputBuffer.Append(HebrewNumber.ToString(digits));
+ }
+
+ internal static int ParseRepeatPattern(ReadOnlySpan<char> format, int pos, char patternChar)
+ {
+ int len = format.Length;
+ int index = pos + 1;
+ while ((index < len) && (format[index] == patternChar))
+ {
+ index++;
+ }
+ return (index - pos);
+ }
+
+ private static String FormatDayOfWeek(int dayOfWeek, int repeat, DateTimeFormatInfo dtfi)
+ {
+ Debug.Assert(dayOfWeek >= 0 && dayOfWeek <= 6, "dayOfWeek >= 0 && dayOfWeek <= 6");
+ if (repeat == 3)
+ {
+ return (dtfi.GetAbbreviatedDayName((DayOfWeek)dayOfWeek));
+ }
+ // Call dtfi.GetDayName() here, instead of accessing DayNames property, because we don't
+ // want a clone of DayNames, which will hurt perf.
+ return (dtfi.GetDayName((DayOfWeek)dayOfWeek));
+ }
+
+ private static String FormatMonth(int month, int repeatCount, DateTimeFormatInfo dtfi)
+ {
+ Debug.Assert(month >= 1 && month <= 12, "month >=1 && month <= 12");
+ if (repeatCount == 3)
+ {
+ return (dtfi.GetAbbreviatedMonthName(month));
+ }
+ // Call GetMonthName() here, instead of accessing MonthNames property, because we don't
+ // want a clone of MonthNames, which will hurt perf.
+ return (dtfi.GetMonthName(month));
+ }
+
+ //
+ // FormatHebrewMonthName
+ //
+ // Action: Return the Hebrew month name for the specified DateTime.
+ // Returns: The month name string for the specified DateTime.
+ // Arguments:
+ // time the time to format
+ // month The month is the value of HebrewCalendar.GetMonth(time).
+ // repeat Return abbreviated month name if repeat=3, or full month name if repeat=4
+ // dtfi The DateTimeFormatInfo which uses the Hebrew calendars as its calendar.
+ // Exceptions: None.
+ //
+
+ /* Note:
+ If DTFI is using Hebrew calendar, GetMonthName()/GetAbbreviatedMonthName() will return month names like this:
+ 1 Hebrew 1st Month
+ 2 Hebrew 2nd Month
+ .. ...
+ 6 Hebrew 6th Month
+ 7 Hebrew 6th Month II (used only in a leap year)
+ 8 Hebrew 7th Month
+ 9 Hebrew 8th Month
+ 10 Hebrew 9th Month
+ 11 Hebrew 10th Month
+ 12 Hebrew 11th Month
+ 13 Hebrew 12th Month
+
+ Therefore, if we are in a regular year, we have to increment the month name if moth is greater or eqaul to 7.
+ */
+ private static String FormatHebrewMonthName(DateTime time, int month, int repeatCount, DateTimeFormatInfo dtfi)
+ {
+ Debug.Assert(repeatCount != 3 || repeatCount != 4, "repeateCount should be 3 or 4");
+ if (dtfi.Calendar.IsLeapYear(dtfi.Calendar.GetYear(time)))
+ {
+ // This month is in a leap year
+ return (dtfi.internalGetMonthName(month, MonthNameStyles.LeapYear, (repeatCount == 3)));
+ }
+ // This is in a regular year.
+ if (month >= 7)
+ {
+ month++;
+ }
+ if (repeatCount == 3)
+ {
+ return (dtfi.GetAbbreviatedMonthName(month));
+ }
+ return (dtfi.GetMonthName(month));
+ }
+
+ //
+ // The pos should point to a quote character. This method will
+ // append to the result StringBuilder the string encloed by the quote character.
+ //
+ internal static int ParseQuoteString(ReadOnlySpan<char> format, int pos, StringBuilder result)
+ {
+ //
+ // NOTE : pos will be the index of the quote character in the 'format' string.
+ //
+ int formatLen = format.Length;
+ int beginPos = pos;
+ char quoteChar = format[pos++]; // Get the character used to quote the following string.
+
+ bool foundQuote = false;
+ while (pos < formatLen)
+ {
+ char ch = format[pos++];
+ if (ch == quoteChar)
+ {
+ foundQuote = true;
+ break;
+ }
+ else if (ch == '\\')
+ {
+ // The following are used to support escaped character.
+ // Escaped character is also supported in the quoted string.
+ // Therefore, someone can use a format like "'minute:' mm\"" to display:
+ // minute: 45"
+ // because the second double quote is escaped.
+ if (pos < formatLen)
+ {
+ result.Append(format[pos++]);
+ }
+ else
+ {
+ //
+ // This means that '\' is at the end of the formatting string.
+ //
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ }
+ else
+ {
+ result.Append(ch);
+ }
+ }
+
+ if (!foundQuote)
+ {
+ // Here we can't find the matching quote.
+ throw new FormatException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.Format_BadQuote, quoteChar));
+ }
+
+ //
+ // Return the character count including the begin/end quote characters and enclosed string.
+ //
+ return (pos - beginPos);
+ }
+
+ //
+ // Get the next character at the index of 'pos' in the 'format' string.
+ // Return value of -1 means 'pos' is already at the end of the 'format' string.
+ // Otherwise, return value is the int value of the next character.
+ //
+ internal static int ParseNextChar(ReadOnlySpan<char> format, int pos)
+ {
+ if (pos >= format.Length - 1)
+ {
+ return (-1);
+ }
+ return ((int)format[pos + 1]);
+ }
+
+ //
+ // IsUseGenitiveForm
+ //
+ // Actions: Check the format to see if we should use genitive month in the formatting.
+ // Starting at the position (index) in the (format) string, look back and look ahead to
+ // see if there is "d" or "dd". In the case like "d MMMM" or "MMMM dd", we can use
+ // genitive form. Genitive form is not used if there is more than two "d".
+ // Arguments:
+ // format The format string to be scanned.
+ // index Where we should start the scanning. This is generally where "M" starts.
+ // tokenLen The len of the current pattern character. This indicates how many "M" that we have.
+ // patternToMatch The pattern that we want to search. This generally uses "d"
+ //
+ private static bool IsUseGenitiveForm(ReadOnlySpan<char> format, int index, int tokenLen, char patternToMatch)
+ {
+ int i;
+ int repeat = 0;
+ //
+ // Look back to see if we can find "d" or "ddd"
+ //
+
+ // Find first "d".
+ for (i = index - 1; i >= 0 && format[i] != patternToMatch; i--) { /*Do nothing here */ };
+
+ if (i >= 0)
+ {
+ // Find a "d", so look back to see how many "d" that we can find.
+ while (--i >= 0 && format[i] == patternToMatch)
+ {
+ repeat++;
+ }
+ //
+ // repeat == 0 means that we have one (patternToMatch)
+ // repeat == 1 means that we have two (patternToMatch)
+ //
+ if (repeat <= 1)
+ {
+ return (true);
+ }
+ // Note that we can't just stop here. We may find "ddd" while looking back, and we have to look
+ // ahead to see if there is "d" or "dd".
+ }
+
+ //
+ // If we can't find "d" or "dd" by looking back, try look ahead.
+ //
+
+ // Find first "d"
+ for (i = index + tokenLen; i < format.Length && format[i] != patternToMatch; i++) { /* Do nothing here */ };
+
+ if (i < format.Length)
+ {
+ repeat = 0;
+ // Find a "d", so contine the walk to see how may "d" that we can find.
+ while (++i < format.Length && format[i] == patternToMatch)
+ {
+ repeat++;
+ }
+ //
+ // repeat == 0 means that we have one (patternToMatch)
+ // repeat == 1 means that we have two (patternToMatch)
+ //
+ if (repeat <= 1)
+ {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+
+ //
+ // FormatCustomized
+ //
+ // Actions: Format the DateTime instance using the specified format.
+ //
+ private static StringBuilder FormatCustomized(
+ DateTime dateTime, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, TimeSpan offset, StringBuilder result)
+ {
+ Calendar cal = dtfi.Calendar;
+
+ bool resultBuilderIsPooled = false;
+ if (result == null)
+ {
+ resultBuilderIsPooled = true;
+ result = StringBuilderCache.Acquire();
+ }
+
+ // This is a flag to indicate if we are format the dates using Hebrew calendar.
+ bool isHebrewCalendar = (cal.ID == CalendarId.HEBREW);
+ // This is a flag to indicate if we are formating hour/minute/second only.
+ bool bTimeOnly = true;
+
+ int i = 0;
+ int tokenLen, hour12;
+
+ while (i < format.Length)
+ {
+ char ch = format[i];
+ int nextChar;
+ switch (ch)
+ {
+ case 'g':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ result.Append(dtfi.GetEraName(cal.GetEra(dateTime)));
+ break;
+ case 'h':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ hour12 = dateTime.Hour % 12;
+ if (hour12 == 0)
+ {
+ hour12 = 12;
+ }
+ FormatDigits(result, hour12, tokenLen);
+ break;
+ case 'H':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ FormatDigits(result, dateTime.Hour, tokenLen);
+ break;
+ case 'm':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ FormatDigits(result, dateTime.Minute, tokenLen);
+ break;
+ case 's':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ FormatDigits(result, dateTime.Second, tokenLen);
+ break;
+ case 'f':
+ case 'F':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ if (tokenLen <= MaxSecondsFractionDigits)
+ {
+ long fraction = (dateTime.Ticks % Calendar.TicksPerSecond);
+ fraction = fraction / (long)Math.Pow(10, 7 - tokenLen);
+ if (ch == 'f')
+ {
+ result.Append(((int)fraction).ToString(fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ int effectiveDigits = tokenLen;
+ while (effectiveDigits > 0)
+ {
+ if (fraction % 10 == 0)
+ {
+ fraction = fraction / 10;
+ effectiveDigits--;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (effectiveDigits > 0)
+ {
+ result.Append(((int)fraction).ToString(fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ // No fraction to emit, so see if we should remove decimal also.
+ if (result.Length > 0 && result[result.Length - 1] == '.')
+ {
+ result.Remove(result.Length - 1, 1);
+ }
+ }
+ }
+ }
+ else
+ {
+ if (resultBuilderIsPooled)
+ {
+ StringBuilderCache.Release(result);
+ }
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ break;
+ case 't':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ if (tokenLen == 1)
+ {
+ if (dateTime.Hour < 12)
+ {
+ if (dtfi.AMDesignator.Length >= 1)
+ {
+ result.Append(dtfi.AMDesignator[0]);
+ }
+ }
+ else
+ {
+ if (dtfi.PMDesignator.Length >= 1)
+ {
+ result.Append(dtfi.PMDesignator[0]);
+ }
+ }
+ }
+ else
+ {
+ result.Append((dateTime.Hour < 12 ? dtfi.AMDesignator : dtfi.PMDesignator));
+ }
+ break;
+ case 'd':
+ //
+ // tokenLen == 1 : Day of month as digits with no leading zero.
+ // tokenLen == 2 : Day of month as digits with leading zero for single-digit months.
+ // tokenLen == 3 : Day of week as a three-leter abbreviation.
+ // tokenLen >= 4 : Day of week as its full name.
+ //
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ if (tokenLen <= 2)
+ {
+ int day = cal.GetDayOfMonth(dateTime);
+ if (isHebrewCalendar)
+ {
+ // For Hebrew calendar, we need to convert numbers to Hebrew text for yyyy, MM, and dd values.
+ HebrewFormatDigits(result, day);
+ }
+ else
+ {
+ FormatDigits(result, day, tokenLen);
+ }
+ }
+ else
+ {
+ int dayOfWeek = (int)cal.GetDayOfWeek(dateTime);
+ result.Append(FormatDayOfWeek(dayOfWeek, tokenLen, dtfi));
+ }
+ bTimeOnly = false;
+ break;
+ case 'M':
+ //
+ // tokenLen == 1 : Month as digits with no leading zero.
+ // tokenLen == 2 : Month as digits with leading zero for single-digit months.
+ // tokenLen == 3 : Month as a three-letter abbreviation.
+ // tokenLen >= 4 : Month as its full name.
+ //
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ int month = cal.GetMonth(dateTime);
+ if (tokenLen <= 2)
+ {
+ if (isHebrewCalendar)
+ {
+ // For Hebrew calendar, we need to convert numbers to Hebrew text for yyyy, MM, and dd values.
+ HebrewFormatDigits(result, month);
+ }
+ else
+ {
+ FormatDigits(result, month, tokenLen);
+ }
+ }
+ else
+ {
+ if (isHebrewCalendar)
+ {
+ result.Append(FormatHebrewMonthName(dateTime, month, tokenLen, dtfi));
+ }
+ else
+ {
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0 && tokenLen >= 4)
+ {
+ result.Append(
+ dtfi.internalGetMonthName(
+ month,
+ IsUseGenitiveForm(format, i, tokenLen, 'd') ? MonthNameStyles.Genitive : MonthNameStyles.Regular,
+ false));
+ }
+ else
+ {
+ result.Append(FormatMonth(month, tokenLen, dtfi));
+ }
+ }
+ }
+ bTimeOnly = false;
+ break;
+ case 'y':
+ // Notes about OS behavior:
+ // y: Always print (year % 100). No leading zero.
+ // yy: Always print (year % 100) with leading zero.
+ // yyy/yyyy/yyyyy/... : Print year value. No leading zero.
+
+ int year = cal.GetYear(dateTime);
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ if (dtfi.HasForceTwoDigitYears)
+ {
+ FormatDigits(result, year, tokenLen <= 2 ? tokenLen : 2);
+ }
+ else if (cal.ID == CalendarId.HEBREW)
+ {
+ HebrewFormatDigits(result, year);
+ }
+ else
+ {
+ if (tokenLen <= 2)
+ {
+ FormatDigits(result, year % 100, tokenLen);
+ }
+ else
+ {
+ String fmtPattern = "D" + tokenLen.ToString();
+ result.Append(year.ToString(fmtPattern, CultureInfo.InvariantCulture));
+ }
+ }
+ bTimeOnly = false;
+ break;
+ case 'z':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ FormatCustomizedTimeZone(dateTime, offset, format, tokenLen, bTimeOnly, result);
+ break;
+ case 'K':
+ tokenLen = 1;
+ FormatCustomizedRoundripTimeZone(dateTime, offset, result);
+ break;
+ case ':':
+ result.Append(dtfi.TimeSeparator);
+ tokenLen = 1;
+ break;
+ case '/':
+ result.Append(dtfi.DateSeparator);
+ tokenLen = 1;
+ break;
+ case '\'':
+ case '\"':
+ tokenLen = ParseQuoteString(format, i, result);
+ break;
+ case '%':
+ // Optional format character.
+ // For example, format string "%d" will print day of month
+ // without leading zero. Most of the cases, "%" can be ignored.
+ nextChar = ParseNextChar(format, i);
+ // nextChar will be -1 if we already reach the end of the format string.
+ // Besides, we will not allow "%%" appear in the pattern.
+ if (nextChar >= 0 && nextChar != '%')
+ {
+ char nextCharChar = (char)nextChar;
+ StringBuilder origStringBuilder = FormatCustomized(dateTime, MemoryMarshal.CreateReadOnlySpan<char>(ref nextCharChar, 1), dtfi, offset, result);
+ Debug.Assert(ReferenceEquals(origStringBuilder, result));
+ tokenLen = 2;
+ }
+ else
+ {
+ //
+ // This means that '%' is at the end of the format string or
+ // "%%" appears in the format string.
+ //
+ if (resultBuilderIsPooled)
+ {
+ StringBuilderCache.Release(result);
+ }
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ break;
+ case '\\':
+ // Escaped character. Can be used to insert character into the format string.
+ // For exmple, "\d" will insert the character 'd' into the string.
+ //
+ // NOTENOTE : we can remove this format character if we enforce the enforced quote
+ // character rule.
+ // That is, we ask everyone to use single quote or double quote to insert characters,
+ // then we can remove this character.
+ //
+ nextChar = ParseNextChar(format, i);
+ if (nextChar >= 0)
+ {
+ result.Append(((char)nextChar));
+ tokenLen = 2;
+ }
+ else
+ {
+ //
+ // This means that '\' is at the end of the formatting string.
+ //
+ if (resultBuilderIsPooled)
+ {
+ StringBuilderCache.Release(result);
+ }
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ break;
+ default:
+ // NOTENOTE : we can remove this rule if we enforce the enforced quote
+ // character rule.
+ // That is, if we ask everyone to use single quote or double quote to insert characters,
+ // then we can remove this default block.
+ result.Append(ch);
+ tokenLen = 1;
+ break;
+ }
+ i += tokenLen;
+ }
+ return result;
+ }
+
+
+ // output the 'z' famliy of formats, which output a the offset from UTC, e.g. "-07:30"
+ private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan offset, ReadOnlySpan<char> format, Int32 tokenLen, Boolean timeOnly, StringBuilder result)
+ {
+ // See if the instance already has an offset
+ Boolean dateTimeFormat = (offset == NullOffset);
+ if (dateTimeFormat)
+ {
+ // No offset. The instance is a DateTime and the output should be the local time zone
+
+ if (timeOnly && dateTime.Ticks < Calendar.TicksPerDay)
+ {
+ // For time only format and a time only input, the time offset on 0001/01/01 is less
+ // accurate than the system's current offset because of daylight saving time.
+ offset = TimeZoneInfo.GetLocalUtcOffset(DateTime.Now, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+ else if (dateTime.Kind == DateTimeKind.Utc)
+ {
+ offset = TimeSpan.Zero;
+ }
+ else
+ {
+ offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+ }
+ if (offset >= TimeSpan.Zero)
+ {
+ result.Append('+');
+ }
+ else
+ {
+ result.Append('-');
+ // get a positive offset, so that you don't need a separate code path for the negative numbers.
+ offset = offset.Negate();
+ }
+
+ if (tokenLen <= 1)
+ {
+ // 'z' format e.g "-7"
+ result.AppendFormat(CultureInfo.InvariantCulture, "{0:0}", offset.Hours);
+ }
+ else
+ {
+ // 'zz' or longer format e.g "-07"
+ result.AppendFormat(CultureInfo.InvariantCulture, "{0:00}", offset.Hours);
+ if (tokenLen >= 3)
+ {
+ // 'zzz*' or longer format e.g "-07:30"
+ result.AppendFormat(CultureInfo.InvariantCulture, ":{0:00}", offset.Minutes);
+ }
+ }
+ }
+
+ // output the 'K' format, which is for round-tripping the data
+ private static void FormatCustomizedRoundripTimeZone(DateTime dateTime, TimeSpan offset, StringBuilder result)
+ {
+ // The objective of this format is to round trip the data in the type
+ // For DateTime it should round-trip the Kind value and preserve the time zone.
+ // DateTimeOffset instance, it should do so by using the internal time zone.
+
+ if (offset == NullOffset)
+ {
+ // source is a date time, so behavior depends on the kind.
+ switch (dateTime.Kind)
+ {
+ case DateTimeKind.Local:
+ // This should output the local offset, e.g. "-07:30"
+ offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ // fall through to shared time zone output code
+ break;
+ case DateTimeKind.Utc:
+ // The 'Z' constant is a marker for a UTC date
+ result.Append("Z");
+ return;
+ default:
+ // If the kind is unspecified, we output nothing here
+ return;
+ }
+ }
+ if (offset >= TimeSpan.Zero)
+ {
+ result.Append('+');
+ }
+ else
+ {
+ result.Append('-');
+ // get a positive offset, so that you don't need a separate code path for the negative numbers.
+ offset = offset.Negate();
+ }
+
+ Append2DigitNumber(result, offset.Hours);
+ result.Append(':');
+ Append2DigitNumber(result, offset.Minutes);
+ }
+
+ private static void Append2DigitNumber(StringBuilder result, int val)
+ {
+ result.Append((char)('0' + (val / 10)));
+ result.Append((char)('0' + (val % 10)));
+ }
+
+ internal static String GetRealFormat(ReadOnlySpan<char> format, DateTimeFormatInfo dtfi)
+ {
+ String realFormat = null;
+
+ switch (format[0])
+ {
+ case 'd': // Short Date
+ realFormat = dtfi.ShortDatePattern;
+ break;
+ case 'D': // Long Date
+ realFormat = dtfi.LongDatePattern;
+ break;
+ case 'f': // Full (long date + short time)
+ realFormat = dtfi.LongDatePattern + " " + dtfi.ShortTimePattern;
+ break;
+ case 'F': // Full (long date + long time)
+ realFormat = dtfi.FullDateTimePattern;
+ break;
+ case 'g': // General (short date + short time)
+ realFormat = dtfi.GeneralShortTimePattern;
+ break;
+ case 'G': // General (short date + long time)
+ realFormat = dtfi.GeneralLongTimePattern;
+ break;
+ case 'm':
+ case 'M': // Month/Day Date
+ realFormat = dtfi.MonthDayPattern;
+ break;
+ case 'o':
+ case 'O':
+ realFormat = RoundtripFormat;
+ break;
+ case 'r':
+ case 'R': // RFC 1123 Standard
+ realFormat = dtfi.RFC1123Pattern;
+ break;
+ case 's': // Sortable without Time Zone Info
+ realFormat = dtfi.SortableDateTimePattern;
+ break;
+ case 't': // Short Time
+ realFormat = dtfi.ShortTimePattern;
+ break;
+ case 'T': // Long Time
+ realFormat = dtfi.LongTimePattern;
+ break;
+ case 'u': // Universal with Sortable format
+ realFormat = dtfi.UniversalSortableDateTimePattern;
+ break;
+ case 'U': // Universal with Full (long date + long time) format
+ realFormat = dtfi.FullDateTimePattern;
+ break;
+ case 'y':
+ case 'Y': // Year/Month Date
+ realFormat = dtfi.YearMonthPattern;
+ break;
+ default:
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ return (realFormat);
+ }
+
+
+ // Expand a pre-defined format string (like "D" for long date) to the real format that
+ // we are going to use in the date time parsing.
+ // This method also convert the dateTime if necessary (e.g. when the format is in Universal time),
+ // and change dtfi if necessary (e.g. when the format should use invariant culture).
+ //
+ private static String ExpandPredefinedFormat(ReadOnlySpan<char> format, ref DateTime dateTime, ref DateTimeFormatInfo dtfi, ref TimeSpan offset)
+ {
+ switch (format[0])
+ {
+ case 'o':
+ case 'O': // Round trip format
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 'r':
+ case 'R': // RFC 1123 Standard
+ if (offset != NullOffset)
+ {
+ // Convert to UTC invariants mean this will be in range
+ dateTime = dateTime - offset;
+ }
+ else if (dateTime.Kind == DateTimeKind.Local)
+ {
+ InvalidFormatForLocal(format, dateTime);
+ }
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 's': // Sortable without Time Zone Info
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 'u': // Universal time in sortable format.
+ if (offset != NullOffset)
+ {
+ // Convert to UTC invariants mean this will be in range
+ dateTime = dateTime - offset;
+ }
+ else if (dateTime.Kind == DateTimeKind.Local)
+ {
+ InvalidFormatForLocal(format, dateTime);
+ }
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 'U': // Universal time in culture dependent format.
+ if (offset != NullOffset)
+ {
+ // This format is not supported by DateTimeOffset
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ // Universal time is always in Greogrian calendar.
+ //
+ // Change the Calendar to be Gregorian Calendar.
+ //
+ dtfi = (DateTimeFormatInfo)dtfi.Clone();
+ if (dtfi.Calendar.GetType() != typeof(GregorianCalendar))
+ {
+ dtfi.Calendar = GregorianCalendar.GetDefaultInstance();
+ }
+ dateTime = dateTime.ToUniversalTime();
+ break;
+ }
+ return GetRealFormat(format, dtfi);
+ }
+
+ internal static String Format(DateTime dateTime, String format, IFormatProvider provider)
+ {
+ return Format(dateTime, format, provider, NullOffset);
+ }
+
+ internal static string Format(DateTime dateTime, String format, IFormatProvider provider, TimeSpan offset)
+ {
+ if (format != null && format.Length == 1)
+ {
+ // Optimize for these standard formats that are not affected by culture.
+ switch (format[0])
+ {
+ // Round trip format
+ case 'o':
+ case 'O':
+ const int MinFormatOLength = 27, MaxFormatOLength = 33;
+ Span<char> span = stackalloc char[MaxFormatOLength];
+ TryFormatO(dateTime, offset, span, out int ochars);
+ Debug.Assert(ochars >= MinFormatOLength && ochars <= MaxFormatOLength);
+ return span.Slice(0, ochars).ToString();
+
+ // RFC1123
+ case 'r':
+ case 'R':
+ const int FormatRLength = 29;
+ string str = string.FastAllocateString(FormatRLength);
+ TryFormatR(dateTime, offset, new Span<char>(ref str.GetRawStringData(), str.Length), out int rchars);
+ Debug.Assert(rchars == str.Length);
+ return str;
+ }
+ }
+
+ DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider);
+ return StringBuilderCache.GetStringAndRelease(FormatStringBuilder(dateTime, format, dtfi, offset));
+ }
+
+ internal static bool TryFormat(DateTime dateTime, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider) =>
+ TryFormat(dateTime, destination, out charsWritten, format, provider, NullOffset);
+
+ internal static bool TryFormat(DateTime dateTime, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider, TimeSpan offset)
+ {
+ if (format.Length == 1)
+ {
+ // Optimize for these standard formats that are not affected by culture.
+ switch (format[0])
+ {
+ // Round trip format
+ case 'o':
+ case 'O':
+ return TryFormatO(dateTime, offset, destination, out charsWritten);
+
+ // RFC1123
+ case 'r':
+ case 'R':
+ return TryFormatR(dateTime, offset, destination, out charsWritten);
+ }
+ }
+
+ DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider);
+ StringBuilder sb = FormatStringBuilder(dateTime, format, dtfi, offset);
+
+ bool success = sb.Length <= destination.Length;
+ if (success)
+ {
+ sb.CopyTo(0, destination, sb.Length);
+ charsWritten = sb.Length;
+ }
+ else
+ {
+ charsWritten = 0;
+ }
+
+ StringBuilderCache.Release(sb);
+ return success;
+ }
+
+ private static StringBuilder FormatStringBuilder(DateTime dateTime, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, TimeSpan offset)
+ {
+ Debug.Assert(dtfi != null);
+ if (format.Length == 0)
+ {
+ Boolean timeOnlySpecialCase = false;
+ if (dateTime.Ticks < Calendar.TicksPerDay)
+ {
+ // If the time is less than 1 day, consider it as time of day.
+ // Just print out the short time format.
+ //
+ // This is a workaround for VB, since they use ticks less then one day to be
+ // time of day. In cultures which use calendar other than Gregorian calendar, these
+ // alternative calendar may not support ticks less than a day.
+ // For example, Japanese calendar only supports date after 1868/9/8.
+ // This will pose a problem when people in VB get the time of day, and use it
+ // to call ToString(), which will use the general format (short date + long time).
+ // Since Japanese calendar does not support Gregorian year 0001, an exception will be
+ // thrown when we try to get the Japanese year for Gregorian year 0001.
+ // Therefore, the workaround allows them to call ToString() for time of day from a DateTime by
+ // formatting as ISO 8601 format.
+ switch (dtfi.Calendar.ID)
+ {
+ case CalendarId.JAPAN:
+ case CalendarId.TAIWAN:
+ case CalendarId.HIJRI:
+ case CalendarId.HEBREW:
+ case CalendarId.JULIAN:
+ case CalendarId.UMALQURA:
+ case CalendarId.PERSIAN:
+ timeOnlySpecialCase = true;
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ }
+ }
+ if (offset == NullOffset)
+ {
+ // Default DateTime.ToString case.
+ format = timeOnlySpecialCase ? "s" : "G";
+ }
+ else
+ {
+ // Default DateTimeOffset.ToString case.
+ format = timeOnlySpecialCase ? RoundtripDateTimeUnfixed : dtfi.DateTimeOffsetPattern;
+ }
+ }
+
+ if (format.Length == 1)
+ {
+ format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi, ref offset);
+ }
+
+ return FormatCustomized(dateTime, format, dtfi, offset, result: null);
+ }
+
+ // Roundtrippable format. One of
+ // 012345678901234567890123456789012
+ // ---------------------------------
+ // 2017-06-12T05:30:45.7680000-07:00
+ // 2017-06-12T05:30:45.7680000Z (Z is short for "+00:00" but also distinguishes DateTimeKind.Utc from DateTimeKind.Local)
+ // 2017-06-12T05:30:45.7680000 (interpreted as local time wrt to current time zone)
+ private static bool TryFormatO(DateTime dateTime, TimeSpan offset, Span<char> destination, out int charsWritten)
+ {
+ const int MinimumBytesNeeded = 27;
+
+ int charsRequired = MinimumBytesNeeded;
+ DateTimeKind kind = DateTimeKind.Local;
+
+ if (offset == NullOffset)
+ {
+ kind = dateTime.Kind;
+ if (kind == DateTimeKind.Local)
+ {
+ offset = TimeZoneInfo.Local.GetUtcOffset(dateTime);
+ charsRequired += 6;
+ }
+ else if (kind == DateTimeKind.Utc)
+ {
+ charsRequired += 1;
+ }
+ }
+ else
+ {
+ charsRequired += 6;
+ }
+
+ if (destination.Length < charsRequired)
+ {
+ charsWritten = 0;
+ return false;
+ }
+ charsWritten = charsRequired;
+
+ // Hoist most of the bounds checks on destination.
+ { var unused = destination[MinimumBytesNeeded - 1]; }
+
+ WriteFourDecimalDigits((uint)dateTime.Year, destination, 0);
+ destination[4] = '-';
+ WriteTwoDecimalDigits((uint)dateTime.Month, destination, 5);
+ destination[7] = '-';
+ WriteTwoDecimalDigits((uint)dateTime.Day, destination, 8);
+ destination[10] = 'T';
+ WriteTwoDecimalDigits((uint)dateTime.Hour, destination, 11);
+ destination[13] = ':';
+ WriteTwoDecimalDigits((uint)dateTime.Minute, destination, 14);
+ destination[16] = ':';
+ WriteTwoDecimalDigits((uint)dateTime.Second, destination, 17);
+ destination[19] = '.';
+ WriteDigits((uint)((ulong)dateTime.Ticks % (ulong)TimeSpan.TicksPerSecond), destination.Slice(20, 7));
+
+ if (kind == DateTimeKind.Local)
+ {
+ char sign;
+ if (offset < default(TimeSpan) /* a "const" version of TimeSpan.Zero */)
+ {
+ sign = '-';
+ offset = TimeSpan.FromTicks(-offset.Ticks);
+ }
+ else
+ {
+ sign = '+';
+ }
+
+ // Writing the value backward allows the JIT to optimize by
+ // performing a single bounds check against buffer.
+ WriteTwoDecimalDigits((uint)offset.Minutes, destination, 31);
+ destination[30] = ':';
+ WriteTwoDecimalDigits((uint)offset.Hours, destination, 28);
+ destination[27] = sign;
+ }
+ else if (kind == DateTimeKind.Utc)
+ {
+ destination[27] = 'Z';
+ }
+
+ return true;
+ }
+
+ // Rfc1123
+ // 01234567890123456789012345678
+ // -----------------------------
+ // Tue, 03 Jan 2017 08:08:05 GMT
+ private static bool TryFormatR(DateTime dateTime, TimeSpan offset, Span<char> destination, out int charsWritten)
+ {
+ // Writing the check in this fashion elides all bounds checks on 'destination'
+ // for the remainder of the method.
+ if (28 >= (uint)destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ if (offset != NullOffset)
+ {
+ // Convert to UTC invariants.
+ dateTime = dateTime - offset;
+ }
+
+ dateTime.GetDatePart(out int year, out int month, out int day);
+
+ string dayAbbrev = InvariantAbbreviatedDayNames[(int)dateTime.DayOfWeek];
+ Debug.Assert(dayAbbrev.Length == 3);
+
+ string monthAbbrev = InvariantAbbreviatedMonthNames[month - 1];
+ Debug.Assert(monthAbbrev.Length == 3);
+
+ destination[0] = dayAbbrev[0];
+ destination[1] = dayAbbrev[1];
+ destination[2] = dayAbbrev[2];
+ destination[3] = ',';
+ destination[4] = ' ';
+ WriteTwoDecimalDigits((uint)day, destination, 5);
+ destination[7] = ' ';
+ destination[8] = monthAbbrev[0];
+ destination[9] = monthAbbrev[1];
+ destination[10] = monthAbbrev[2];
+ destination[11] = ' ';
+ WriteFourDecimalDigits((uint)year, destination, 12);
+ destination[16] = ' ';
+ WriteTwoDecimalDigits((uint)dateTime.Hour, destination, 17);
+ destination[19] = ':';
+ WriteTwoDecimalDigits((uint)dateTime.Minute, destination, 20);
+ destination[22] = ':';
+ WriteTwoDecimalDigits((uint)dateTime.Second, destination, 23);
+ destination[25] = ' ';
+ destination[26] = 'G';
+ destination[27] = 'M';
+ destination[28] = 'T';
+
+ charsWritten = 29;
+ return true;
+ }
+
+ /// <summary>
+ /// Writes a value [ 00 .. 99 ] to the buffer starting at the specified offset.
+ /// This method performs best when the starting index is a constant literal.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteTwoDecimalDigits(uint value, Span<char> destination, int offset)
+ {
+ Debug.Assert(0 <= value && value <= 99);
+
+ uint temp = '0' + value;
+ value /= 10;
+ destination[offset + 1] = (char)(temp - (value * 10));
+ destination[offset] = (char)('0' + value);
+ }
+
+ /// <summary>
+ /// Writes a value [ 0000 .. 9999 ] to the buffer starting at the specified offset.
+ /// This method performs best when the starting index is a constant literal.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteFourDecimalDigits(uint value, Span<char> buffer, int startingIndex = 0)
+ {
+ Debug.Assert(0 <= value && value <= 9999);
+
+ uint temp = '0' + value;
+ value /= 10;
+ buffer[startingIndex + 3] = (char)(temp - (value * 10));
+
+ temp = '0' + value;
+ value /= 10;
+ buffer[startingIndex + 2] = (char)(temp - (value * 10));
+
+ temp = '0' + value;
+ value /= 10;
+ buffer[startingIndex + 1] = (char)(temp - (value * 10));
+
+ buffer[startingIndex] = (char)('0' + value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteDigits(ulong value, Span<char> buffer)
+ {
+ // We can mutate the 'value' parameter since it's a copy-by-value local.
+ // It'll be used to represent the value left over after each division by 10.
+
+ for (int i = buffer.Length - 1; i >= 1; i--)
+ {
+ ulong temp = '0' + value;
+ value /= 10;
+ buffer[i] = (char)(temp - (value * 10));
+ }
+
+ Debug.Assert(value < 10);
+ buffer[0] = (char)('0' + value);
+ }
+
+ internal static String[] GetAllDateTimes(DateTime dateTime, char format, DateTimeFormatInfo dtfi)
+ {
+ Debug.Assert(dtfi != null);
+ String[] allFormats = null;
+ String[] results = null;
+
+ switch (format)
+ {
+ case 'd':
+ case 'D':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ case 'm':
+ case 'M':
+ case 't':
+ case 'T':
+ case 'y':
+ case 'Y':
+ allFormats = dtfi.GetAllDateTimePatterns(format);
+ results = new String[allFormats.Length];
+ for (int i = 0; i < allFormats.Length; i++)
+ {
+ results[i] = Format(dateTime, allFormats[i], dtfi);
+ }
+ break;
+ case 'U':
+ DateTime universalTime = dateTime.ToUniversalTime();
+ allFormats = dtfi.GetAllDateTimePatterns(format);
+ results = new String[allFormats.Length];
+ for (int i = 0; i < allFormats.Length; i++)
+ {
+ results[i] = Format(universalTime, allFormats[i], dtfi);
+ }
+ break;
+ //
+ // The following ones are special cases because these patterns are read-only in
+ // DateTimeFormatInfo.
+ //
+ case 'r':
+ case 'R':
+ case 'o':
+ case 'O':
+ case 's':
+ case 'u':
+ results = new String[] { Format(dateTime, new String(format, 1), dtfi) };
+ break;
+ default:
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ return (results);
+ }
+
+ internal static String[] GetAllDateTimes(DateTime dateTime, DateTimeFormatInfo dtfi)
+ {
+ List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
+
+ for (int i = 0; i < allStandardFormats.Length; i++)
+ {
+ String[] strings = GetAllDateTimes(dateTime, allStandardFormats[i], dtfi);
+ for (int j = 0; j < strings.Length; j++)
+ {
+ results.Add(strings[j]);
+ }
+ }
+ String[] value = new String[results.Count];
+ results.CopyTo(0, value, 0, results.Count);
+ return (value);
+ }
+
+ // This is a placeholder for an MDA to detect when the user is using a
+ // local DateTime with a format that will be interpreted as UTC.
+ internal static void InvalidFormatForLocal(ReadOnlySpan<char> format, DateTime dateTime)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs
new file mode 100644
index 0000000000..edec75ac85
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs
@@ -0,0 +1,2875 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Globalization
+{
+ //
+ // Flags used to indicate different styles of month names.
+ // This is an internal flag used by internalGetMonthName().
+ // Use flag here in case that we need to provide a combination of these styles
+ // (such as month name of a leap year in genitive form. Not likely for now,
+ // but would like to keep the option open).
+ //
+
+ [Flags]
+ internal enum MonthNameStyles
+ {
+ Regular = 0x00000000,
+ Genitive = 0x00000001,
+ LeapYear = 0x00000002,
+ }
+
+ //
+ // Flags used to indicate special rule used in parsing/formatting
+ // for a specific DateTimeFormatInfo instance.
+ // This is an internal flag.
+ //
+ // This flag is different from MonthNameStyles because this flag
+ // can be expanded to accommodate parsing behaviors like CJK month names
+ // or alternative month names, etc.
+
+ [Flags]
+ internal enum DateTimeFormatFlags
+ {
+ None = 0x00000000,
+ UseGenitiveMonth = 0x00000001,
+ UseLeapYearMonth = 0x00000002,
+ UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names.
+ UseHebrewRule = 0x00000008, // Format/Parse using the Hebrew calendar rule.
+ UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names.
+ UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers.
+
+ NotInitialized = -1,
+ }
+
+
+ public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable
+ {
+ // cache for the invariant culture.
+ // invariantInfo is constant irrespective of your current culture.
+ private static volatile DateTimeFormatInfo s_invariantInfo;
+
+ // an index which points to a record in Culture Data Table.
+ private CultureData _cultureData;
+
+ // The culture name used to create this DTFI.
+ private String _name = null;
+
+ // The language name of the culture used to create this DTFI.
+ private String _langName = null;
+
+ // CompareInfo usually used by the parser.
+ private CompareInfo _compareInfo = null;
+
+ // Culture matches current DTFI. mainly used for string comparisons during parsing.
+ private CultureInfo _cultureInfo = null;
+
+ //
+ // Caches for various properties.
+ //
+
+ private String amDesignator = null;
+ private String pmDesignator = null;
+
+ private String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't)
+
+ private String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't)
+
+ private String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't)
+
+ private String timeSeparator = null; // derived from long time (whidbey expects, arrowhead doesn't)
+ private String monthDayPattern = null;
+ // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM}
+ private String dateTimeOffsetPattern = null;
+
+ //
+ // The following are constant values.
+ //
+ private const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
+
+ // The sortable pattern is based on ISO 8601.
+ private const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
+ private const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
+
+ //
+ // The following are affected by calendar settings.
+ //
+ private Calendar calendar = null;
+
+ private int firstDayOfWeek = -1;
+ private int calendarWeekRule = -1;
+
+
+ private String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't)
+
+ private String[] abbreviatedDayNames = null;
+
+
+ private String[] m_superShortDayNames = null;
+
+ private String[] dayNames = null;
+ private String[] abbreviatedMonthNames = null;
+ private String[] monthNames = null;
+ // Cache the genitive month names that we retrieve from the data table.
+
+ private String[] genitiveMonthNames = null;
+
+ // Cache the abbreviated genitive month names that we retrieve from the data table.
+
+ private String[] m_genitiveAbbreviatedMonthNames = null;
+
+ // Cache the month names of a leap year that we retrieve from the data table.
+
+ private String[] leapYearMonthNames = null;
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+
+ // The "default" Date/time patterns
+ private String longDatePattern = null;
+ private String shortDatePattern = null;
+ private String yearMonthPattern = null;
+ private String longTimePattern = null;
+ private String shortTimePattern = null;
+
+ private String[] allYearMonthPatterns = null;
+
+ private String[] allShortDatePatterns = null;
+ private String[] allLongDatePatterns = null;
+ private String[] allShortTimePatterns = null;
+ private String[] allLongTimePatterns = null;
+
+ // Cache the era names for this DateTimeFormatInfo instance.
+ private String[] m_eraNames = null;
+ private String[] m_abbrevEraNames = null;
+ private String[] m_abbrevEnglishEraNames = null;
+
+ private CalendarId[] optionalCalendars = null;
+
+ private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
+
+ // CultureInfo updates this
+ internal bool _isReadOnly = false;
+
+ // This flag gives hints about if formatting/parsing should perform special code path for things like
+ // genitive form or leap year month names.
+
+ private DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
+
+ private String CultureName
+ {
+ get
+ {
+ if (_name == null)
+ {
+ _name = _cultureData.CultureName;
+ }
+ return (_name);
+ }
+ }
+
+ private CultureInfo Culture
+ {
+ get
+ {
+ if (_cultureInfo == null)
+ {
+ _cultureInfo = CultureInfo.GetCultureInfo(this.CultureName);
+ }
+ return _cultureInfo;
+ }
+ }
+
+ // TODO: This ignores other cultures that might want to do something similar
+ private String LanguageName
+ {
+ get
+ {
+ if (_langName == null)
+ {
+ _langName = _cultureData.SISO639LANGNAME;
+ }
+ return (_langName);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Create an array of string which contains the abbreviated day names.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private string[] internalGetAbbreviatedDayOfWeekNames() => this.abbreviatedDayNames ?? internalGetAbbreviatedDayOfWeekNamesCore();
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private string[] internalGetAbbreviatedDayOfWeekNamesCore()
+ {
+ // Get the abbreviated day names for our current calendar
+ this.abbreviatedDayNames = _cultureData.AbbreviatedDayNames(Calendar.ID);
+ Debug.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week");
+ return this.abbreviatedDayNames;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Action: Returns the string array of the one-letter day of week names.
+ // Returns:
+ // an array of one-letter day of week names
+ // Arguments:
+ // None
+ // Exceptions:
+ // None
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ private string[] internalGetSuperShortDayNames() => this.m_superShortDayNames ?? internalGetSuperShortDayNamesCore();
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private string[] internalGetSuperShortDayNamesCore()
+ {
+ // Get the super short day names for our current calendar
+ this.m_superShortDayNames = _cultureData.SuperShortDayNames(Calendar.ID);
+ Debug.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week");
+ return this.m_superShortDayNames;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Create an array of string which contains the day names.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private string[] internalGetDayOfWeekNames() => this.dayNames ?? internalGetDayOfWeekNamesCore();
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private string[] internalGetDayOfWeekNamesCore()
+ {
+ // Get the day names for our current calendar
+ this.dayNames = _cultureData.DayNames(Calendar.ID);
+ Debug.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week");
+ return this.dayNames;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Create an array of string which contains the abbreviated month names.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private String[] internalGetAbbreviatedMonthNames() => this.abbreviatedMonthNames ?? internalGetAbbreviatedMonthNamesCore();
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private String[] internalGetAbbreviatedMonthNamesCore()
+ {
+ // Get the month names for our current calendar
+ this.abbreviatedMonthNames = _cultureData.AbbreviatedMonthNames(Calendar.ID);
+ Debug.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13,
+ "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year");
+ return this.abbreviatedMonthNames;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Create an array of string which contains the month names.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private string[] internalGetMonthNames() => this.monthNames ?? internalGetMonthNamesCore();
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private string[] internalGetMonthNamesCore()
+ {
+ // Get the month names for our current calendar
+ this.monthNames = _cultureData.MonthNames(Calendar.ID);
+ Debug.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13,
+ "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year");
+ return this.monthNames;
+ }
+
+
+ //
+ // Invariant DateTimeFormatInfo doesn't have user-overriden values
+ // Default calendar is gregorian
+ public DateTimeFormatInfo()
+ : this(CultureInfo.InvariantCulture._cultureData, GregorianCalendar.GetDefaultInstance())
+ {
+ }
+
+ internal DateTimeFormatInfo(CultureData cultureData, Calendar cal)
+ {
+ Debug.Assert(cultureData != null);
+ Debug.Assert(cal != null);
+
+ // Remember our culture
+ _cultureData = cultureData;
+
+ this.Calendar = cal;
+ }
+
+ private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId)
+ {
+ Debug.Assert(cultureData != null);
+ Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initalized calendarId");
+
+ if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; }
+ if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; }
+
+ if (this.amDesignator == null) { this.amDesignator = cultureData.SAM1159; }
+ if (this.pmDesignator == null) { this.pmDesignator = cultureData.SPM2359; }
+ if (this.timeSeparator == null) { this.timeSeparator = cultureData.TimeSeparator; }
+ if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarId); }
+
+ this.allLongTimePatterns = _cultureData.LongTimes;
+ Debug.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
+
+ this.allShortTimePatterns = _cultureData.ShortTimes;
+ Debug.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
+
+ this.allLongDatePatterns = cultureData.LongDates(calendarId);
+ Debug.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
+
+ this.allShortDatePatterns = cultureData.ShortDates(calendarId);
+ Debug.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
+
+ this.allYearMonthPatterns = cultureData.YearMonths(calendarId);
+ Debug.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
+ }
+
+ // Returns a default DateTimeFormatInfo that will be universally
+ // supported and constant irrespective of the current culture.
+ // Used by FromString methods.
+ //
+
+ public static DateTimeFormatInfo InvariantInfo
+ {
+ get
+ {
+ if (s_invariantInfo == null)
+ {
+ DateTimeFormatInfo info = new DateTimeFormatInfo();
+ info.Calendar.SetReadOnlyState(true);
+ info._isReadOnly = true;
+ s_invariantInfo = info;
+ }
+ return (s_invariantInfo);
+ }
+ }
+
+ // Returns the current culture's DateTimeFormatInfo. Used by Parse methods.
+ //
+
+ public static DateTimeFormatInfo CurrentInfo
+ {
+ get
+ {
+ System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture;
+ if (!culture._isInherited)
+ {
+ DateTimeFormatInfo info = culture.dateTimeInfo;
+ if (info != null)
+ {
+ return info;
+ }
+ }
+ return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo));
+ }
+ }
+
+ public static DateTimeFormatInfo GetInstance(IFormatProvider provider) =>
+ provider == null ? CurrentInfo :
+ provider is CultureInfo cultureProvider && !cultureProvider._isInherited ? cultureProvider.DateTimeFormat :
+ provider is DateTimeFormatInfo info ? info :
+ provider.GetFormat(typeof(DateTimeFormatInfo)) is DateTimeFormatInfo info2 ? info2 :
+ CurrentInfo; // Couldn't get anything, just use currentInfo as fallback
+
+ public Object GetFormat(Type formatType)
+ {
+ return (formatType == typeof(DateTimeFormatInfo) ? this : null);
+ }
+
+
+ public Object Clone()
+ {
+ DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone();
+ // We can use the data member calendar in the setter, instead of the property Calendar,
+ // since the cloned copy should have the same state as the original copy.
+ n.calendar = (Calendar)this.Calendar.Clone();
+ n._isReadOnly = false;
+ return n;
+ }
+
+
+ public String AMDesignator
+ {
+ get
+ {
+ if (this.amDesignator == null)
+ {
+ this.amDesignator = _cultureData.SAM1159;
+ }
+ Debug.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
+ return (this.amDesignator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+ ClearTokenHashTable();
+ amDesignator = value;
+ }
+ }
+
+
+ public Calendar Calendar
+ {
+ get
+ {
+ Debug.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
+ return (this.calendar);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj);
+ }
+ if (value == calendar)
+ {
+ return;
+ }
+
+ for (int i = 0; i < this.OptionalCalendars.Length; i++)
+ {
+ if (this.OptionalCalendars[i] == value.ID)
+ {
+ // We can use this one, so do so.
+
+ // Clean related properties if we already had a calendar set
+ if (calendar != null)
+ {
+ // clean related properties which are affected by the calendar setting,
+ // so that they will be refreshed when they are accessed next time.
+ //
+
+ // These properites are in the order as appearing in calendar.xml.
+ m_eraNames = null;
+ m_abbrevEraNames = null;
+ m_abbrevEnglishEraNames = null;
+
+ monthDayPattern = null;
+
+ dayNames = null;
+ abbreviatedDayNames = null;
+ m_superShortDayNames = null;
+ monthNames = null;
+ abbreviatedMonthNames = null;
+ genitiveMonthNames = null;
+ m_genitiveAbbreviatedMonthNames = null;
+ leapYearMonthNames = null;
+ formatFlags = DateTimeFormatFlags.NotInitialized;
+
+ allShortDatePatterns = null;
+ allLongDatePatterns = null;
+ allYearMonthPatterns = null;
+ dateTimeOffsetPattern = null;
+
+ // The defaults need reset as well:
+ longDatePattern = null;
+ shortDatePattern = null;
+ yearMonthPattern = null;
+
+ // These properies are not in the OS data, but they are dependent on the values like shortDatePattern.
+ fullDateTimePattern = null; // Long date + long time
+ generalShortTimePattern = null; // short date + short time
+ generalLongTimePattern = null; // short date + long time
+
+ // Derived item that changes
+ dateSeparator = null;
+
+ // We don't need to do these because they are not changed by changing calendar
+ // amDesignator
+ // pmDesignator
+ // timeSeparator
+ // longTimePattern
+ // firstDayOfWeek
+ // calendarWeekRule
+
+ // remember to reload tokens
+ ClearTokenHashTable();
+ }
+
+ // Remember the new calendar
+ calendar = value;
+ InitializeOverridableProperties(_cultureData, calendar.ID);
+
+ // We succeeded, return
+ return;
+ }
+ }
+
+ // The assigned calendar is not a valid calendar for this culture, throw
+ throw new ArgumentOutOfRangeException(nameof(value), SR.Argument_InvalidCalendar);
+ }
+ }
+
+ private CalendarId[] OptionalCalendars
+ {
+ get
+ {
+ if (this.optionalCalendars == null)
+ {
+ this.optionalCalendars = _cultureData.CalendarIds;
+ }
+ return (this.optionalCalendars);
+ }
+ }
+
+ /*=================================GetEra==========================
+ **Action: Get the era value by parsing the name of the era.
+ **Returns: The era value for the specified era name.
+ ** -1 if the name of the era is not valid or not supported.
+ **Arguments: eraName the name of the era.
+ **Exceptions: None.
+ ============================================================================*/
+
+
+ public int GetEra(String eraName)
+ {
+ if (eraName == null)
+ {
+ throw new ArgumentNullException(nameof(eraName),
+ SR.ArgumentNull_String);
+ }
+
+ // The Era Name and Abbreviated Era Name
+ // for Taiwan Calendar on non-Taiwan SKU returns empty string (which
+ // would be matched below) but we don't want the empty string to give
+ // us an Era number
+ // confer 85900 DTFI.GetEra("") should fail on all cultures
+ if (eraName.Length == 0)
+ {
+ return (-1);
+ }
+
+ // The following is based on the assumption that the era value is starting from 1, and has a
+ // serial values.
+ // If that ever changes, the code has to be changed.
+
+ // The calls to String.Compare should use the current culture for the string comparisons, but the
+ // invariant culture when comparing against the english names.
+ for (int i = 0; i < EraNames.Length; i++)
+ {
+ // Compare the era name in a case-insensitive way for the appropriate culture.
+ if (m_eraNames[i].Length > 0)
+ {
+ if (this.Culture.CompareInfo.Compare(eraName, m_eraNames[i], CompareOptions.IgnoreCase) == 0)
+ {
+ return (i + 1);
+ }
+ }
+ }
+ for (int i = 0; i < AbbreviatedEraNames.Length; i++)
+ {
+ // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.
+ if (this.Culture.CompareInfo.Compare(eraName, m_abbrevEraNames[i], CompareOptions.IgnoreCase) == 0)
+ {
+ return (i + 1);
+ }
+ }
+ for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++)
+ {
+ // this comparison should use the InvariantCulture. The English name could have linguistically
+ // interesting characters.
+ if (CompareInfo.Invariant.Compare(eraName, m_abbrevEnglishEraNames[i], CompareOptions.IgnoreCase) == 0)
+ {
+ return (i + 1);
+ }
+ }
+ return (-1);
+ }
+
+
+ internal String[] EraNames
+ {
+ get
+ {
+ if (this.m_eraNames == null)
+ {
+ this.m_eraNames = _cultureData.EraNames(Calendar.ID); ;
+ }
+ return (this.m_eraNames);
+ }
+ }
+
+ /*=================================GetEraName==========================
+ **Action: Get the name of the era for the specified era value.
+ **Returns: The name of the specified era.
+ **Arguments:
+ ** era the era value.
+ **Exceptions:
+ ** ArguementException if the era valie is invalid.
+ ============================================================================*/
+
+ // Era names are 1 indexed
+ public String GetEraName(int era)
+ {
+ if (era == Calendar.CurrentEra)
+ {
+ era = Calendar.CurrentEraValue;
+ }
+
+ // The following is based on the assumption that the era value is starting from 1, and has a
+ // serial values.
+ // If that ever changes, the code has to be changed.
+ if ((--era) < EraNames.Length && (era >= 0))
+ {
+ return (m_eraNames[era]);
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal String[] AbbreviatedEraNames
+ {
+ get
+ {
+ if (this.m_abbrevEraNames == null)
+ {
+ this.m_abbrevEraNames = _cultureData.AbbrevEraNames(Calendar.ID);
+ }
+ return (this.m_abbrevEraNames);
+ }
+ }
+
+ // Era names are 1 indexed
+ public String GetAbbreviatedEraName(int era)
+ {
+ if (AbbreviatedEraNames.Length == 0)
+ {
+ // If abbreviation era name is not used in this culture,
+ // return the full era name.
+ return (GetEraName(era));
+ }
+ if (era == Calendar.CurrentEra)
+ {
+ era = Calendar.CurrentEraValue;
+ }
+ if ((--era) < m_abbrevEraNames.Length && (era >= 0))
+ {
+ return (m_abbrevEraNames[era]);
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal String[] AbbreviatedEnglishEraNames
+ {
+ get
+ {
+ if (this.m_abbrevEnglishEraNames == null)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
+ this.m_abbrevEnglishEraNames = _cultureData.AbbreviatedEnglishEraNames(Calendar.ID);
+ }
+ return (this.m_abbrevEnglishEraNames);
+ }
+ }
+
+ // Note that cultureData derives this from the short date format (unless someone's set this previously)
+ // Note that this property is quite undesirable.
+ public string DateSeparator
+ {
+ get
+ {
+ if (dateSeparator == null)
+ {
+ dateSeparator = _cultureData.DateSeparator(Calendar.ID);
+ }
+ Debug.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
+ return dateSeparator;
+ }
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
+ }
+ ClearTokenHashTable();
+ dateSeparator = value;
+ }
+ }
+
+ public DayOfWeek FirstDayOfWeek
+ {
+ get
+ {
+ if (this.firstDayOfWeek == -1)
+ {
+ this.firstDayOfWeek = _cultureData.IFIRSTDAYOFWEEK;
+ }
+ Debug.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
+
+ return ((DayOfWeek)this.firstDayOfWeek);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday)
+ {
+ firstDayOfWeek = (int)value;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(value), SR.Format(SR.ArgumentOutOfRange_Range,
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ }
+ }
+
+ public CalendarWeekRule CalendarWeekRule
+ {
+ get
+ {
+ if (this.calendarWeekRule == -1)
+ {
+ this.calendarWeekRule = _cultureData.IFIRSTWEEKOFYEAR;
+ }
+ Debug.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
+ return ((CalendarWeekRule)this.calendarWeekRule);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek)
+ {
+ calendarWeekRule = (int)value;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(value), SR.Format(SR.ArgumentOutOfRange_Range,
+ CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
+ }
+ }
+ }
+
+ public String FullDateTimePattern
+ {
+ get
+ {
+ if (fullDateTimePattern == null)
+ {
+ fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
+ }
+ return (fullDateTimePattern);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+ fullDateTimePattern = value;
+ }
+ }
+
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String LongDatePattern
+ {
+ get
+ {
+ // Initialize our long date pattern from the 1st array value if not set
+ if (this.longDatePattern == null)
+ {
+ // Initialize our data
+ this.longDatePattern = this.UnclonedLongDatePatterns[0];
+ }
+
+ return this.longDatePattern;
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+
+ // Remember the new string
+ this.longDatePattern = value;
+
+ // Clear the token hash table
+ ClearTokenHashTable();
+
+ // Clean up cached values that will be affected by this property.
+ this.fullDateTimePattern = null;
+ }
+ }
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String LongTimePattern
+ {
+ get
+ {
+ // Initialize our long time pattern from the 1st array value if not set
+ if (this.longTimePattern == null)
+ {
+ // Initialize our data
+ this.longTimePattern = this.UnclonedLongTimePatterns[0];
+ }
+
+ return this.longTimePattern;
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+
+ // Remember the new string
+ this.longTimePattern = value;
+
+ // Clear the token hash table
+ ClearTokenHashTable();
+
+ // Clean up cached values that will be affected by this property.
+ this.fullDateTimePattern = null; // Full date = long date + long Time
+ this.generalLongTimePattern = null; // General long date = short date + long Time
+ this.dateTimeOffsetPattern = null;
+ }
+ }
+
+
+ // Note: just to be confusing there's only 1 month day pattern, not a whole list
+ public String MonthDayPattern
+ {
+ get
+ {
+ if (this.monthDayPattern == null)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
+ this.monthDayPattern = _cultureData.MonthDay(Calendar.ID);
+ }
+ Debug.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
+ return (this.monthDayPattern);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+
+ this.monthDayPattern = value;
+ }
+ }
+
+
+ public String PMDesignator
+ {
+ get
+ {
+ if (this.pmDesignator == null)
+ {
+ this.pmDesignator = _cultureData.SPM2359;
+ }
+ Debug.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
+ return (this.pmDesignator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+ ClearTokenHashTable();
+
+ pmDesignator = value;
+ }
+ }
+
+
+ public String RFC1123Pattern
+ {
+ get
+ {
+ return (rfc1123Pattern);
+ }
+ }
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String ShortDatePattern
+ {
+ get
+ {
+ // Initialize our short date pattern from the 1st array value if not set
+ if (this.shortDatePattern == null)
+ {
+ // Initialize our data
+ this.shortDatePattern = this.UnclonedShortDatePatterns[0];
+ }
+
+ return this.shortDatePattern;
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+
+ // Remember the new string
+ this.shortDatePattern = value;
+
+ // Clear the token hash table, note that even short dates could require this
+ ClearTokenHashTable();
+
+ // Clean up cached values that will be affected by this property.
+ generalLongTimePattern = null; // General long time = short date + long time
+ generalShortTimePattern = null; // General short time = short date + short Time
+ dateTimeOffsetPattern = null;
+ }
+ }
+
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String ShortTimePattern
+ {
+ get
+ {
+ // Initialize our short time pattern from the 1st array value if not set
+ if (this.shortTimePattern == null)
+ {
+ // Initialize our data
+ this.shortTimePattern = this.UnclonedShortTimePatterns[0];
+ }
+ return this.shortTimePattern;
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+
+ // Remember the new string
+ this.shortTimePattern = value;
+
+ // Clear the token hash table, note that even short times could require this
+ ClearTokenHashTable();
+
+ // Clean up cached values that will be affected by this property.
+ generalShortTimePattern = null; // General short date = short date + short time.
+ }
+ }
+
+
+ public String SortableDateTimePattern
+ {
+ get
+ {
+ return (sortableDateTimePattern);
+ }
+ }
+
+ /*=================================GeneralShortTimePattern=====================
+ **Property: Return the pattern for 'g' general format: shortDate + short time
+ **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
+ ** We put this internal property here so that we can avoid doing the
+ ** concatation every time somebody asks for the general format.
+ ==============================================================================*/
+
+ internal String GeneralShortTimePattern
+ {
+ get
+ {
+ if (generalShortTimePattern == null)
+ {
+ generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern;
+ }
+ return (generalShortTimePattern);
+ }
+ }
+
+ /*=================================GeneralLongTimePattern=====================
+ **Property: Return the pattern for 'g' general format: shortDate + Long time
+ **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
+ ** We put this internal property here so that we can avoid doing the
+ ** concatation every time somebody asks for the general format.
+ ==============================================================================*/
+
+ internal String GeneralLongTimePattern
+ {
+ get
+ {
+ if (generalLongTimePattern == null)
+ {
+ generalLongTimePattern = ShortDatePattern + " " + LongTimePattern;
+ }
+ return (generalLongTimePattern);
+ }
+ }
+
+ /*=================================DateTimeOffsetPattern==========================
+ **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
+ **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
+ ** We put this internal property here so that we can avoid doing the
+ ** concatation every time somebody uses this form
+ ==============================================================================*/
+
+ internal String DateTimeOffsetPattern
+ {
+ get
+ {
+ if (dateTimeOffsetPattern == null)
+ {
+ string dateTimePattern = ShortDatePattern + " " + LongTimePattern;
+
+ /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to append a time zone offset */
+
+ bool foundZ = false;
+ bool inQuote = false;
+ char quote = '\'';
+ for (int i = 0; !foundZ && i < LongTimePattern.Length; i++)
+ {
+ switch (LongTimePattern[i])
+ {
+ case 'z':
+ /* if we aren't in a quote, we've found a z */
+ foundZ = !inQuote;
+ /* we'll fall out of the loop now because the test includes !foundZ */
+ break;
+ case '\'':
+ case '\"':
+ if (inQuote && (quote == LongTimePattern[i]))
+ {
+ /* we were in a quote and found a matching exit quote, so we are outside a quote now */
+ inQuote = false;
+ }
+ else if (!inQuote)
+ {
+ quote = LongTimePattern[i];
+ inQuote = true;
+ }
+ else
+ {
+ /* we were in a quote and saw the other type of quote character, so we are still in a quote */
+ }
+ break;
+ case '%':
+ case '\\':
+ i++; /* skip next character that is escaped by this backslash */
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!foundZ)
+ {
+ dateTimePattern = dateTimePattern + " zzz";
+ }
+
+ dateTimeOffsetPattern = dateTimePattern;
+ }
+ return (dateTimeOffsetPattern);
+ }
+ }
+
+ // Note that cultureData derives this from the long time format (unless someone's set this previously)
+ // Note that this property is quite undesirable.
+ public string TimeSeparator
+ {
+ get
+ {
+ if (timeSeparator == null)
+ {
+ timeSeparator = _cultureData.TimeSeparator;
+ }
+ Debug.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
+ return (timeSeparator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
+ }
+
+ ClearTokenHashTable();
+
+ timeSeparator = value;
+ }
+ }
+
+ public String UniversalSortableDateTimePattern
+ {
+ get
+ {
+ return (universalSortableDateTimePattern);
+ }
+ }
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String YearMonthPattern
+ {
+ get
+ {
+ // Initialize our year/month pattern from the 1st array value if not set
+ if (this.yearMonthPattern == null)
+ {
+ // Initialize our data
+ this.yearMonthPattern = this.UnclonedYearMonthPatterns[0];
+ }
+ return this.yearMonthPattern;
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+
+ // Remember the new string
+ this.yearMonthPattern = value;
+
+ // Clear the token hash table, note that even short times could require this
+ ClearTokenHashTable();
+ }
+ }
+
+ //
+ // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value"
+ //
+ private static void CheckNullValue(String[] values, int length)
+ {
+ Debug.Assert(values != null, "value != null");
+ Debug.Assert(values.Length >= length);
+ for (int i = 0; i < length; i++)
+ {
+ if (values[i] == null)
+ {
+ throw new ArgumentNullException("value",
+ SR.ArgumentNull_ArrayValue);
+ }
+ }
+ }
+
+
+ public String[] AbbreviatedDayNames
+ {
+ get
+ {
+ return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
+ }
+ CheckNullValue(value, value.Length);
+ ClearTokenHashTable();
+
+ abbreviatedDayNames = value;
+ }
+ }
+
+ // Returns the string array of the one-letter day of week names.
+ public String[] ShortestDayNames
+ {
+ get
+ {
+ return ((String[])internalGetSuperShortDayNames().Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
+ }
+ CheckNullValue(value, value.Length);
+ this.m_superShortDayNames = value;
+ }
+ }
+
+
+ public String[] DayNames
+ {
+ get
+ {
+ return ((String[])internalGetDayOfWeekNames().Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
+ }
+ CheckNullValue(value, value.Length);
+ ClearTokenHashTable();
+
+ dayNames = value;
+ }
+ }
+
+
+ public String[] AbbreviatedMonthNames
+ {
+ get
+ {
+ return ((String[])internalGetAbbreviatedMonthNames().Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
+ }
+ CheckNullValue(value, value.Length - 1);
+ ClearTokenHashTable();
+ abbreviatedMonthNames = value;
+ }
+ }
+
+
+ public String[] MonthNames
+ {
+ get
+ {
+ return ((String[])internalGetMonthNames().Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
+ }
+ CheckNullValue(value, value.Length - 1);
+ monthNames = value;
+ ClearTokenHashTable();
+ }
+ }
+
+ // Whitespaces that we allow in the month names.
+ // U+00a0 is non-breaking space.
+ private static readonly char[] s_monthSpaces = { ' ', '\u00a0' };
+
+ internal bool HasSpacesInMonthNames
+ {
+ get
+ {
+ return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0;
+ }
+ }
+
+ internal bool HasSpacesInDayNames
+ {
+ get
+ {
+ return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0;
+ }
+ }
+
+
+ //
+ // internalGetMonthName
+ //
+ // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form
+ // or full form.
+ // Arguments:
+ // month
+ // style To indicate a form like regular/genitive/month name in a leap year.
+ // abbreviated When true, return abbreviated form. Otherwise, return a full form.
+ // Exceptions:
+ // ArgumentOutOfRangeException When month name is invalid.
+ //
+ internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated)
+ {
+ //
+ // Right now, style is mutual exclusive, but I make the style to be flag so that
+ // maybe we can combine flag if there is such a need.
+ //
+ String[] monthNamesArray = null;
+ switch (style)
+ {
+ case MonthNameStyles.Genitive:
+ monthNamesArray = internalGetGenitiveMonthNames(abbreviated);
+ break;
+ case MonthNameStyles.LeapYear:
+ monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/);
+ break;
+ default:
+ monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames() : internalGetMonthNames());
+ break;
+ }
+ // The month range is from 1 ~ this.m_monthNames.Length
+ // (actually is 13 right now for all cases)
+ if ((month < 1) || (month > monthNamesArray.Length))
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
+ 1, monthNamesArray.Length));
+ }
+ return (monthNamesArray[month - 1]);
+ }
+
+ //
+ // internalGetGenitiveMonthNames
+ //
+ // Action: Retrieve the array which contains the month names in genitive form.
+ // If this culture does not use the gentive form, the normal month name is returned.
+ // Arguments:
+ // abbreviated When true, return abbreviated form. Otherwise, return a full form.
+ //
+ private String[] internalGetGenitiveMonthNames(bool abbreviated)
+ {
+ if (abbreviated)
+ {
+ if (this.m_genitiveAbbreviatedMonthNames == null)
+ {
+ this.m_genitiveAbbreviatedMonthNames = _cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID);
+ Debug.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13,
+ "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year");
+ }
+ return (this.m_genitiveAbbreviatedMonthNames);
+ }
+
+ if (this.genitiveMonthNames == null)
+ {
+ this.genitiveMonthNames = _cultureData.GenitiveMonthNames(this.Calendar.ID);
+ Debug.Assert(this.genitiveMonthNames.Length == 13,
+ "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year");
+ }
+ return (this.genitiveMonthNames);
+ }
+
+ //
+ // internalGetLeapYearMonthNames
+ //
+ // Actions: Retrieve the month names used in a leap year.
+ // If this culture does not have different month names in a leap year, the normal month name is returned.
+ // Arguments: None. (can use abbreviated later if needed)
+ //
+ internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/)
+ {
+ if (this.leapYearMonthNames == null)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0");
+ this.leapYearMonthNames = _cultureData.LeapYearMonthNames(Calendar.ID);
+ Debug.Assert(this.leapYearMonthNames.Length == 13,
+ "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names");
+ }
+ return (leapYearMonthNames);
+ }
+
+
+ public String GetAbbreviatedDayName(DayOfWeek dayofweek)
+ {
+ if ((int)dayofweek < 0 || (int)dayofweek > 6)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range,
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ //
+ // Don't call the public property AbbreviatedDayNames here since a clone is needed in that
+ // property, so it will be slower. Instead, use GetAbbreviatedDayOfWeekNames() directly.
+ //
+ return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]);
+ }
+
+ // Returns the super short day of week names for the specified day of week.
+ public string GetShortestDayName(DayOfWeek dayOfWeek)
+ {
+ if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(dayOfWeek), SR.Format(SR.ArgumentOutOfRange_Range,
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ //
+ // Don't call the public property SuperShortDayNames here since a clone is needed in that
+ // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly.
+ //
+ return (internalGetSuperShortDayNames()[(int)dayOfWeek]);
+ }
+
+ // Get all possible combination of inputs
+ private static String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
+ {
+ Debug.Assert(patterns1 != null);
+ Debug.Assert(patterns2 != null);
+
+ // Get array size
+ String[] result = new String[patterns1.Length * patterns2.Length];
+
+ // Counter of actual results
+ int k = 0;
+ for (int i = 0; i < patterns1.Length; i++)
+ {
+ for (int j = 0; j < patterns2.Length; j++)
+ {
+ // Can't combine if null or empty
+ result[k++] = patterns1[i] + connectString + patterns2[j];
+ }
+ }
+
+ // Return the combinations
+ return (result);
+ }
+
+ public string[] GetAllDateTimePatterns()
+ {
+ List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
+
+ for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++)
+ {
+ String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]);
+ for (int j = 0; j < strings.Length; j++)
+ {
+ results.Add(strings[j]);
+ }
+ }
+ return results.ToArray();
+ }
+
+ public string[] GetAllDateTimePatterns(char format)
+ {
+ String[] result = null;
+
+ switch (format)
+ {
+ case 'd':
+ result = this.AllShortDatePatterns;
+ break;
+ case 'D':
+ result = this.AllLongDatePatterns;
+ break;
+ case 'f':
+ result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " ");
+ break;
+ case 'F':
+ case 'U':
+ result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " ");
+ break;
+ case 'g':
+ result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " ");
+ break;
+ case 'G':
+ result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " ");
+ break;
+ case 'm':
+ case 'M':
+ result = new String[] { MonthDayPattern };
+ break;
+ case 'o':
+ case 'O':
+ result = new String[] { RoundtripFormat };
+ break;
+ case 'r':
+ case 'R':
+ result = new String[] { rfc1123Pattern };
+ break;
+ case 's':
+ result = new String[] { sortableDateTimePattern };
+ break;
+ case 't':
+ result = this.AllShortTimePatterns;
+ break;
+ case 'T':
+ result = this.AllLongTimePatterns;
+ break;
+ case 'u':
+ result = new String[] { UniversalSortableDateTimePattern };
+ break;
+ case 'y':
+ case 'Y':
+ result = this.AllYearMonthPatterns;
+ break;
+ default:
+ throw new ArgumentException(SR.Format(SR.Format_BadFormatSpecifier, format), nameof(format));
+ }
+ return (result);
+ }
+
+
+ public String GetDayName(DayOfWeek dayofweek)
+ {
+ if ((int)dayofweek < 0 || (int)dayofweek > 6)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range,
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+
+ // Use the internal one so that we don't clone the array unnecessarily
+ return (internalGetDayOfWeekNames()[(int)dayofweek]);
+ }
+
+ public String GetAbbreviatedMonthName(int month)
+ {
+ if (month < 1 || month > 13)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
+ 1, 13));
+ }
+ // Use the internal one so we don't clone the array unnecessarily
+ return (internalGetAbbreviatedMonthNames()[month - 1]);
+ }
+
+ public String GetMonthName(int month)
+ {
+ if (month < 1 || month > 13)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
+ 1, 13));
+ }
+ // Use the internal one so we don't clone the array unnecessarily
+ return (internalGetMonthNames()[month - 1]);
+ }
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ //
+ // The resulting [] can get returned to the calling app, so clone it.
+ private static string[] GetMergedPatterns(string[] patterns, string defaultPattern)
+ {
+ Debug.Assert(patterns != null && patterns.Length > 0,
+ "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
+ Debug.Assert(defaultPattern != null,
+ "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string");
+
+ // If the default happens to be the first in the list just return (a cloned) copy
+ if (defaultPattern == patterns[0])
+ {
+ return (string[])patterns.Clone();
+ }
+
+ // We either need a bigger list, or the pattern from the list.
+ int i;
+ for (i = 0; i < patterns.Length; i++)
+ {
+ // Stop if we found it
+ if (defaultPattern == patterns[i])
+ break;
+ }
+
+ // Either way we're going to need a new array
+ string[] newPatterns;
+
+ // Did we find it
+ if (i < patterns.Length)
+ {
+ // Found it, output will be same size
+ newPatterns = (string[])patterns.Clone();
+
+ // Have to move [0] item to [i] so we can re-write default at [0]
+ // (remember defaultPattern == [i] so this is OK)
+ newPatterns[i] = newPatterns[0];
+ }
+ else
+ {
+ // Not found, make room for it
+ newPatterns = new String[patterns.Length + 1];
+
+ // Copy existing array
+ Array.Copy(patterns, 0, newPatterns, 1, patterns.Length);
+ }
+
+ // Remember the default
+ newPatterns[0] = defaultPattern;
+
+ // Return the reconstructed list
+ return newPatterns;
+ }
+
+ // Needed by DateTimeFormatInfo and DateTimeFormat
+ internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
+ internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz";
+
+ // Default string isn't necessarily in our string array, so get the
+ // merged patterns of both
+ private String[] AllYearMonthPatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern);
+ }
+ }
+
+ private String[] AllShortDatePatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern);
+ }
+ }
+
+ private String[] AllShortTimePatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern);
+ }
+ }
+
+ private String[] AllLongDatePatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern);
+ }
+ }
+
+ private String[] AllLongTimePatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern);
+ }
+ }
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllYearMonthPatterns
+ private String[] UnclonedYearMonthPatterns
+ {
+ get
+ {
+ if (allYearMonthPatterns == null)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0");
+ this.allYearMonthPatterns = _cultureData.YearMonths(this.Calendar.ID);
+ Debug.Assert(this.allYearMonthPatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns");
+ }
+
+ return allYearMonthPatterns;
+ }
+ }
+
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllShortDatePatterns
+ private String[] UnclonedShortDatePatterns
+ {
+ get
+ {
+ if (allShortDatePatterns == null)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0");
+ this.allShortDatePatterns = _cultureData.ShortDates(this.Calendar.ID);
+ Debug.Assert(this.allShortDatePatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns");
+ }
+
+ return this.allShortDatePatterns;
+ }
+ }
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllLongDatePatterns
+ private String[] UnclonedLongDatePatterns
+ {
+ get
+ {
+ if (allLongDatePatterns == null)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0");
+ this.allLongDatePatterns = _cultureData.LongDates(this.Calendar.ID);
+ Debug.Assert(this.allLongDatePatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns");
+ }
+
+ return this.allLongDatePatterns;
+ }
+ }
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllShortTimePatterns
+ private String[] UnclonedShortTimePatterns
+ {
+ get
+ {
+ if (this.allShortTimePatterns == null)
+ {
+ this.allShortTimePatterns = _cultureData.ShortTimes;
+ Debug.Assert(this.allShortTimePatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns");
+ }
+
+ return this.allShortTimePatterns;
+ }
+ }
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllLongTimePatterns
+ private String[] UnclonedLongTimePatterns
+ {
+ get
+ {
+ if (this.allLongTimePatterns == null)
+ {
+ this.allLongTimePatterns = _cultureData.LongTimes;
+ Debug.Assert(this.allLongTimePatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns");
+ }
+
+ return this.allLongTimePatterns;
+ }
+ }
+
+ public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi)
+ {
+ if (dtfi == null)
+ {
+ throw new ArgumentNullException(nameof(dtfi),
+ SR.ArgumentNull_Obj);
+ }
+ if (dtfi.IsReadOnly)
+ {
+ return (dtfi);
+ }
+ DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone());
+ // We can use the data member calendar in the setter, instead of the property Calendar,
+ // since the cloned copy should have the same state as the original copy.
+ newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar);
+ newInfo._isReadOnly = true;
+ return (newInfo);
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return (_isReadOnly);
+ }
+ }
+
+ // Return the native name for the calendar in DTFI.Calendar. The native name is referred to
+ // the culture used to create the DTFI. E.g. in the following example, the native language is Japanese.
+ // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar();
+ // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar.
+ // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized);
+ // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar.
+ public string NativeCalendarName
+ {
+ get
+ {
+ return _cultureData.CalendarName(Calendar.ID);
+ }
+ }
+
+ //
+ // Used by custom cultures and others to set the list of available formats. Note that none of them are
+ // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items
+ // from the list.
+ //
+ // Most of the format characters that can be used in GetAllDateTimePatterns are
+ // not really needed since they are one of the following:
+ //
+ // r/R/s/u locale-independent constants -- cannot be changed!
+ // m/M/y/Y fields with a single string in them -- that can be set through props directly
+ // f/F/g/G/U derived fields based on combinations of various of the below formats
+ //
+ // NOTE: No special validation is done here beyond what is done when the actual respective fields
+ // are used (what would be the point of disallowing here what we allow in the appropriate property?)
+ //
+ // WARNING: If more validation is ever done in one place, it should be done in the other.
+ //
+ public void SetAllDateTimePatterns(String[] patterns, char format)
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+
+ if (patterns == null)
+ {
+ throw new ArgumentNullException(nameof(patterns), SR.ArgumentNull_Array);
+ }
+
+ if (patterns.Length == 0)
+ {
+ throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns));
+ }
+
+
+ for (int i = 0; i < patterns.Length; i++)
+ {
+ if (patterns[i] == null)
+ {
+ throw new ArgumentNullException("patterns[" + i + "]", SR.ArgumentNull_ArrayValue);
+ }
+ }
+
+ // Remember the patterns, and use the 1st as default
+ switch (format)
+ {
+ case 'd':
+ allShortDatePatterns = patterns;
+ shortDatePattern = allShortDatePatterns[0];
+ break;
+
+ case 'D':
+ allLongDatePatterns = patterns;
+ longDatePattern = allLongDatePatterns[0];
+ break;
+
+ case 't':
+ allShortTimePatterns = patterns;
+ shortTimePattern = allShortTimePatterns[0];
+ break;
+
+ case 'T':
+ allLongTimePatterns = patterns;
+ longTimePattern = allLongTimePatterns[0];
+ break;
+
+ case 'y':
+ case 'Y':
+ allYearMonthPatterns = patterns;
+ yearMonthPattern = allYearMonthPatterns[0];
+ break;
+
+ default:
+ throw new ArgumentException(SR.Format(SR.Format_BadFormatSpecifier, format), nameof(format));
+ }
+
+ // Clear the token hash table, note that even short dates could require this
+ ClearTokenHashTable();
+ }
+
+ public String[] AbbreviatedMonthGenitiveNames
+ {
+ get
+ {
+ return ((String[])internalGetGenitiveMonthNames(true).Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
+ }
+ CheckNullValue(value, value.Length - 1);
+ ClearTokenHashTable();
+ this.m_genitiveAbbreviatedMonthNames = value;
+ }
+ }
+
+ public String[] MonthGenitiveNames
+ {
+ get
+ {
+ return ((String[])internalGetGenitiveMonthNames(false).Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
+ }
+ CheckNullValue(value, value.Length - 1);
+ genitiveMonthNames = value;
+ ClearTokenHashTable();
+ }
+ }
+
+ //
+ // Positive TimeSpan Pattern
+ //
+ private string _fullTimeSpanPositivePattern;
+ internal String FullTimeSpanPositivePattern
+ {
+ get
+ {
+ if (_fullTimeSpanPositivePattern == null)
+ {
+ CultureData cultureDataWithoutUserOverrides;
+ if (_cultureData.UseUserOverride)
+ cultureDataWithoutUserOverrides = CultureData.GetCultureData(_cultureData.CultureName, false);
+ else
+ cultureDataWithoutUserOverrides = _cultureData;
+ String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
+
+ _fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
+ }
+ return _fullTimeSpanPositivePattern;
+ }
+ }
+
+ //
+ // Negative TimeSpan Pattern
+ //
+ private string _fullTimeSpanNegativePattern;
+ internal String FullTimeSpanNegativePattern
+ {
+ get
+ {
+ if (_fullTimeSpanNegativePattern == null)
+ _fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
+ return _fullTimeSpanNegativePattern;
+ }
+ }
+
+ //
+ // Get suitable CompareInfo from current DTFI object.
+ //
+ internal CompareInfo CompareInfo
+ {
+ get
+ {
+ if (_compareInfo == null)
+ {
+ // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the
+ // CompareInfo cache. otherwise we would just create CompareInfo using _cultureData.
+ _compareInfo = CompareInfo.GetCompareInfo(_cultureData.SCOMPAREINFO);
+ }
+
+ return _compareInfo;
+ }
+ }
+
+
+ internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite
+ | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault
+ | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal
+ | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind);
+
+ internal static void ValidateStyles(DateTimeStyles style, String parameterName)
+ {
+ if ((style & InvalidDateTimeStyles) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName);
+ }
+ if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0))
+ {
+ throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName);
+ }
+ if (((style & DateTimeStyles.RoundtripKind) != 0)
+ && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0))
+ {
+ throw new ArgumentException(SR.Argument_ConflictingDateTimeRoundtripStyles, parameterName);
+ }
+ }
+
+ //
+ // Actions: Return the internal flag used in formatting and parsing.
+ // The flag can be used to indicate things like if genitive forms is used in this DTFi, or if leap year gets different month names.
+ //
+ internal DateTimeFormatFlags FormatFlags => formatFlags != DateTimeFormatFlags.NotInitialized ? formatFlags : InitializeFormatFlags();
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private DateTimeFormatFlags InitializeFormatFlags()
+ {
+ // Build the format flags from the data in this DTFI
+ formatFlags =
+ (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth(
+ MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)) |
+ (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames(
+ MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)) |
+ (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayNames) |
+ (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID);
+ return formatFlags;
+ }
+
+ internal Boolean HasForceTwoDigitYears
+ {
+ get
+ {
+ switch (calendar.ID)
+ {
+ // Handle Japanese and Taiwan cases.
+ // If is y/yy, do not get (year % 100). "y" will print
+ // year without leading zero. "yy" will print year with two-digit in leading zero.
+ // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero.
+ // So year 5 is "05", and year 125 is "125".
+ // The reason for not doing (year % 100) is for Taiwan calendar.
+ // If year 125, then output 125 and not 25.
+ // Note: OS uses "yyyy" for Taiwan calendar by default.
+ case (CalendarId.JAPAN):
+ case (CalendarId.TAIWAN):
+ return true;
+ }
+ return false;
+ }
+ }
+
+ // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar.
+ internal Boolean HasYearMonthAdjustment
+ {
+ get
+ {
+ return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0);
+ }
+ }
+
+ // This is a callback that the parser can make back into the DTFI to let it fiddle with special
+ // cases associated with that culture or calendar. Currently this only has special cases for
+ // the Hebrew calendar, but this could be extended to other cultures.
+ //
+ // The return value is whether the year and month are actually valid for this calendar.
+ internal Boolean YearMonthAdjustment(ref int year, ref int month, Boolean parsedMonthName)
+ {
+ if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0)
+ {
+ // Special rules to fix up the Hebrew year/month
+
+ // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000.
+ // E.g. if the year is 5763, we only format as 763.
+ if (year < 1000)
+ {
+ year += 5000;
+ }
+
+ // Because we need to calculate leap year, we should fall out now for an invalid year.
+ if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSupportedDateTime))
+ {
+ return false;
+ }
+
+ // To handle leap months, the set of month names in the symbol table does not always correspond to the numbers.
+ // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid and
+ // shuffle the other months down.
+ if (parsedMonthName)
+ {
+ if (!Calendar.IsLeapYear(year))
+ {
+ if (month >= 8)
+ {
+ month--;
+ }
+ else if (month == 7)
+ {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ //
+ // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens.
+ //
+ private TokenHashValue[] _dtfiTokenHash;
+
+ private const int TOKEN_HASH_SIZE = 199;
+ private const int SECOND_PRIME = 197;
+ private const String dateSeparatorOrTimeZoneOffset = "-";
+ private const String invariantDateSeparator = "/";
+ private const String invariantTimeSeparator = ":";
+
+ //
+ // Common Ignorable Symbols
+ //
+ internal const String IgnorablePeriod = ".";
+ internal const String IgnorableComma = ",";
+
+ //
+ // Year/Month/Day suffixes
+ //
+ internal const String CJKYearSuff = "\u5e74";
+ internal const String CJKMonthSuff = "\u6708";
+ internal const String CJKDaySuff = "\u65e5";
+
+ internal const String KoreanYearSuff = "\ub144";
+ internal const String KoreanMonthSuff = "\uc6d4";
+ internal const String KoreanDaySuff = "\uc77c";
+
+ internal const String KoreanHourSuff = "\uc2dc";
+ internal const String KoreanMinuteSuff = "\ubd84";
+ internal const String KoreanSecondSuff = "\ucd08";
+
+ internal const String CJKHourSuff = "\u6642";
+ internal const String ChineseHourSuff = "\u65f6";
+
+ internal const String CJKMinuteSuff = "\u5206";
+ internal const String CJKSecondSuff = "\u79d2";
+
+ internal const String LocalTimeMark = "T";
+
+ internal const String GMTName = "GMT";
+ internal const String ZuluName = "Z";
+
+ internal const String KoreanLangName = "ko";
+ internal const String JapaneseLangName = "ja";
+ internal const String EnglishLangName = "en";
+
+ private static volatile DateTimeFormatInfo s_jajpDTFI;
+ private static volatile DateTimeFormatInfo s_zhtwDTFI;
+
+ //
+ // Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse
+ // date string with Japanese era name correctly even when the supplied DTFI
+ // does not use Japanese calendar.
+ // The created instance is stored in global s_jajpDTFI.
+ //
+ internal static DateTimeFormatInfo GetJapaneseCalendarDTFI()
+ {
+ DateTimeFormatInfo temp = s_jajpDTFI;
+ if (temp == null)
+ {
+ temp = new CultureInfo("ja-JP", false).DateTimeFormat;
+ temp.Calendar = JapaneseCalendar.GetDefaultInstance();
+ s_jajpDTFI = temp;
+ }
+ return (temp);
+ }
+
+ // Create a Taiwan DTFI which uses TaiwanCalendar. This is used to parse
+ // date string with era name correctly even when the supplied DTFI
+ // does not use Taiwan calendar.
+ // The created instance is stored in global s_zhtwDTFI.
+ internal static DateTimeFormatInfo GetTaiwanCalendarDTFI()
+ {
+ DateTimeFormatInfo temp = s_zhtwDTFI;
+ if (temp == null)
+ {
+ temp = new CultureInfo("zh-TW", false).DateTimeFormat;
+ temp.Calendar = TaiwanCalendar.GetDefaultInstance();
+ s_zhtwDTFI = temp;
+ }
+ return (temp);
+ }
+
+
+ // DTFI properties should call this when the setter are called.
+ private void ClearTokenHashTable()
+ {
+ _dtfiTokenHash = null;
+ formatFlags = DateTimeFormatFlags.NotInitialized;
+ }
+
+ internal TokenHashValue[] CreateTokenHashTable()
+ {
+ TokenHashValue[] temp = _dtfiTokenHash;
+ if (temp == null)
+ {
+ temp = new TokenHashValue[TOKEN_HASH_SIZE];
+
+ bool koreanLanguage = LanguageName.Equals(KoreanLangName);
+
+ string sep = this.TimeSeparator.Trim();
+ if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0);
+ if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0);
+
+ if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep)
+ {
+ //
+ // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or ChineseHourSuff for some cultures like
+ // ja-JP and ko-KR. In these cases having the same symbol inserted into the hash table with multiple TokenTypes causes undesirable
+ // DateTime.Parse behavior. For instance, the DateTimeFormatInfo.Tokenize() method might return SEP_DateOrOffset for KoreanHourSuff
+ // instead of SEP_HourSuff.
+ //
+ InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0);
+ }
+
+ InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
+ InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
+
+ // TODO: This ignores similar custom cultures
+ if (LanguageName.Equals("sq"))
+ {
+ // Albanian allows time formats like "12:00.PD"
+ InsertHash(temp, IgnorablePeriod + this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
+ InsertHash(temp, IgnorablePeriod + this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
+ }
+
+ // CJK suffix
+ InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0);
+ InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0);
+ InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0);
+ InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0);
+ InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0);
+ InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0);
+
+ InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0);
+ InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0);
+ InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0);
+ InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0);
+
+ // TODO: This ignores other custom cultures that might want to do something similar
+ if (koreanLanguage)
+ {
+ // Korean suffix
+ InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0);
+ InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0);
+ InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0);
+ }
+
+ if (LanguageName.Equals("ky"))
+ {
+ // For some cultures, the date separator works more like a comma, being allowed before or after any date part
+ InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0);
+ }
+ else
+ {
+ InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0);
+ }
+
+ String[] dateWords = null;
+ DateTimeFormatInfoScanner scanner = null;
+
+ // We need to rescan the date words since we're always synthetic
+ scanner = new DateTimeFormatInfoScanner();
+ dateWords = scanner.GetDateWordsOfDTFI(this);
+ // Ensure the formatflags is initialized.
+ DateTimeFormatFlags flag = FormatFlags;
+
+ // For some cultures, the date separator works more like a comma, being allowed before or after any date part.
+ // In these cultures, we do not use normal date separator since we disallow date separator after a date terminal state.
+ // This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date separator as ignorable symbol.
+ bool useDateSepAsIgnorableSymbol = false;
+
+ String monthPostfix = null;
+ if (dateWords != null)
+ {
+ // There are DateWords. It could be a real date word (such as "de"), or a monthPostfix.
+ // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix.
+ for (int i = 0; i < dateWords.Length; i++)
+ {
+ switch (dateWords[i][0])
+ {
+ // This is a month postfix
+ case DateTimeFormatInfoScanner.MonthPostfixChar:
+ // Get the real month postfix.
+ monthPostfix = dateWords[i].Substring(1);
+ // Add the month name + postfix into the token.
+ AddMonthNames(temp, monthPostfix);
+ break;
+ case DateTimeFormatInfoScanner.IgnorableSymbolChar:
+ String symbol = dateWords[i].Substring(1);
+ InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0);
+ if (this.DateSeparator.Trim(null).Equals(symbol))
+ {
+ // The date separator is the same as the ignorable symbol.
+ useDateSepAsIgnorableSymbol = true;
+ }
+ break;
+ default:
+ InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
+ // TODO: This ignores similar custom cultures
+ if (LanguageName.Equals("eu"))
+ {
+ // Basque has date words with leading dots
+ InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0);
+ }
+ break;
+ }
+ }
+ }
+
+ if (!useDateSepAsIgnorableSymbol)
+ {
+ // Use the normal date separator.
+ InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0);
+ }
+ // Add the regular month names.
+ AddMonthNames(temp, null);
+
+ // Add the abbreviated month names.
+ for (int i = 1; i <= 13; i++)
+ {
+ InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i);
+ }
+
+
+ if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0)
+ {
+ for (int i = 1; i <= 13; i++)
+ {
+ String str;
+ str = internalGetMonthName(i, MonthNameStyles.Genitive, false);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+ }
+
+ if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0)
+ {
+ for (int i = 1; i <= 13; i++)
+ {
+ String str;
+ str = internalGetMonthName(i, MonthNameStyles.LeapYear, false);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+ }
+
+ for (int i = 0; i < 7; i++)
+ {
+ //String str = GetDayOfWeekNames()[i];
+ // We have to call public methods here to work with inherited DTFI.
+ String str = GetDayName((DayOfWeek)i);
+ InsertHash(temp, str, TokenType.DayOfWeekToken, i);
+
+ str = GetAbbreviatedDayName((DayOfWeek)i);
+ InsertHash(temp, str, TokenType.DayOfWeekToken, i);
+ }
+
+ int[] eras = calendar.Eras;
+ for (int i = 1; i <= eras.Length; i++)
+ {
+ InsertHash(temp, GetEraName(i), TokenType.EraToken, i);
+ InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i);
+ }
+
+ // TODO: This ignores other cultures that might want to do something similar
+ if (LanguageName.Equals(JapaneseLangName))
+ {
+ // Japanese allows day of week forms like: "(Tue)"
+ for (int i = 0; i < 7; i++)
+ {
+ String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")";
+ InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i);
+ }
+ if (this.Calendar.GetType() != typeof(JapaneseCalendar))
+ {
+ // Special case for Japanese. If this is a Japanese DTFI, and the calendar is not Japanese calendar,
+ // we will check Japanese Era name as well when the calendar is Gregorian.
+ DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI();
+ for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++)
+ {
+ InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i);
+ InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i);
+ // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
+ InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i - 1], TokenType.JapaneseEraToken, i);
+ }
+ }
+ }
+ // TODO: This prohibits similar custom cultures, but we hard coded the name
+ else if (CultureName.Equals("zh-TW"))
+ {
+ DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI();
+ for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++)
+ {
+ if (twDtfi.GetEraName(i).Length > 0)
+ {
+ InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i);
+ }
+ }
+ }
+
+ InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
+ InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
+
+ // Add invariant month names and day names.
+ for (int i = 1; i <= 12; i++)
+ {
+ String str;
+ // We have to call public methods here to work with inherited DTFI.
+ // Insert the month name first, so that they are at the front of abbreviated
+ // month names.
+ str = InvariantInfo.GetMonthName(i);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ str = InvariantInfo.GetAbbreviatedMonthName(i);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+
+ for (int i = 0; i < 7; i++)
+ {
+ // We have to call public methods here to work with inherited DTFI.
+ String str = InvariantInfo.GetDayName((DayOfWeek)i);
+ InsertHash(temp, str, TokenType.DayOfWeekToken, i);
+
+ str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i);
+ InsertHash(temp, str, TokenType.DayOfWeekToken, i);
+ }
+
+ for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++)
+ {
+ // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
+ InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1);
+ }
+
+ InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0);
+ InsertHash(temp, GMTName, TokenType.TimeZoneToken, 0);
+ InsertHash(temp, ZuluName, TokenType.TimeZoneToken, 0);
+
+ InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
+ InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
+
+ _dtfiTokenHash = temp;
+ }
+ return (temp);
+ }
+
+ private void AddMonthNames(TokenHashValue[] temp, String monthPostfix)
+ {
+ for (int i = 1; i <= 13; i++)
+ {
+ String str;
+ //str = internalGetMonthName(i, MonthNameStyles.Regular, false);
+ // We have to call public methods here to work with inherited DTFI.
+ // Insert the month name first, so that they are at the front of abbreviated
+ // month names.
+ str = GetMonthName(i);
+ if (str.Length > 0)
+ {
+ if (monthPostfix != null)
+ {
+ // Insert the month name with the postfix first, so it can be matched first.
+ InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i);
+ }
+ else
+ {
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+ }
+ str = GetAbbreviatedMonthName(i);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Try to parse the current word to see if it is a Hebrew number.
+ // Tokens will be updated accordingly.
+ // This is called by the Lexer of DateTime.Parse().
+ //
+ // Unlike most of the functions in this class, the return value indicates
+ // whether or not it started to parse. The badFormat parameter indicates
+ // if parsing began, but the format was bad.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ private static bool TryParseHebrewNumber(
+ ref __DTString str,
+ out Boolean badFormat,
+ out int number)
+ {
+ number = -1;
+ badFormat = false;
+
+ int i = str.Index;
+ if (!HebrewNumber.IsDigit(str.Value[i]))
+ {
+ // If the current character is not a Hebrew digit, just return false.
+ // There is no chance that we can parse a valid Hebrew number from here.
+ return (false);
+ }
+ // The current character is a Hebrew digit. Try to parse this word as a Hebrew number.
+ HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
+ HebrewNumberParsingState state;
+
+ do
+ {
+ state = HebrewNumber.ParseByChar(str.Value[i++], ref context);
+ switch (state)
+ {
+ case HebrewNumberParsingState.InvalidHebrewNumber: // Not a valid Hebrew number.
+ case HebrewNumberParsingState.NotHebrewDigit: // The current character is not a Hebrew digit character.
+ // Break out so that we don't continue to try parse this as a Hebrew number.
+ return (false);
+ }
+ } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber));
+
+ // When we are here, we are either at the end of the string, or we find a valid Hebrew number.
+ Debug.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEndOfHebrewNumber,
+ "Invalid returned state from HebrewNumber.ParseByChar()");
+
+ if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber)
+ {
+ // We reach end of the string but we can't find a terminal state in parsing Hebrew number.
+ return (false);
+ }
+
+ // We have found a valid Hebrew number. Update the index.
+ str.Advance(i - str.Index);
+
+ // Get the final Hebrew number value from the HebrewNumberParsingContext.
+ number = context.result;
+
+ return (true);
+ }
+
+ private static bool IsHebrewChar(char ch)
+ {
+ return (ch >= '\x0590' && ch <= '\x05ff');
+ }
+
+ internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue,
+ ref __DTString str)
+ {
+ tokenType = TokenType.UnknownToken;
+ tokenValue = 0;
+
+ TokenHashValue value;
+ Debug.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
+
+ char ch = str.m_current;
+ bool isLetter = Char.IsLetter(ch);
+ if (isLetter)
+ {
+ ch = this.Culture.TextInfo.ToLower(ch);
+ if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask)
+ {
+ bool badFormat;
+ if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue))
+ {
+ if (badFormat)
+ {
+ tokenType = TokenType.UnknownToken;
+ return (false);
+ }
+ // This is a Hebrew number.
+ // Do nothing here. TryParseHebrewNumber() will update token accordingly.
+ tokenType = TokenType.HebrewNumber;
+ return (true);
+ }
+ }
+ }
+
+
+ int hashcode = ch % TOKEN_HASH_SIZE;
+ int hashProbe = 1 + ch % SECOND_PRIME;
+ int remaining = str.Length - str.Index;
+ int i = 0;
+
+ TokenHashValue[] hashTable = _dtfiTokenHash;
+ if (hashTable == null)
+ {
+ hashTable = CreateTokenHashTable();
+ }
+ do
+ {
+ value = hashTable[hashcode];
+ if (value == null)
+ {
+ // Not found.
+ break;
+ }
+ // Check this value has the right category (regular token or separator token) that we are looking for.
+ if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining)
+ {
+ bool compareStrings = true;
+ if (isLetter)
+ {
+ // If this token starts with a letter, make sure that we won't allow partial match. So you can't tokenize "MarchWed" separately.
+ // Also an optimization to avoid string comparison
+ int nextCharIndex = str.Index + value.tokenString.Length;
+ if (nextCharIndex > str.Length)
+ {
+ compareStrings = false;
+ }
+ else if (nextCharIndex < str.Length)
+ {
+ // Check word boundary. The next character should NOT be a letter.
+ char nextCh = str.Value[nextCharIndex];
+ compareStrings = !(Char.IsLetter(nextCh));
+ }
+ }
+
+ if (compareStrings &&
+ ((value.tokenString.Length == 1 && str.Value[str.Index] == value.tokenString[0]) ||
+ Culture.CompareInfo.Compare(str.Value.Slice(str.Index, value.tokenString.Length), value.tokenString, CompareOptions.IgnoreCase) == 0))
+ {
+ tokenType = value.tokenType & TokenMask;
+ tokenValue = value.tokenValue;
+ str.Advance(value.tokenString.Length);
+ return (true);
+ }
+ else if ((value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) ||
+ (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames))
+ {
+ // For month or day token, we will match the names which have spaces.
+ int matchStrLen = 0;
+ if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen))
+ {
+ tokenType = value.tokenType & TokenMask;
+ tokenValue = value.tokenValue;
+ str.Advance(matchStrLen);
+ return (true);
+ }
+ }
+ }
+ i++;
+ hashcode += hashProbe;
+ if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
+ } while (i < TOKEN_HASH_SIZE);
+
+ return (false);
+ }
+
+ private void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe)
+ {
+ // Remember the current slot.
+ TokenHashValue previousNode = hashTable[hashcode];
+
+ //// Console.WriteLine(" Insert Key: {0} in {1}", str, slotToInsert);
+ // Insert the new node into the current slot.
+ hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue); ;
+
+ while (++pos < TOKEN_HASH_SIZE)
+ {
+ hashcode += hashProbe;
+ if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
+ // Remember this slot
+ TokenHashValue temp = hashTable[hashcode];
+
+ if (temp != null && this.Culture.TextInfo.ToLower(temp.tokenString[0]) != ch)
+ {
+ continue;
+ }
+ // Put the previous slot into this slot.
+ hashTable[hashcode] = previousNode;
+ //// Console.WriteLine(" Move {0} to slot {1}", previousNode.tokenString, hashcode);
+ if (temp == null)
+ {
+ // Done
+ return;
+ }
+ previousNode = temp;
+ };
+ Debug.Fail("The hashtable is full. This should not happen.");
+ }
+
+ private void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue)
+ {
+ // The month of the 13th month is allowed to be null, so make sure that we ignore null value here.
+ if (str == null || str.Length == 0)
+ {
+ return;
+ }
+ TokenHashValue value;
+ int i = 0;
+ // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are skipped by
+ // DateTime.Parse().
+ if (Char.IsWhiteSpace(str[0]) || Char.IsWhiteSpace(str[str.Length - 1]))
+ {
+ str = str.Trim(null); // Trim white space characters.
+ // Could have space for separators
+ if (str.Length == 0)
+ return;
+ }
+ char ch = this.Culture.TextInfo.ToLower(str[0]);
+ int hashcode = ch % TOKEN_HASH_SIZE;
+ int hashProbe = 1 + ch % SECOND_PRIME;
+ do
+ {
+ value = hashTable[hashcode];
+ if (value == null)
+ {
+ //// Console.WriteLine(" Put Key: {0} in {1}", str, hashcode);
+ hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
+ return;
+ }
+ else
+ {
+ // Collision happens. Find another slot.
+ if (str.Length >= value.tokenString.Length)
+ {
+ // If there are two tokens with the same prefix, we have to make sure that the longer token should be at the front of
+ // the shorter ones.
+ if (this.CompareStringIgnoreCaseOptimized(str, 0, value.tokenString.Length, value.tokenString, 0, value.tokenString.Length))
+ {
+ if (str.Length > value.tokenString.Length)
+ {
+ // The str to be inserted has the same prefix as the current token, and str is longer.
+ // Insert str into this node, and shift every node behind it.
+ InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProbe);
+ return;
+ }
+ else
+ {
+ // Same token. If they have different types (regular token vs separator token). Add them.
+ // If we have the same regular token or separator token in the hash already, do NOT update the hash.
+ // Therefore, the order of inserting token is significant here regarding what tokenType will be kept in the hash.
+
+
+ //
+ // Check the current value of RegularToken (stored in the lower 8-bit of tokenType) , and insert the tokenType into the hash ONLY when we don't have a RegularToken yet.
+ // Also check the current value of SeparatorToken (stored in the upper 8-bit of token), and insert the tokenType into the hash ONLY when we don't have the SeparatorToken yet.
+ //
+
+ int nTokenType = (int)tokenType;
+ int nCurrentTokenTypeInHash = (int)value.tokenType;
+
+ //
+ // The folowing is the fix for the issue of throwing FormatException when "mar" is passed in string of the short date format dd/MMM/yyyy for es-MX
+ //
+
+ if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) ||
+ ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0))
+ {
+ value.tokenType |= tokenType;
+ if (tokenValue != 0)
+ {
+ value.tokenValue = tokenValue;
+ }
+ }
+ // The token to be inserted is already in the table. Skip it.
+ return;
+ }
+ }
+ }
+ }
+ //// Console.WriteLine(" COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str);
+ i++;
+ hashcode += hashProbe;
+ if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
+ } while (i < TOKEN_HASH_SIZE);
+ Debug.Fail("The hashtable is full. This should not happen.");
+ }
+
+ private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offset2, int length2)
+ {
+ // Optimize for one character cases which are common due to date and time separators (/ and :)
+ if (length1 == 1 && length2 == 1 && string1[offset1] == string2[offset2])
+ {
+ return true;
+ }
+
+ return (this.Culture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.IgnoreCase) == 0);
+ }
+
+ // class DateTimeFormatInfo
+
+ internal class TokenHashValue
+ {
+ internal String tokenString;
+ internal TokenType tokenType;
+ internal int tokenValue;
+
+ internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue)
+ {
+ this.tokenString = tokenString;
+ this.tokenType = tokenType;
+ this.tokenValue = tokenValue;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfoScanner.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfoScanner.cs
new file mode 100644
index 0000000000..d3c3aac84e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfoScanner.cs
@@ -0,0 +1,729 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+// DateTimeFormatInfoScanner
+//
+// Scan a specified DateTimeFormatInfo to search for data used in DateTime.Parse()
+//
+// The data includes:
+//
+// DateWords: such as "de" used in es-ES (Spanish) LongDatePattern.
+// Postfix: such as "ta" used in fi-FI after the month name.
+//
+// This class is shared among mscorlib.dll and sysglobl.dll.
+// Use conditional CULTURE_AND_REGIONINFO_BUILDER_ONLY to differentiate between
+// methods for mscorlib.dll and sysglobl.dll.
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Collections.Generic;
+using System.Text;
+
+namespace System.Globalization
+{
+ //
+ // from LocaleEx.txt header
+ //
+ //; IFORMATFLAGS
+ //; Parsing/formatting flags.
+ internal enum FORMATFLAGS
+ {
+ None = 0x00000000,
+ UseGenitiveMonth = 0x00000001,
+ UseLeapYearMonth = 0x00000002,
+ UseSpacesInMonthNames = 0x00000004,
+ UseHebrewParsing = 0x00000008,
+ UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names.
+ UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers.
+ }
+
+ internal enum CalendarId : ushort
+ {
+ UNINITIALIZED_VALUE = 0,
+ GREGORIAN = 1, // Gregorian (localized) calendar
+ GREGORIAN_US = 2, // Gregorian (U.S.) calendar
+ JAPAN = 3, // Japanese Emperor Era calendar
+ /* SSS_WARNINGS_OFF */
+ TAIWAN = 4, // Taiwan Era calendar /* SSS_WARNINGS_ON */
+ KOREA = 5, // Korean Tangun Era calendar
+ HIJRI = 6, // Hijri (Arabic Lunar) calendar
+ THAI = 7, // Thai calendar
+ HEBREW = 8, // Hebrew (Lunar) calendar
+ GREGORIAN_ME_FRENCH = 9, // Gregorian Middle East French calendar
+ GREGORIAN_ARABIC = 10, // Gregorian Arabic calendar
+ GREGORIAN_XLIT_ENGLISH = 11, // Gregorian Transliterated English calendar
+ GREGORIAN_XLIT_FRENCH = 12,
+ // Note that all calendars after this point are MANAGED ONLY for now.
+ JULIAN = 13,
+ JAPANESELUNISOLAR = 14,
+ CHINESELUNISOLAR = 15,
+ SAKA = 16, // reserved to match Office but not implemented in our code
+ LUNAR_ETO_CHN = 17, // reserved to match Office but not implemented in our code
+ LUNAR_ETO_KOR = 18, // reserved to match Office but not implemented in our code
+ LUNAR_ETO_ROKUYOU = 19, // reserved to match Office but not implemented in our code
+ KOREANLUNISOLAR = 20,
+ TAIWANLUNISOLAR = 21,
+ PERSIAN = 22,
+ UMALQURA = 23,
+ LAST_CALENDAR = 23 // Last calendar ID
+ }
+
+ internal class DateTimeFormatInfoScanner
+ {
+ // Special prefix-like flag char in DateWord array.
+
+ // Use char in PUA area since we won't be using them in real data.
+ // The char used to tell a read date word or a month postfix. A month postfix
+ // is "ta" in the long date pattern like "d. MMMM'ta 'yyyy" for fi-FI.
+ // In this case, it will be stored as "\xfffeta" in the date word array.
+ internal const char MonthPostfixChar = '\xe000';
+
+ // Add ignorable symbol in a DateWord array.
+
+ // hu-HU has:
+ // shrot date pattern: yyyy. MM. dd.;yyyy-MM-dd;yy-MM-dd
+ // long date pattern: yyyy. MMMM d.
+ // Here, "." is the date separator (derived from short date pattern). However,
+ // "." also appear at the end of long date pattern. In this case, we just
+ // "." as ignorable symbol so that the DateTime.Parse() state machine will not
+ // treat the additional date separator at the end of y,m,d pattern as an error
+ // condition.
+ internal const char IgnorableSymbolChar = '\xe001';
+
+ // Known CJK suffix
+ internal const String CJKYearSuff = "\u5e74";
+ internal const String CJKMonthSuff = "\u6708";
+ internal const String CJKDaySuff = "\u65e5";
+
+ internal const String KoreanYearSuff = "\ub144";
+ internal const String KoreanMonthSuff = "\uc6d4";
+ internal const String KoreanDaySuff = "\uc77c";
+
+ internal const String KoreanHourSuff = "\uc2dc";
+ internal const String KoreanMinuteSuff = "\ubd84";
+ internal const String KoreanSecondSuff = "\ucd08";
+
+ internal const String CJKHourSuff = "\u6642";
+ internal const String ChineseHourSuff = "\u65f6";
+
+ internal const String CJKMinuteSuff = "\u5206";
+ internal const String CJKSecondSuff = "\u79d2";
+
+ // The collection fo date words & postfix.
+ internal List<string> m_dateWords = new List<string>();
+ // Hashtable for the known words.
+ private static volatile Dictionary<string, string> s_knownWords;
+
+ static Dictionary<string, string> KnownWords
+ {
+ get
+ {
+ if (s_knownWords == null)
+ {
+ Dictionary<string, string> temp = new Dictionary<string, string>();
+ // Add known words into the hash table.
+
+ // Skip these special symbols.
+ temp.Add("/", String.Empty);
+ temp.Add("-", String.Empty);
+ temp.Add(".", String.Empty);
+ // Skip known CJK suffixes.
+ temp.Add(CJKYearSuff, String.Empty);
+ temp.Add(CJKMonthSuff, String.Empty);
+ temp.Add(CJKDaySuff, String.Empty);
+ temp.Add(KoreanYearSuff, String.Empty);
+ temp.Add(KoreanMonthSuff, String.Empty);
+ temp.Add(KoreanDaySuff, String.Empty);
+ temp.Add(KoreanHourSuff, String.Empty);
+ temp.Add(KoreanMinuteSuff, String.Empty);
+ temp.Add(KoreanSecondSuff, String.Empty);
+ temp.Add(CJKHourSuff, String.Empty);
+ temp.Add(ChineseHourSuff, String.Empty);
+ temp.Add(CJKMinuteSuff, String.Empty);
+ temp.Add(CJKSecondSuff, String.Empty);
+
+ s_knownWords = temp;
+ }
+ return (s_knownWords);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Parameters:
+ // pattern: The pattern to be scanned.
+ // currentIndex: the current index to start the scan.
+ //
+ // Returns:
+ // Return the index with the first character that is a letter, which will
+ // be the start of a date word.
+ // Note that the index can be pattern.Length if we reach the end of the string.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static int SkipWhiteSpacesAndNonLetter(String pattern, int currentIndex)
+ {
+ while (currentIndex < pattern.Length)
+ {
+ char ch = pattern[currentIndex];
+ if (ch == '\\')
+ {
+ // Escaped character. Look ahead one character.
+ currentIndex++;
+ if (currentIndex < pattern.Length)
+ {
+ ch = pattern[currentIndex];
+ if (ch == '\'')
+ {
+ // Skip the leading single quote. We will
+ // stop at the first letter.
+ continue;
+ }
+ // Fall thru to check if this is a letter.
+ }
+ else
+ {
+ // End of string
+ break;
+ }
+ }
+ if (Char.IsLetter(ch) || ch == '\'' || ch == '.')
+ {
+ break;
+ }
+ // Skip the current char since it is not a letter.
+ currentIndex++;
+ }
+ return (currentIndex);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // A helper to add the found date word or month postfix into ArrayList for date words.
+ //
+ // Parameters:
+ // formatPostfix: What kind of postfix this is.
+ // Possible values:
+ // null: This is a regular date word
+ // "MMMM": month postfix
+ // word: The date word or postfix to be added.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal void AddDateWordOrPostfix(String formatPostfix, String str)
+ {
+ if (str.Length > 0)
+ {
+ // Some cultures use . like an abbreviation
+ if (str.Equals("."))
+ {
+ AddIgnorableSymbols(".");
+ return;
+ }
+ String words;
+ if (KnownWords.TryGetValue(str, out words) == false)
+ {
+ if (m_dateWords == null)
+ {
+ m_dateWords = new List<string>();
+ }
+ if (formatPostfix == "MMMM")
+ {
+ // Add the word into the ArrayList as "\xfffe" + real month postfix.
+ String temp = MonthPostfixChar + str;
+ if (!m_dateWords.Contains(temp))
+ {
+ m_dateWords.Add(temp);
+ }
+ }
+ else
+ {
+ if (!m_dateWords.Contains(str))
+ {
+ m_dateWords.Add(str);
+ }
+ if (str[str.Length - 1] == '.')
+ {
+ // Old version ignore the trialing dot in the date words. Support this as well.
+ String strWithoutDot = str.Substring(0, str.Length - 1);
+ if (!m_dateWords.Contains(strWithoutDot))
+ {
+ m_dateWords.Add(strWithoutDot);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scan the pattern from the specified index and add the date word/postfix
+ // when appropriate.
+ //
+ // Parameters:
+ // pattern: The pattern to be scanned.
+ // index: The starting index to be scanned.
+ // formatPostfix: The kind of postfix to be scanned.
+ // Possible values:
+ // null: This is a regular date word
+ // "MMMM": month postfix
+ //
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal int AddDateWords(String pattern, int index, String formatPostfix)
+ {
+ // Skip any whitespaces so we will start from a letter.
+ int newIndex = SkipWhiteSpacesAndNonLetter(pattern, index);
+ if (newIndex != index && formatPostfix != null)
+ {
+ // There are whitespaces. This will not be a postfix.
+ formatPostfix = null;
+ }
+ index = newIndex;
+
+ // This is the first char added into dateWord.
+ // Skip all non-letter character. We will add the first letter into DateWord.
+ StringBuilder dateWord = new StringBuilder();
+ // We assume that date words should start with a letter.
+ // Skip anything until we see a letter.
+
+ while (index < pattern.Length)
+ {
+ char ch = pattern[index];
+ if (ch == '\'')
+ {
+ // We have seen the end of quote. Add the word if we do not see it before,
+ // and break the while loop.
+ AddDateWordOrPostfix(formatPostfix, dateWord.ToString());
+ index++;
+ break;
+ }
+ else if (ch == '\\')
+ {
+ //
+ // Escaped character. Look ahead one character
+ //
+
+ // Skip escaped backslash.
+ index++;
+ if (index < pattern.Length)
+ {
+ dateWord.Append(pattern[index]);
+ index++;
+ }
+ }
+ else if (Char.IsWhiteSpace(ch))
+ {
+ // Found a whitespace. We have to add the current date word/postfix.
+ AddDateWordOrPostfix(formatPostfix, dateWord.ToString());
+ if (formatPostfix != null)
+ {
+ // Done with postfix. The rest will be regular date word.
+ formatPostfix = null;
+ }
+ // Reset the dateWord.
+ dateWord.Length = 0;
+ index++;
+ }
+ else
+ {
+ dateWord.Append(ch);
+ index++;
+ }
+ }
+ return (index);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // A simple helper to find the repeat count for a specified char.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static int ScanRepeatChar(String pattern, char ch, int index, out int count)
+ {
+ count = 1;
+ while (++index < pattern.Length && pattern[index] == ch)
+ {
+ count++;
+ }
+ // Return the updated position.
+ return (index);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Add the text that is a date separator but is treated like ignroable symbol.
+ // E.g.
+ // hu-HU has:
+ // shrot date pattern: yyyy. MM. dd.;yyyy-MM-dd;yy-MM-dd
+ // long date pattern: yyyy. MMMM d.
+ // Here, "." is the date separator (derived from short date pattern). However,
+ // "." also appear at the end of long date pattern. In this case, we just
+ // "." as ignorable symbol so that the DateTime.Parse() state machine will not
+ // treat the additional date separator at the end of y,m,d pattern as an error
+ // condition.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal void AddIgnorableSymbols(String text)
+ {
+ if (m_dateWords == null)
+ {
+ // Create the date word array.
+ m_dateWords = new List<string>();
+ }
+ // Add the ignorable symbol into the ArrayList.
+ String temp = IgnorableSymbolChar + text;
+ if (!m_dateWords.Contains(temp))
+ {
+ m_dateWords.Add(temp);
+ }
+ }
+
+
+ //
+ // Flag used to trace the date patterns (yy/yyyyy/M/MM/MMM/MMM/d/dd) that we have seen.
+ //
+ private enum FoundDatePattern
+ {
+ None = 0x0000,
+ FoundYearPatternFlag = 0x0001,
+ FoundMonthPatternFlag = 0x0002,
+ FoundDayPatternFlag = 0x0004,
+ FoundYMDPatternFlag = 0x0007, // FoundYearPatternFlag | FoundMonthPatternFlag | FoundDayPatternFlag;
+ }
+
+ // Check if we have found all of the year/month/day pattern.
+ private FoundDatePattern _ymdFlags = FoundDatePattern.None;
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Given a date format pattern, scan for date word or postfix.
+ //
+ // A date word should be always put in a single quoted string. And it will
+ // start from a letter, so whitespace and symbols will be ignored before
+ // the first letter.
+ //
+ // Examples of date word:
+ // 'de' in es-SP: dddd, dd' de 'MMMM' de 'yyyy
+ // "\x0443." in bg-BG: dd.M.yyyy '\x0433.'
+ //
+ // Example of postfix:
+ // month postfix:
+ // "ta" in fi-FI: d. MMMM'ta 'yyyy
+ // Currently, only month postfix is supported.
+ //
+ // Usage:
+ // Always call this with Framework-style pattern, instead of Windows style pattern.
+ // Windows style pattern uses '' for single quote, while .NET uses \'
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal void ScanDateWord(String pattern)
+ {
+ // Check if we have found all of the year/month/day pattern.
+ _ymdFlags = FoundDatePattern.None;
+
+ int i = 0;
+ while (i < pattern.Length)
+ {
+ char ch = pattern[i];
+ int chCount;
+
+ switch (ch)
+ {
+ case '\'':
+ // Find a beginning quote. Search until the end quote.
+ i = AddDateWords(pattern, i + 1, null);
+ break;
+ case 'M':
+ i = ScanRepeatChar(pattern, 'M', i, out chCount);
+ if (chCount >= 4)
+ {
+ if (i < pattern.Length && pattern[i] == '\'')
+ {
+ i = AddDateWords(pattern, i + 1, "MMMM");
+ }
+ }
+ _ymdFlags |= FoundDatePattern.FoundMonthPatternFlag;
+ break;
+ case 'y':
+ i = ScanRepeatChar(pattern, 'y', i, out chCount);
+ _ymdFlags |= FoundDatePattern.FoundYearPatternFlag;
+ break;
+ case 'd':
+ i = ScanRepeatChar(pattern, 'd', i, out chCount);
+ if (chCount <= 2)
+ {
+ // Only count "d" & "dd".
+ // ddd, dddd are day names. Do not count them.
+ _ymdFlags |= FoundDatePattern.FoundDayPatternFlag;
+ }
+ break;
+ case '\\':
+ // Found a escaped char not in a quoted string. Skip the current backslash
+ // and its next character.
+ i += 2;
+ break;
+ case '.':
+ if (_ymdFlags == FoundDatePattern.FoundYMDPatternFlag)
+ {
+ // If we find a dot immediately after the we have seen all of the y, m, d pattern.
+ // treat it as a ignroable symbol. Check for comments in AddIgnorableSymbols for
+ // more details.
+ AddIgnorableSymbols(".");
+ _ymdFlags = FoundDatePattern.None;
+ }
+ i++;
+ break;
+ default:
+ if (_ymdFlags == FoundDatePattern.FoundYMDPatternFlag && !Char.IsWhiteSpace(ch))
+ {
+ // We are not seeing "." after YMD. Clear the flag.
+ _ymdFlags = FoundDatePattern.None;
+ }
+ // We are not in quote. Skip the current character.
+ i++;
+ break;
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Given a DTFI, get all of the date words from date patterns and time patterns.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal String[] GetDateWordsOfDTFI(DateTimeFormatInfo dtfi)
+ {
+ // Enumarate all LongDatePatterns, and get the DateWords and scan for month postfix.
+ String[] datePatterns = dtfi.GetAllDateTimePatterns('D');
+ int i;
+
+ // Scan the long date patterns
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+
+ // Scan the short date patterns
+ datePatterns = dtfi.GetAllDateTimePatterns('d');
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+ // Scan the YearMonth patterns.
+ datePatterns = dtfi.GetAllDateTimePatterns('y');
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+
+ // Scan the month/day pattern
+ ScanDateWord(dtfi.MonthDayPattern);
+
+ // Scan the long time patterns.
+ datePatterns = dtfi.GetAllDateTimePatterns('T');
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+
+ // Scan the short time patterns.
+ datePatterns = dtfi.GetAllDateTimePatterns('t');
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+
+ String[] result = null;
+ if (m_dateWords != null && m_dateWords.Count > 0)
+ {
+ result = new String[m_dateWords.Count];
+ for (i = 0; i < m_dateWords.Count; i++)
+ {
+ result[i] = m_dateWords[i];
+ }
+ }
+ return (result);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scan the month names to see if genitive month names are used, and return
+ // the format flag.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static FORMATFLAGS GetFormatFlagGenitiveMonth(String[] monthNames, String[] genitveMonthNames, String[] abbrevMonthNames, String[] genetiveAbbrevMonthNames)
+ {
+ // If we have different names in regular and genitive month names, use genitive month flag.
+ return ((!EqualStringArrays(monthNames, genitveMonthNames) || !EqualStringArrays(abbrevMonthNames, genetiveAbbrevMonthNames))
+ ? FORMATFLAGS.UseGenitiveMonth : 0);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scan the month names to see if spaces are used or start with a digit, and return the format flag
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static FORMATFLAGS GetFormatFlagUseSpaceInMonthNames(String[] monthNames, String[] genitveMonthNames, String[] abbrevMonthNames, String[] genetiveAbbrevMonthNames)
+ {
+ FORMATFLAGS formatFlags = 0;
+ formatFlags |= (ArrayElementsBeginWithDigit(monthNames) ||
+ ArrayElementsBeginWithDigit(genitveMonthNames) ||
+ ArrayElementsBeginWithDigit(abbrevMonthNames) ||
+ ArrayElementsBeginWithDigit(genetiveAbbrevMonthNames)
+ ? FORMATFLAGS.UseDigitPrefixInTokens : 0);
+
+ formatFlags |= (ArrayElementsHaveSpace(monthNames) ||
+ ArrayElementsHaveSpace(genitveMonthNames) ||
+ ArrayElementsHaveSpace(abbrevMonthNames) ||
+ ArrayElementsHaveSpace(genetiveAbbrevMonthNames)
+ ? FORMATFLAGS.UseSpacesInMonthNames : 0);
+ return (formatFlags);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scan the day names and set the correct format flag.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static FORMATFLAGS GetFormatFlagUseSpaceInDayNames(String[] dayNames, String[] abbrevDayNames)
+ {
+ return ((ArrayElementsHaveSpace(dayNames) ||
+ ArrayElementsHaveSpace(abbrevDayNames))
+ ? FORMATFLAGS.UseSpacesInDayNames : 0);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Check the calendar to see if it is HebrewCalendar and set the Hebrew format flag if necessary.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static FORMATFLAGS GetFormatFlagUseHebrewCalendar(int calID)
+ {
+ return (calID == (int)CalendarId.HEBREW ?
+ FORMATFLAGS.UseHebrewParsing | FORMATFLAGS.UseLeapYearMonth : 0);
+ }
+
+
+ //-----------------------------------------------------------------------------
+ // EqualStringArrays
+ // compares two string arrays and return true if all elements of the first
+ // array equals to all elmentsof the second array.
+ // otherwise it returns false.
+ //-----------------------------------------------------------------------------
+
+ private static bool EqualStringArrays(string[] array1, string[] array2)
+ {
+ // Shortcut if they're the same array
+ if (array1 == array2)
+ {
+ return true;
+ }
+
+ // This is effectively impossible
+ if (array1.Length != array2.Length)
+ {
+ return false;
+ }
+
+ // Check each string
+ for (int i = 0; i < array1.Length; i++)
+ {
+ if (!array1[i].Equals(array2[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ //-----------------------------------------------------------------------------
+ // ArrayElementsHaveSpace
+ // It checks all input array elements if any of them has space character
+ // returns true if found space character in one of the array elements.
+ // otherwise returns false.
+ //-----------------------------------------------------------------------------
+
+ private static bool ArrayElementsHaveSpace(string[] array)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ // it is faster to check for space character manually instead of calling IndexOf
+ // so we don't have to go to native code side.
+ for (int j = 0; j < array[i].Length; j++)
+ {
+ if (Char.IsWhiteSpace(array[i][j]))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Check if any element of the array start with a digit.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private static bool ArrayElementsBeginWithDigit(string[] array)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ // it is faster to check for space character manually instead of calling IndexOf
+ // so we don't have to go to native code side.
+ if (array[i].Length > 0 &&
+ array[i][0] >= '0' && array[i][0] <= '9')
+ {
+ int index = 1;
+ while (index < array[i].Length && array[i][index] >= '0' && array[i][index] <= '9')
+ {
+ // Skip other digits.
+ index++;
+ }
+ if (index == array[i].Length)
+ {
+ return (false);
+ }
+
+ if (index == array[i].Length - 1)
+ {
+ // Skip known CJK month suffix.
+ // CJK uses month name like "1\x6708", since \x6708 is a known month suffix,
+ // we don't need the UseDigitPrefixInTokens since it is slower.
+ switch (array[i][index])
+ {
+ case '\x6708': // CJKMonthSuff
+ case '\xc6d4': // KoreanMonthSuff
+ return (false);
+ }
+ }
+
+ if (index == array[i].Length - 4)
+ {
+ // Skip known CJK month suffix.
+ // Starting with Windows 8, the CJK months for some cultures looks like: "1' \x6708'"
+ // instead of just "1\x6708"
+ if (array[i][index] == '\'' && array[i][index + 1] == ' ' &&
+ array[i][index + 2] == '\x6708' && array[i][index + 3] == '\'')
+ {
+ return (false);
+ }
+ }
+ return (true);
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs
new file mode 100644
index 0000000000..970d1765bb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeParse.cs
@@ -0,0 +1,5668 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace System
+{
+ internal static class DateTimeParse
+ {
+ internal const Int32 MaxDateTimeNumberDigits = 8;
+
+ internal delegate bool MatchNumberDelegate(ref __DTString str, int digitLen, out int result);
+
+ internal static MatchNumberDelegate m_hebrewNumberParser = new MatchNumberDelegate(DateTimeParse.MatchHebrewDigits);
+
+ internal static DateTime ParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, DateTimeStyles style)
+ {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ result.Init(s);
+ if (TryParseExact(s, format, dtfi, style, ref result))
+ {
+ return result.parsedDate;
+ }
+ else
+ {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+ internal static DateTime ParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, DateTimeStyles style, out TimeSpan offset)
+ {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ offset = TimeSpan.Zero;
+ result.Init(s);
+ result.flags |= ParseFlags.CaptureOffset;
+ if (TryParseExact(s, format, dtfi, style, ref result))
+ {
+ offset = result.timeZoneOffset;
+ return result.parsedDate;
+ }
+ else
+ {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+ internal static bool TryParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result)
+ {
+ result = DateTime.MinValue;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init(s);
+ if (TryParseExact(s, format, dtfi, style, ref resultData))
+ {
+ result = resultData.parsedDate;
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result, out TimeSpan offset)
+ {
+ result = DateTime.MinValue;
+ offset = TimeSpan.Zero;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init(s);
+ resultData.flags |= ParseFlags.CaptureOffset;
+ if (TryParseExact(s, format, dtfi, style, ref resultData))
+ {
+ result = resultData.parsedDate;
+ offset = resultData.timeZoneOffset;
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result)
+ {
+ if (s.Length == 0)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDateTime));
+ return false;
+ }
+
+ if (format.Length == 0)
+ {
+ result.SetBadFormatSpecifierFailure();
+ return false;
+ }
+
+ Debug.Assert(dtfi != null, "dtfi == null");
+
+ return DoStrictParse(s, format, style, dtfi, ref result);
+ }
+
+ internal static DateTime ParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style)
+ {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ result.Init(s);
+ if (TryParseExactMultiple(s, formats, dtfi, style, ref result))
+ {
+ return result.parsedDate;
+ }
+ else
+ {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+
+ internal static DateTime ParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style, out TimeSpan offset)
+ {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ offset = TimeSpan.Zero;
+ result.Init(s);
+ result.flags |= ParseFlags.CaptureOffset;
+ if (TryParseExactMultiple(s, formats, dtfi, style, ref result))
+ {
+ offset = result.timeZoneOffset;
+ return result.parsedDate;
+ }
+ else
+ {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+ internal static bool TryParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result, out TimeSpan offset)
+ {
+ result = DateTime.MinValue;
+ offset = TimeSpan.Zero;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init(s);
+ resultData.flags |= ParseFlags.CaptureOffset;
+ if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData))
+ {
+ result = resultData.parsedDate;
+ offset = resultData.timeZoneOffset;
+ return true;
+ }
+ return false;
+ }
+
+
+ internal static bool TryParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result)
+ {
+ result = DateTime.MinValue;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init(s);
+ if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData))
+ {
+ result = resultData.parsedDate;
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result)
+ {
+ if (formats == null)
+ {
+ result.SetFailure(ParseFailureKind.ArgumentNull, nameof(SR.ArgumentNull_String), null, nameof(formats));
+ return false;
+ }
+
+ if (s.Length == 0)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDateTime));
+ return false;
+ }
+
+ if (formats.Length == 0)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_NoFormatSpecifier));
+ return false;
+ }
+
+ Debug.Assert(dtfi != null, "dtfi == null");
+
+ //
+ // Do a loop through the provided formats and see if we can parse succesfully in
+ // one of the formats.
+ //
+ for (int i = 0; i < formats.Length; i++)
+ {
+ if (formats[i] == null || formats[i].Length == 0)
+ {
+ result.SetBadFormatSpecifierFailure();
+ return false;
+ }
+ // Create a new result each time to ensure the runs are independent. Carry through
+ // flags from the caller and return the result.
+ DateTimeResult innerResult = new DateTimeResult(); // The buffer to store the parsing result.
+ innerResult.Init(s);
+ innerResult.flags = result.flags;
+ if (TryParseExact(s, formats[i], dtfi, style, ref innerResult))
+ {
+ result.parsedDate = innerResult.parsedDate;
+ result.timeZoneOffset = innerResult.timeZoneOffset;
+ return (true);
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Date Token Types
+ //
+ // Following is the set of tokens that can be generated from a date
+ // string. Notice that the legal set of trailing separators have been
+ // folded in with the date number, and month name tokens. This set
+ // of tokens is chosen to reduce the number of date parse states.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal enum DTT : int
+ {
+ End = 0, // '\0'
+ NumEnd = 1, // Num[ ]*[\0]
+ NumAmpm = 2, // Num[ ]+AmPm
+ NumSpace = 3, // Num[ ]+^[Dsep|Tsep|'0\']
+ NumDatesep = 4, // Num[ ]*Dsep
+ NumTimesep = 5, // Num[ ]*Tsep
+ MonthEnd = 6, // Month[ ]*'\0'
+ MonthSpace = 7, // Month[ ]+^[Dsep|Tsep|'\0']
+ MonthDatesep = 8, // Month[ ]*Dsep
+ NumDatesuff = 9, // Month[ ]*DSuff
+ NumTimesuff = 10, // Month[ ]*TSuff
+ DayOfWeek = 11, // Day of week name
+ YearSpace = 12, // Year+^[Dsep|Tsep|'0\']
+ YearDateSep = 13, // Year+Dsep
+ YearEnd = 14, // Year+['\0']
+ TimeZone = 15, // timezone name
+ Era = 16, // era name
+ NumUTCTimeMark = 17, // Num + 'Z'
+ // When you add a new token which will be in the
+ // state table, add it after NumLocalTimeMark.
+ Unk = 18, // unknown
+ NumLocalTimeMark = 19, // Num + 'T'
+ Max = 20, // marker
+ }
+
+ internal enum TM
+ {
+ NotSet = -1,
+ AM = 0,
+ PM = 1,
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // DateTime parsing state enumeration (DS.*)
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal enum DS
+ {
+ BEGIN = 0,
+ N = 1, // have one number
+ NN = 2, // have two numbers
+
+ // The following are known to be part of a date
+
+ D_Nd = 3, // date string: have number followed by date separator
+ D_NN = 4, // date string: have two numbers
+ D_NNd = 5, // date string: have two numbers followed by date separator
+
+ D_M = 6, // date string: have a month
+ D_MN = 7, // date string: have a month and a number
+ D_NM = 8, // date string: have a number and a month
+ D_MNd = 9, // date string: have a month and number followed by date separator
+ D_NDS = 10, // date string: have one number followed a date suffix.
+
+ D_Y = 11, // date string: have a year.
+ D_YN = 12, // date string: have a year and a number
+ D_YNd = 13, // date string: have a year and a number and a date separator
+ D_YM = 14, // date string: have a year and a month
+ D_YMd = 15, // date string: have a year and a month and a date separator
+ D_S = 16, // have numbers followed by a date suffix.
+ T_S = 17, // have numbers followed by a time suffix.
+
+ // The following are known to be part of a time
+
+ T_Nt = 18, // have num followed by time separator
+ T_NNt = 19, // have two numbers followed by time separator
+
+
+ ERROR = 20,
+
+ // The following are terminal states. These all have an action
+ // associated with them; and transition back to BEGIN.
+
+ DX_NN = 21, // day from two numbers
+ DX_NNN = 22, // day from three numbers
+ DX_MN = 23, // day from month and one number
+ DX_NM = 24, // day from month and one number
+ DX_MNN = 25, // day from month and two numbers
+ DX_DS = 26, // a set of date suffixed numbers.
+ DX_DSN = 27, // day from date suffixes and one number.
+ DX_NDS = 28, // day from one number and date suffixes .
+ DX_NNDS = 29, // day from one number and date suffixes .
+
+ DX_YNN = 30, // date string: have a year and two number
+ DX_YMN = 31, // date string: have a year, a month, and a number.
+ DX_YN = 32, // date string: have a year and one number
+ DX_YM = 33, // date string: have a year, a month.
+ TX_N = 34, // time from one number (must have ampm)
+ TX_NN = 35, // time from two numbers
+ TX_NNN = 36, // time from three numbers
+ TX_TS = 37, // a set of time suffixed numbers.
+ DX_NNY = 38,
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // NOTE: The following state machine table is dependent on the order of the
+ // DS and DTT enumerations.
+ //
+ // For each non terminal state, the following table defines the next state
+ // for each given date token type.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ // End NumEnd NumAmPm NumSpace NumDaySep NumTimesep MonthEnd MonthSpace MonthDSep NumDateSuff NumTimeSuff DayOfWeek YearSpace YearDateSep YearEnd TimeZone Era UTCTimeMark
+ private static DS[][] dateParsingStates = {
+// DS.BEGIN // DS.BEGIN
+new DS[] { DS.BEGIN, DS.ERROR, DS.TX_N, DS.N, DS.D_Nd, DS.T_Nt, DS.ERROR, DS.D_M, DS.D_M, DS.D_S, DS.T_S, DS.BEGIN, DS.D_Y, DS.D_Y, DS.ERROR, DS.BEGIN, DS.BEGIN, DS.ERROR},
+
+// DS.N // DS.N
+new DS[] { DS.ERROR, DS.DX_NN, DS.ERROR, DS.NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_NM, DS.D_MNd, DS.D_NDS, DS.ERROR, DS.N, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.N, DS.N, DS.ERROR},
+
+// DS.NN // DS.NN
+new DS[] { DS.DX_NN, DS.DX_NNN, DS.TX_N, DS.DX_NNN, DS.ERROR, DS.T_Nt, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.ERROR, DS.T_S, DS.NN, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.NN, DS.NN, DS.ERROR},
+
+// DS.D_Nd // DS.D_Nd
+new DS[] { DS.ERROR, DS.DX_NN, DS.ERROR, DS.D_NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_MN, DS.D_MNd, DS.ERROR, DS.ERROR, DS.D_Nd, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.ERROR, DS.D_Nd, DS.ERROR},
+
+// DS.D_NN // DS.D_NN
+new DS[] { DS.DX_NN, DS.DX_NNN, DS.TX_N, DS.DX_NNN, DS.ERROR, DS.T_Nt, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_NN, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.ERROR, DS.D_NN, DS.ERROR},
+
+// DS.D_NNd // DS.D_NNd
+new DS[] { DS.ERROR, DS.DX_NNN, DS.DX_NNN, DS.DX_NNN, DS.ERROR, DS.ERROR, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.DX_DS, DS.ERROR, DS.D_NNd, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.ERROR, DS.D_NNd, DS.ERROR},
+
+// DS.D_M // DS.D_M
+new DS[] { DS.ERROR, DS.DX_MN, DS.ERROR, DS.D_MN, DS.D_MNd, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_M, DS.D_YM, DS.D_YMd, DS.DX_YM, DS.ERROR, DS.D_M, DS.ERROR},
+
+// DS.D_MN // DS.D_MN
+new DS[] { DS.DX_MN, DS.DX_MNN, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_MN, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_MN, DS.ERROR},
+
+// DS.D_NM // DS.D_NM
+new DS[] { DS.DX_NM, DS.DX_MNN, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_NM, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_NM, DS.ERROR},
+
+// DS.D_MNd // DS.D_MNd
+new DS[] { DS.ERROR, DS.DX_MNN, DS.ERROR, DS.DX_MNN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_MNd, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_MNd, DS.ERROR},
+
+// DS.D_NDS, // DS.D_NDS,
+new DS[] { DS.DX_NDS,DS.DX_NNDS, DS.DX_NNDS, DS.DX_NNDS, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_NDS, DS.T_S, DS.D_NDS, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_NDS, DS.ERROR},
+
+// DS.D_Y // DS.D_Y
+new DS[] { DS.ERROR, DS.DX_YN, DS.ERROR, DS.D_YN, DS.D_YNd, DS.ERROR, DS.DX_YM, DS.D_YM, DS.D_YMd, DS.D_YM, DS.ERROR, DS.D_Y, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_Y, DS.ERROR},
+
+// DS.D_YN // DS.D_YN
+new DS[] { DS.DX_YN, DS.DX_YNN, DS.DX_YNN, DS.DX_YNN, DS.ERROR, DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR},
+
+// DS.D_YNd // DS.D_YNd
+new DS[] { DS.ERROR, DS.DX_YNN, DS.DX_YNN, DS.DX_YNN, DS.ERROR, DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR},
+
+// DS.D_YM // DS.D_YM
+new DS[] { DS.DX_YM, DS.DX_YMN, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR},
+
+// DS.D_YMd // DS.D_YMd
+new DS[] { DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR},
+
+// DS.D_S // DS.D_S
+new DS[] { DS.DX_DS, DS.DX_DSN, DS.TX_N, DS.T_Nt, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.T_S, DS.D_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.ERROR},
+
+// DS.T_S // DS.T_S
+new DS[] { DS.TX_TS, DS.TX_TS, DS.TX_TS, DS.T_Nt, DS.D_Nd, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.T_S, DS.T_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_S, DS.T_S, DS.ERROR},
+
+// DS.T_Nt // DS.T_Nt
+new DS[] { DS.ERROR, DS.TX_NN, DS.TX_NN, DS.TX_NN, DS.ERROR, DS.T_NNt, DS.DX_NM, DS.D_NM, DS.ERROR, DS.ERROR, DS.T_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_Nt, DS.T_Nt, DS.TX_NN},
+
+// DS.T_NNt // DS.T_NNt
+new DS[] { DS.ERROR, DS.TX_NNN, DS.TX_NNN, DS.TX_NNN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_S, DS.T_NNt, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_NNt, DS.T_NNt, DS.TX_NNN},
+};
+ // End NumEnd NumAmPm NumSpace NumDaySep NumTimesep MonthEnd MonthSpace MonthDSep NumDateSuff NumTimeSuff DayOfWeek YearSpace YearDateSep YearEnd TimeZone Era UTCMark
+
+ internal const String GMTName = "GMT";
+ internal const String ZuluName = "Z";
+
+ //
+ // Search from the index of str at str.Index to see if the target string exists in the str.
+ //
+ private static bool MatchWord(ref __DTString str, String target)
+ {
+ if (target.Length > (str.Value.Length - str.Index))
+ {
+ return false;
+ }
+
+ if (str.CompareInfo.Compare(str.Value.Slice(str.Index, target.Length), target, CompareOptions.IgnoreCase) != 0)
+ {
+ return (false);
+ }
+
+ int nextCharIndex = str.Index + target.Length;
+
+ if (nextCharIndex < str.Value.Length)
+ {
+ char nextCh = str.Value[nextCharIndex];
+ if (Char.IsLetter(nextCh))
+ {
+ return (false);
+ }
+ }
+ str.Index = nextCharIndex;
+ if (str.Index < str.Length)
+ {
+ str.m_current = str.Value[str.Index];
+ }
+
+ return (true);
+ }
+
+
+ //
+ // Check the word at the current index to see if it matches GMT name or Zulu name.
+ //
+ private static bool GetTimeZoneName(ref __DTString str)
+ {
+ if (MatchWord(ref str, GMTName))
+ {
+ return (true);
+ }
+
+ if (MatchWord(ref str, ZuluName))
+ {
+ return (true);
+ }
+
+ return (false);
+ }
+
+ internal static bool IsDigit(char ch) => (uint)(ch - '0') <= 9;
+
+ /*=================================ParseFraction==========================
+ **Action: Starting at the str.Index, which should be a decimal symbol.
+ ** if the current character is a digit, parse the remaining
+ ** numbers as fraction. For example, if the sub-string starting at str.Index is "123", then
+ ** the method will return 0.123
+ **Returns: The fraction number.
+ **Arguments:
+ ** str the parsing string
+ **Exceptions:
+ ============================================================================*/
+
+ private static bool ParseFraction(ref __DTString str, out double result)
+ {
+ result = 0;
+ double decimalBase = 0.1;
+ int digits = 0;
+ char ch;
+ while (str.GetNext()
+ && IsDigit(ch = str.m_current))
+ {
+ result += (ch - '0') * decimalBase;
+ decimalBase *= 0.1;
+ digits++;
+ }
+ return (digits > 0);
+ }
+
+ /*=================================ParseTimeZone==========================
+ **Action: Parse the timezone offset in the following format:
+ ** "+8", "+08", "+0800", "+0800"
+ ** This method is used by DateTime.Parse().
+ **Returns: The TimeZone offset.
+ **Arguments:
+ ** str the parsing string
+ **Exceptions:
+ ** FormatException if invalid timezone format is found.
+ ============================================================================*/
+
+ private static bool ParseTimeZone(ref __DTString str, ref TimeSpan result)
+ {
+ // The hour/minute offset for timezone.
+ int hourOffset = 0;
+ int minuteOffset = 0;
+ DTSubString sub;
+
+ // Consume the +/- character that has already been read
+ sub = str.GetSubString();
+ if (sub.length != 1)
+ {
+ return false;
+ }
+ char offsetChar = sub[0];
+ if (offsetChar != '+' && offsetChar != '-')
+ {
+ return false;
+ }
+ str.ConsumeSubString(sub);
+
+ sub = str.GetSubString();
+ if (sub.type != DTSubStringType.Number)
+ {
+ return false;
+ }
+ int value = sub.value;
+ int length = sub.length;
+ if (length == 1 || length == 2)
+ {
+ // Parsing "+8" or "+08"
+ hourOffset = value;
+ str.ConsumeSubString(sub);
+ // See if we have minutes
+ sub = str.GetSubString();
+ if (sub.length == 1 && sub[0] == ':')
+ {
+ // Parsing "+8:00" or "+08:00"
+ str.ConsumeSubString(sub);
+ sub = str.GetSubString();
+ if (sub.type != DTSubStringType.Number || sub.length < 1 || sub.length > 2)
+ {
+ return false;
+ }
+ minuteOffset = sub.value;
+ str.ConsumeSubString(sub);
+ }
+ }
+ else if (length == 3 || length == 4)
+ {
+ // Parsing "+800" or "+0800"
+ hourOffset = value / 100;
+ minuteOffset = value % 100;
+ str.ConsumeSubString(sub);
+ }
+ else
+ {
+ // Wrong number of digits
+ return false;
+ }
+ Debug.Assert(hourOffset >= 0 && hourOffset <= 99, "hourOffset >= 0 && hourOffset <= 99");
+ Debug.Assert(minuteOffset >= 0 && minuteOffset <= 99, "minuteOffset >= 0 && minuteOffset <= 99");
+ if (minuteOffset < 0 || minuteOffset >= 60)
+ {
+ return false;
+ }
+
+ result = new TimeSpan(hourOffset, minuteOffset, 0);
+ if (offsetChar == '-')
+ {
+ result = result.Negate();
+ }
+ return true;
+ }
+
+ // This is the helper function to handle timezone in string in the format like +/-0800
+ private static bool HandleTimeZone(ref __DTString str, ref DateTimeResult result)
+ {
+ if ((str.Index < str.Length - 1))
+ {
+ char nextCh = str.Value[str.Index];
+ // Skip whitespace, but don't update the index unless we find a time zone marker
+ int whitespaceCount = 0;
+ while (Char.IsWhiteSpace(nextCh) && str.Index + whitespaceCount < str.Length - 1)
+ {
+ whitespaceCount++;
+ nextCh = str.Value[str.Index + whitespaceCount];
+ }
+ if (nextCh == '+' || nextCh == '-')
+ {
+ str.Index += whitespaceCount;
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0)
+ {
+ // Should not have two timezone offsets.
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ result.flags |= ParseFlags.TimeZoneUsed;
+ if (!ParseTimeZone(ref str, ref result.timeZoneOffset))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ //
+ // This is the lexer. Check the character at the current index, and put the found token in dtok and
+ // some raw date/time information in raw.
+ //
+ private static Boolean Lex(DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi, DateTimeStyles styles)
+ {
+ TokenType tokenType;
+ int tokenValue;
+ int indexBeforeSeparator;
+ char charBeforeSeparator;
+
+ TokenType sep;
+ dtok.dtt = DTT.Unk; // Assume the token is unkown.
+
+ str.GetRegularToken(out tokenType, out tokenValue, dtfi);
+
+#if _LOGGING
+ if (_tracingEnabled)
+ {
+ Trace($"Lex({Hex(str.Value)})\tpos:{str.Index}({Hex(str.m_current)}), {tokenType}, DS.{dps}");
+ }
+#endif // _LOGGING
+
+ // Look at the regular token.
+ switch (tokenType)
+ {
+ case TokenType.NumberToken:
+ case TokenType.YearNumberToken:
+ if (raw.numCount == 3 || tokenValue == -1)
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0010", dps);
+ return false;
+ }
+ //
+ // This is a digit.
+ //
+ // If the previous parsing state is DS.T_NNt (like 12:01), and we got another number,
+ // so we will have a terminal state DS.TX_NNN (like 12:01:02).
+ // If the previous parsing state is DS.T_Nt (like 12:), and we got another number,
+ // so we will have a terminal state DS.TX_NN (like 12:01).
+ //
+ // Look ahead to see if the following character is a decimal point or timezone offset.
+ // This enables us to parse time in the forms of:
+ // "11:22:33.1234" or "11:22:33-08".
+ if (dps == DS.T_NNt)
+ {
+ if ((str.Index < str.Length - 1))
+ {
+ char nextCh = str.Value[str.Index];
+ if (nextCh == '.')
+ {
+ // While ParseFraction can fail, it just means that there were no digits after
+ // the dot. In this case ParseFraction just removes the dot. This is actually
+ // valid for cultures like Albanian, that join the time marker to the time with
+ // with a dot: e.g. "9:03.MD"
+ ParseFraction(ref str, out raw.fraction);
+ }
+ }
+ }
+ if (dps == DS.T_NNt || dps == DS.T_Nt)
+ {
+ if ((str.Index < str.Length - 1))
+ {
+ if (false == HandleTimeZone(ref str, ref result))
+ {
+ LexTraceExit("0020 (value like \"12:01\" or \"12:\" followed by a non-TZ number", dps);
+ return false;
+ }
+ }
+ }
+
+ dtok.num = tokenValue;
+ if (tokenType == TokenType.YearNumberToken)
+ {
+ if (raw.year == -1)
+ {
+ raw.year = tokenValue;
+ //
+ // If we have number which has 3 or more digits (like "001" or "0001"),
+ // we assume this number is a year. Save the currnet raw.numCount in
+ // raw.year.
+ //
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
+ {
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.YearEnd;
+ break;
+ case TokenType.SEP_Am:
+ case TokenType.SEP_Pm:
+ if (raw.timeMark == TM.NotSet)
+ {
+ raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM);
+ dtok.dtt = DTT.YearSpace;
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0030 (TM.AM/TM.PM Happened more than 1x)", dps);
+ }
+ break;
+ case TokenType.SEP_Space:
+ dtok.dtt = DTT.YearSpace;
+ break;
+ case TokenType.SEP_Date:
+ dtok.dtt = DTT.YearDateSep;
+ break;
+ case TokenType.SEP_Time:
+ if (!raw.hasSameDateAndTimeSeparators)
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0040 (Invalid separator after number)", dps);
+ return false;
+ }
+
+ // we have the date and time separators are same and getting a year number, then change the token to YearDateSep as
+ // we are sure we are not parsing time.
+ dtok.dtt = DTT.YearDateSep;
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if ((dateParsingStates[(int)dps][(int)DTT.YearDateSep] == DS.ERROR)
+ && (dateParsingStates[(int)dps][(int)DTT.YearSpace] > DS.ERROR))
+ {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.YearSpace;
+ }
+ else
+ {
+ dtok.dtt = DTT.YearDateSep;
+ }
+ break;
+ case TokenType.SEP_YearSuff:
+ case TokenType.SEP_MonthSuff:
+ case TokenType.SEP_DaySuff:
+ dtok.dtt = DTT.NumDatesuff;
+ dtok.suffix = sep;
+ break;
+ case TokenType.SEP_HourSuff:
+ case TokenType.SEP_MinuteSuff:
+ case TokenType.SEP_SecondSuff:
+ dtok.dtt = DTT.NumTimesuff;
+ dtok.suffix = sep;
+ break;
+ default:
+ // Invalid separator after number number.
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0040 (Invalid separator after number)", dps);
+ return false;
+ }
+ //
+ // Found the token already. Return now.
+ //
+ LexTraceExit("0050 (success)", dps);
+ return true;
+ }
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0060", dps);
+ return false;
+ }
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
+ {
+ //
+ // Note here we check if the numCount is less than three.
+ // When we have more than three numbers, it will be caught as error in the state machine.
+ //
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.NumEnd;
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_Am:
+ case TokenType.SEP_Pm:
+ if (raw.timeMark == TM.NotSet)
+ {
+ raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM);
+ dtok.dtt = DTT.NumAmpm;
+ // Fix AM/PM parsing case, e.g. "1/10 5 AM"
+ if (dps == DS.D_NN)
+ {
+ if (!ProcessTerminalState(DS.DX_NN, ref str, ref result, ref styles, ref raw, dtfi))
+ {
+ return false;
+ }
+ }
+
+ raw.AddNumber(dtok.num);
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ break;
+ }
+ if (dps == DS.T_NNt || dps == DS.T_Nt)
+ {
+ if (false == HandleTimeZone(ref str, ref result))
+ {
+ LexTraceExit("0070 (HandleTimeZone returned false)", dps);
+ return false;
+ }
+ }
+ break;
+ case TokenType.SEP_Space:
+ dtok.dtt = DTT.NumSpace;
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_Date:
+ dtok.dtt = DTT.NumDatesep;
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if ((dateParsingStates[(int)dps][(int)DTT.NumDatesep] == DS.ERROR)
+ && (dateParsingStates[(int)dps][(int)DTT.NumSpace] > DS.ERROR))
+ {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.NumSpace;
+ }
+ else
+ {
+ dtok.dtt = DTT.NumDatesep;
+ }
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_Time:
+ if (raw.hasSameDateAndTimeSeparators &&
+ (dps == DS.D_Y || dps == DS.D_YN || dps == DS.D_YNd || dps == DS.D_YM || dps == DS.D_YMd))
+ {
+ // we are parsing a date and we have the time separator same as date separator, so we mark the token as date separator
+ dtok.dtt = DTT.NumDatesep;
+ raw.AddNumber(dtok.num);
+ break;
+ }
+ dtok.dtt = DTT.NumTimesep;
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_YearSuff:
+ try
+ {
+ dtok.num = dtfi.Calendar.ToFourDigitYear(tokenValue);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0075 (Calendar.ToFourDigitYear failed)", dps);
+ return false;
+ }
+ dtok.dtt = DTT.NumDatesuff;
+ dtok.suffix = sep;
+ break;
+ case TokenType.SEP_MonthSuff:
+ case TokenType.SEP_DaySuff:
+ dtok.dtt = DTT.NumDatesuff;
+ dtok.suffix = sep;
+ break;
+ case TokenType.SEP_HourSuff:
+ case TokenType.SEP_MinuteSuff:
+ case TokenType.SEP_SecondSuff:
+ dtok.dtt = DTT.NumTimesuff;
+ dtok.suffix = sep;
+ break;
+ case TokenType.SEP_LocalTimeMark:
+ dtok.dtt = DTT.NumLocalTimeMark;
+ raw.AddNumber(dtok.num);
+ break;
+ default:
+ // Invalid separator after number number.
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0080", dps);
+ return false;
+ }
+ break;
+ case TokenType.HebrewNumber:
+ if (tokenValue >= 100)
+ {
+ // This is a year number
+ if (raw.year == -1)
+ {
+ raw.year = tokenValue;
+ //
+ // If we have number which has 3 or more digits (like "001" or "0001"),
+ // we assume this number is a year. Save the currnet raw.numCount in
+ // raw.year.
+ //
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
+ {
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.YearEnd;
+ break;
+ case TokenType.SEP_Space:
+ dtok.dtt = DTT.YearSpace;
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if (dateParsingStates[(int)dps][(int)DTT.YearSpace] > DS.ERROR)
+ {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.YearSpace;
+ break;
+ }
+ goto default;
+ default:
+ // Invalid separator after number number.
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0090", dps);
+ return false;
+ }
+ }
+ else
+ {
+ // Invalid separator after number number.
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0100", dps);
+ return false;
+ }
+ }
+ else
+ {
+ // This is a day number
+ dtok.num = tokenValue;
+ raw.AddNumber(dtok.num);
+
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
+ {
+ //
+ // Note here we check if the numCount is less than three.
+ // When we have more than three numbers, it will be caught as error in the state machine.
+ //
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.NumEnd;
+ break;
+ case TokenType.SEP_Space:
+ case TokenType.SEP_Date:
+ dtok.dtt = DTT.NumDatesep;
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if ((dateParsingStates[(int)dps][(int)DTT.NumDatesep] == DS.ERROR)
+ && (dateParsingStates[(int)dps][(int)DTT.NumSpace] > DS.ERROR))
+ {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.NumSpace;
+ }
+ else
+ {
+ dtok.dtt = DTT.NumDatesep;
+ }
+ break;
+ default:
+ // Invalid separator after number number.
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0110", dps);
+ return false;
+ }
+ }
+ break;
+ case TokenType.DayOfWeekToken:
+ if (raw.dayOfWeek == -1)
+ {
+ //
+ // This is a day of week name.
+ //
+ raw.dayOfWeek = tokenValue;
+ dtok.dtt = DTT.DayOfWeek;
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0120 (DayOfWeek seen more than 1x)", dps);
+ return false;
+ }
+ break;
+ case TokenType.MonthToken:
+ if (raw.month == -1)
+ {
+ //
+ // This is a month name
+ //
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
+ {
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.MonthEnd;
+ break;
+ case TokenType.SEP_Space:
+ dtok.dtt = DTT.MonthSpace;
+ break;
+ case TokenType.SEP_Date:
+ dtok.dtt = DTT.MonthDatesep;
+ break;
+ case TokenType.SEP_Time:
+ if (!raw.hasSameDateAndTimeSeparators)
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0130 (Invalid separator after month name)", dps);
+ return false;
+ }
+
+ // we have the date and time separators are same and getting a Month name, then change the token to MonthDatesep as
+ // we are sure we are not parsing time.
+ dtok.dtt = DTT.MonthDatesep;
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if ((dateParsingStates[(int)dps][(int)DTT.MonthDatesep] == DS.ERROR)
+ && (dateParsingStates[(int)dps][(int)DTT.MonthSpace] > DS.ERROR))
+ {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.MonthSpace;
+ }
+ else
+ {
+ dtok.dtt = DTT.MonthDatesep;
+ }
+ break;
+ default:
+ //Invalid separator after month name
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0130 (Invalid separator after month name)", dps);
+ return false;
+ }
+ raw.month = tokenValue;
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0140 (MonthToken seen more than 1x)", dps);
+ return false;
+ }
+ break;
+ case TokenType.EraToken:
+ if (result.era != -1)
+ {
+ result.era = tokenValue;
+ dtok.dtt = DTT.Era;
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0150 (EraToken seen when result.era already set)", dps);
+ return false;
+ }
+ break;
+ case TokenType.JapaneseEraToken:
+ // Special case for Japanese. We allow Japanese era name to be used even if the calendar is not Japanese Calendar.
+ result.calendar = JapaneseCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.GetJapaneseCalendarDTFI();
+ if (result.era != -1)
+ {
+ result.era = tokenValue;
+ dtok.dtt = DTT.Era;
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0160 (JapaneseEraToken seen when result.era already set)", dps);
+ return false;
+ }
+ break;
+ case TokenType.TEraToken:
+ result.calendar = TaiwanCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.GetTaiwanCalendarDTFI();
+ if (result.era != -1)
+ {
+ result.era = tokenValue;
+ dtok.dtt = DTT.Era;
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0170 (TEraToken seen when result.era already set)", dps);
+ return false;
+ }
+ break;
+ case TokenType.TimeZoneToken:
+ //
+ // This is a timezone designator
+ //
+ // NOTENOTE : for now, we only support "GMT" and "Z" (for Zulu time).
+ //
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0)
+ {
+ // Should not have two timezone offsets.
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0180 (seen GMT or Z more than 1x)", dps);
+ return false;
+ }
+ dtok.dtt = DTT.TimeZone;
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = new TimeSpan(0);
+ result.flags |= ParseFlags.TimeZoneUtc;
+ break;
+ case TokenType.EndOfString:
+ dtok.dtt = DTT.End;
+ break;
+ case TokenType.DateWordToken:
+ case TokenType.IgnorableSymbol:
+ // Date words and ignorable symbols can just be skipped over
+ break;
+ case TokenType.Am:
+ case TokenType.Pm:
+ if (raw.timeMark == TM.NotSet)
+ {
+ raw.timeMark = (TM)tokenValue;
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0190 (AM/PM timeMark already set)", dps);
+ return false;
+ }
+ break;
+ case TokenType.UnknownToken:
+ if (Char.IsLetter(str.m_current))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTimeAndParameter, nameof(SR.Format_UnknownDateTimeWord), str.Index);
+ LexTraceExit("0200", dps);
+ return (false);
+ }
+
+ if ((str.m_current == '-' || str.m_current == '+') && ((result.flags & ParseFlags.TimeZoneUsed) == 0))
+ {
+ Int32 originalIndex = str.Index;
+ if (ParseTimeZone(ref str, ref result.timeZoneOffset))
+ {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ LexTraceExit("0220 (success)", dps);
+ return true;
+ }
+ else
+ {
+ // Time zone parse attempt failed. Fall through to punctuation handling.
+ str.Index = originalIndex;
+ }
+ }
+
+ // Visual Basic implements string to date conversions on top of DateTime.Parse:
+ // CDate("#10/10/95#")
+ //
+ if (VerifyValidPunctuation(ref str))
+ {
+ LexTraceExit("0230 (success)", dps);
+ return true;
+ }
+
+ result.SetBadDateTimeFailure();
+ LexTraceExit("0240", dps);
+ return false;
+ }
+
+ LexTraceExit("0250 (success)", dps);
+ return true;
+ }
+
+ private static Boolean VerifyValidPunctuation(ref __DTString str)
+ {
+ // Compatability Behavior. Allow trailing nulls and surrounding hashes
+ Char ch = str.Value[str.Index];
+ if (ch == '#')
+ {
+ bool foundStart = false;
+ bool foundEnd = false;
+ for (int i = 0; i < str.Length; i++)
+ {
+ ch = str.Value[i];
+ if (ch == '#')
+ {
+ if (foundStart)
+ {
+ if (foundEnd)
+ {
+ // Having more than two hashes is invalid
+ return false;
+ }
+ else
+ {
+ foundEnd = true;
+ }
+ }
+ else
+ {
+ foundStart = true;
+ }
+ }
+ else if (ch == '\0')
+ {
+ // Allow nulls only at the end
+ if (!foundEnd)
+ {
+ return false;
+ }
+ }
+ else if ((!Char.IsWhiteSpace(ch)))
+ {
+ // Anything other than whitespace outside hashes is invalid
+ if (!foundStart || foundEnd)
+ {
+ return false;
+ }
+ }
+ }
+ if (!foundEnd)
+ {
+ // The has was un-paired
+ return false;
+ }
+ // Valid Hash usage: eat the hash and continue.
+ str.GetNext();
+ return true;
+ }
+ else if (ch == '\0')
+ {
+ for (int i = str.Index; i < str.Length; i++)
+ {
+ if (str.Value[i] != '\0')
+ {
+ // Nulls are only valid if they are the only trailing character
+ return false;
+ }
+ }
+ // Move to the end of the string
+ str.Index = str.Length;
+ return true;
+ }
+ return false;
+ }
+
+ private const int ORDER_YMD = 0; // The order of date is Year/Month/Day.
+ private const int ORDER_MDY = 1; // The order of date is Month/Day/Year.
+ private const int ORDER_DMY = 2; // The order of date is Day/Month/Year.
+ private const int ORDER_YDM = 3; // The order of date is Year/Day/Month
+ private const int ORDER_YM = 4; // Year/Month order.
+ private const int ORDER_MY = 5; // Month/Year order.
+ private const int ORDER_MD = 6; // Month/Day order.
+ private const int ORDER_DM = 7; // Day/Month order.
+
+ //
+ // Decide the year/month/day order from the datePattern.
+ //
+ // Return 0 for YMD, 1 for MDY, 2 for DMY, otherwise -1.
+ //
+ private static Boolean GetYearMonthDayOrder(String datePattern, DateTimeFormatInfo dtfi, out int order)
+ {
+ int yearOrder = -1;
+ int monthOrder = -1;
+ int dayOrder = -1;
+ int orderCount = 0;
+
+ bool inQuote = false;
+
+ for (int i = 0; i < datePattern.Length && orderCount < 3; i++)
+ {
+ char ch = datePattern[i];
+ if (ch == '\\' || ch == '%')
+ {
+ i++;
+ continue; // Skip next character that is escaped by this backslash
+ }
+
+ if (ch == '\'' || ch == '"')
+ {
+ inQuote = !inQuote;
+ }
+
+ if (!inQuote)
+ {
+ if (ch == 'y')
+ {
+ yearOrder = orderCount++;
+
+ //
+ // Skip all year pattern charaters.
+ //
+ for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'y'; i++)
+ {
+ // Do nothing here.
+ }
+ }
+ else if (ch == 'M')
+ {
+ monthOrder = orderCount++;
+ //
+ // Skip all month pattern characters.
+ //
+ for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'M'; i++)
+ {
+ // Do nothing here.
+ }
+ }
+ else if (ch == 'd')
+ {
+ int patternCount = 1;
+ //
+ // Skip all day pattern characters.
+ //
+ for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'd'; i++)
+ {
+ patternCount++;
+ }
+ //
+ // Make sure this is not "ddd" or "dddd", which means day of week.
+ //
+ if (patternCount <= 2)
+ {
+ dayOrder = orderCount++;
+ }
+ }
+ }
+ }
+
+ if (yearOrder == 0 && monthOrder == 1 && dayOrder == 2)
+ {
+ order = ORDER_YMD;
+ return true;
+ }
+ if (monthOrder == 0 && dayOrder == 1 && yearOrder == 2)
+ {
+ order = ORDER_MDY;
+ return true;
+ }
+ if (dayOrder == 0 && monthOrder == 1 && yearOrder == 2)
+ {
+ order = ORDER_DMY;
+ return true;
+ }
+ if (yearOrder == 0 && dayOrder == 1 && monthOrder == 2)
+ {
+ order = ORDER_YDM;
+ return true;
+ }
+ order = -1;
+ return false;
+ }
+
+ //
+ // Decide the year/month order from the pattern.
+ //
+ // Return 0 for YM, 1 for MY, otherwise -1.
+ //
+ private static Boolean GetYearMonthOrder(String pattern, DateTimeFormatInfo dtfi, out int order)
+ {
+ int yearOrder = -1;
+ int monthOrder = -1;
+ int orderCount = 0;
+
+ bool inQuote = false;
+ for (int i = 0; i < pattern.Length && orderCount < 2; i++)
+ {
+ char ch = pattern[i];
+ if (ch == '\\' || ch == '%')
+ {
+ i++;
+ continue; // Skip next character that is escaped by this backslash
+ }
+
+ if (ch == '\'' || ch == '"')
+ {
+ inQuote = !inQuote;
+ }
+
+ if (!inQuote)
+ {
+ if (ch == 'y')
+ {
+ yearOrder = orderCount++;
+
+ //
+ // Skip all year pattern charaters.
+ //
+ for (; i + 1 < pattern.Length && pattern[i + 1] == 'y'; i++)
+ {
+ }
+ }
+ else if (ch == 'M')
+ {
+ monthOrder = orderCount++;
+ //
+ // Skip all month pattern characters.
+ //
+ for (; i + 1 < pattern.Length && pattern[i + 1] == 'M'; i++)
+ {
+ }
+ }
+ }
+ }
+
+ if (yearOrder == 0 && monthOrder == 1)
+ {
+ order = ORDER_YM;
+ return true;
+ }
+ if (monthOrder == 0 && yearOrder == 1)
+ {
+ order = ORDER_MY;
+ return true;
+ }
+ order = -1;
+ return false;
+ }
+
+ //
+ // Decide the month/day order from the pattern.
+ //
+ // Return 0 for MD, 1 for DM, otherwise -1.
+ //
+ private static Boolean GetMonthDayOrder(String pattern, DateTimeFormatInfo dtfi, out int order)
+ {
+ int monthOrder = -1;
+ int dayOrder = -1;
+ int orderCount = 0;
+
+ bool inQuote = false;
+ for (int i = 0; i < pattern.Length && orderCount < 2; i++)
+ {
+ char ch = pattern[i];
+ if (ch == '\\' || ch == '%')
+ {
+ i++;
+ continue; // Skip next character that is escaped by this backslash
+ }
+
+ if (ch == '\'' || ch == '"')
+ {
+ inQuote = !inQuote;
+ }
+
+ if (!inQuote)
+ {
+ if (ch == 'd')
+ {
+ int patternCount = 1;
+ //
+ // Skip all day pattern charaters.
+ //
+ for (; i + 1 < pattern.Length && pattern[i + 1] == 'd'; i++)
+ {
+ patternCount++;
+ }
+
+ //
+ // Make sure this is not "ddd" or "dddd", which means day of week.
+ //
+ if (patternCount <= 2)
+ {
+ dayOrder = orderCount++;
+ }
+ }
+ else if (ch == 'M')
+ {
+ monthOrder = orderCount++;
+ //
+ // Skip all month pattern characters.
+ //
+ for (; i + 1 < pattern.Length && pattern[i + 1] == 'M'; i++)
+ {
+ }
+ }
+ }
+ }
+
+ if (monthOrder == 0 && dayOrder == 1)
+ {
+ order = ORDER_MD;
+ return true;
+ }
+ if (dayOrder == 0 && monthOrder == 1)
+ {
+ order = ORDER_DM;
+ return true;
+ }
+ order = -1;
+ return false;
+ }
+
+ //
+ // Adjust the two-digit year if necessary.
+ //
+ private static bool TryAdjustYear(ref DateTimeResult result, int year, out int adjustedYear)
+ {
+ if (year < 100)
+ {
+ try
+ {
+ // the Calendar classes need some real work. Many of the calendars that throw
+ // don't implement a fast/non-allocating (and non-throwing) IsValid{Year|Day|Month} method.
+ // we are making a targeted try/catch fix in the in-place release but will revisit this code
+ // in the next side-by-side release.
+ year = result.calendar.ToFourDigitYear(year);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ adjustedYear = -1;
+ return false;
+ }
+ }
+ adjustedYear = year;
+ return true;
+ }
+
+ private static bool SetDateYMD(ref DateTimeResult result, int year, int month, int day)
+ {
+ // Note, longer term these checks should be done at the end of the parse. This current
+ // way of checking creates order dependence with parsing the era name.
+ if (result.calendar.IsValidDay(year, month, day, result.era))
+ {
+ result.SetDate(year, month, day); // YMD
+ return (true);
+ }
+ return (false);
+ }
+
+ private static bool SetDateMDY(ref DateTimeResult result, int month, int day, int year)
+ {
+ return (SetDateYMD(ref result, year, month, day));
+ }
+
+ private static bool SetDateDMY(ref DateTimeResult result, int day, int month, int year)
+ {
+ return (SetDateYMD(ref result, year, month, day));
+ }
+
+ private static bool SetDateYDM(ref DateTimeResult result, int year, int day, int month)
+ {
+ return (SetDateYMD(ref result, year, month, day));
+ }
+
+ private static void GetDefaultYear(ref DateTimeResult result, ref DateTimeStyles styles)
+ {
+ result.Year = result.calendar.GetYear(GetDateTimeNow(ref result, ref styles));
+ result.flags |= ParseFlags.YearDefault;
+ }
+
+ // Processing teriminal case: DS.DX_NN
+ private static Boolean GetDayOfNN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);
+
+ GetDefaultYear(ref result, ref styles);
+
+ int order;
+ if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out order))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.MonthDayPattern);
+ return false;
+ }
+
+ if (order == ORDER_MD)
+ {
+ if (SetDateYMD(ref result, result.Year, n1, n2)) // MD
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ else
+ {
+ // ORDER_DM
+ if (SetDateYMD(ref result, result.Year, n2, n1)) // DM
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ // Processing teriminal case: DS.DX_NNN
+ private static Boolean GetDayOfNNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1); ;
+ int n3 = raw.GetNumber(2);
+
+ int order;
+ if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.ShortDatePattern);
+ return false;
+ }
+ int year;
+
+ if (order == ORDER_YMD)
+ {
+ if (TryAdjustYear(ref result, n1, out year) && SetDateYMD(ref result, year, n2, n3)) // YMD
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ else if (order == ORDER_MDY)
+ {
+ if (TryAdjustYear(ref result, n3, out year) && SetDateMDY(ref result, n1, n2, year)) // MDY
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ else if (order == ORDER_DMY)
+ {
+ if (TryAdjustYear(ref result, n3, out year) && SetDateDMY(ref result, n1, n2, year)) // DMY
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ else if (order == ORDER_YDM)
+ {
+ if (TryAdjustYear(ref result, n1, out year) && SetDateYDM(ref result, year, n2, n3)) // YDM
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ private static Boolean GetDayOfMN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ // The interpretation is based on the MonthDayPattern and YearMonthPattern
+ //
+ // MonthDayPattern YearMonthPattern Interpretation
+ // --------------- ---------------- ---------------
+ // MMMM dd MMMM yyyy Day
+ // MMMM dd yyyy MMMM Day
+ // dd MMMM MMMM yyyy Year
+ // dd MMMM yyyy MMMM Day
+ //
+ // In the first and last cases, it could be either or neither, but a day is a better default interpretation
+ // than a 2 digit year.
+
+ int monthDayOrder;
+ if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.MonthDayPattern);
+ return false;
+ }
+ if (monthDayOrder == ORDER_DM)
+ {
+ int yearMonthOrder;
+ if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.YearMonthPattern);
+ return false;
+ }
+ if (yearMonthOrder == ORDER_MY)
+ {
+ int year;
+ if (!TryAdjustYear(ref result, raw.GetNumber(0), out year) || !SetDateYMD(ref result, year, raw.month, 1))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ return true;
+ }
+ }
+
+ GetDefaultYear(ref result, ref styles);
+ if (!SetDateYMD(ref result, result.Year, raw.month, raw.GetNumber(0)))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ return true;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // Actions:
+ // Deal with the terminal state for Hebrew Month/Day pattern
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ private static Boolean GetHebrewDayOfNM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ int monthDayOrder;
+ if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.MonthDayPattern);
+ return false;
+ }
+ result.Month = raw.month;
+ if (monthDayOrder == ORDER_DM || monthDayOrder == ORDER_MD)
+ {
+ if (result.calendar.IsValidDay(result.Year, result.Month, raw.GetNumber(0), result.era))
+ {
+ result.Day = raw.GetNumber(0);
+ return true;
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ private static Boolean GetDayOfNM(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ // The interpretation is based on the MonthDayPattern and YearMonthPattern
+ //
+ // MonthDayPattern YearMonthPattern Interpretation
+ // --------------- ---------------- ---------------
+ // MMMM dd MMMM yyyy Day
+ // MMMM dd yyyy MMMM Year
+ // dd MMMM MMMM yyyy Day
+ // dd MMMM yyyy MMMM Day
+ //
+ // In the first and last cases, it could be either or neither, but a day is a better default interpretation
+ // than a 2 digit year.
+
+ int monthDayOrder;
+ if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.MonthDayPattern);
+ return false;
+ }
+ if (monthDayOrder == ORDER_MD)
+ {
+ int yearMonthOrder;
+ if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.YearMonthPattern);
+ return false;
+ }
+ if (yearMonthOrder == ORDER_YM)
+ {
+ int year;
+ if (!TryAdjustYear(ref result, raw.GetNumber(0), out year) || !SetDateYMD(ref result, year, raw.month, 1))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ return true;
+ }
+ }
+
+ GetDefaultYear(ref result, ref styles);
+ if (!SetDateYMD(ref result, result.Year, raw.month, raw.GetNumber(0)))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ return true;
+ }
+
+ private static Boolean GetDayOfMNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);
+
+ int order;
+ if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.ShortDatePattern);
+ return false;
+ }
+ int year;
+
+ if (order == ORDER_MDY)
+ {
+ if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
+ {
+ result.SetDate(year, raw.month, n1); // MDY
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
+ {
+ result.SetDate(year, raw.month, n2); // YMD
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ else if (order == ORDER_YMD)
+ {
+ if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
+ {
+ result.SetDate(year, raw.month, n2); // YMD
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ else if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
+ {
+ result.SetDate(year, raw.month, n1); // DMY
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ else if (order == ORDER_DMY)
+ {
+ if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
+ {
+ result.SetDate(year, raw.month, n1); // DMY
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
+ {
+ result.SetDate(year, raw.month, n2); // YMD
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ private static Boolean GetDayOfYNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);
+ String pattern = dtfi.ShortDatePattern;
+
+ // For compatibility, don't throw if we can't determine the order, but default to YMD instead
+ int order;
+ if (GetYearMonthDayOrder(pattern, dtfi, out order) && order == ORDER_YDM)
+ {
+ if (SetDateYMD(ref result, raw.year, n2, n1))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true; // Year + DM
+ }
+ }
+ else
+ {
+ if (SetDateYMD(ref result, raw.year, n1, n2))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true; // Year + MD
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ private static Boolean GetDayOfNNY(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);
+
+ int order;
+ if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.ShortDatePattern);
+ return false;
+ }
+
+ if (order == ORDER_MDY || order == ORDER_YMD)
+ {
+ if (SetDateYMD(ref result, raw.year, n1, n2))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true; // MD + Year
+ }
+ }
+ else
+ {
+ if (SetDateYMD(ref result, raw.year, n2, n1))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true; // DM + Year
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+
+ private static Boolean GetDayOfYMN(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ if (SetDateYMD(ref result, raw.year, raw.month, raw.GetNumber(0)))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ private static Boolean GetDayOfYN(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ if (SetDateYMD(ref result, raw.year, raw.GetNumber(0), 1))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ private static Boolean GetDayOfYM(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0)
+ {
+ // Multiple dates in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ if (SetDateYMD(ref result, raw.year, raw.month, 1))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ private static void AdjustTimeMark(DateTimeFormatInfo dtfi, ref DateTimeRawInfo raw)
+ {
+ // Specail case for culture which uses AM as empty string.
+ // E.g. af-ZA (0x0436)
+ // S1159 \x0000
+ // S2359 nm
+ // In this case, if we are parsing a string like "2005/09/14 12:23", we will assume this is in AM.
+
+ if (raw.timeMark == TM.NotSet)
+ {
+ if (dtfi.AMDesignator != null && dtfi.PMDesignator != null)
+ {
+ if (dtfi.AMDesignator.Length == 0 && dtfi.PMDesignator.Length != 0)
+ {
+ raw.timeMark = TM.AM;
+ }
+ if (dtfi.PMDesignator.Length == 0 && dtfi.AMDesignator.Length != 0)
+ {
+ raw.timeMark = TM.PM;
+ }
+ }
+ }
+ }
+
+ //
+ // Adjust hour according to the time mark.
+ //
+ private static Boolean AdjustHour(ref int hour, TM timeMark)
+ {
+ if (timeMark != TM.NotSet)
+ {
+ if (timeMark == TM.AM)
+ {
+ if (hour < 0 || hour > 12)
+ {
+ return false;
+ }
+ hour = (hour == 12) ? 0 : hour;
+ }
+ else
+ {
+ if (hour < 0 || hour > 23)
+ {
+ return false;
+ }
+ if (hour < 12)
+ {
+ hour += 12;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static Boolean GetTimeOfN(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if ((result.flags & ParseFlags.HaveTime) != 0)
+ {
+ // Multiple times in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ //
+ // In this case, we need a time mark. Check if so.
+ //
+ if (raw.timeMark == TM.NotSet)
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ result.Hour = raw.GetNumber(0);
+ result.flags |= ParseFlags.HaveTime;
+ return true;
+ }
+
+ private static Boolean GetTimeOfNN(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ Debug.Assert(raw.numCount >= 2, "raw.numCount >= 2");
+ if ((result.flags & ParseFlags.HaveTime) != 0)
+ {
+ // Multiple times in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ result.Hour = raw.GetNumber(0);
+ result.Minute = raw.GetNumber(1);
+ result.flags |= ParseFlags.HaveTime;
+ return true;
+ }
+
+ private static Boolean GetTimeOfNNN(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if ((result.flags & ParseFlags.HaveTime) != 0)
+ {
+ // Multiple times in the input string
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ Debug.Assert(raw.numCount >= 3, "raw.numCount >= 3");
+ result.Hour = raw.GetNumber(0);
+ result.Minute = raw.GetNumber(1);
+ result.Second = raw.GetNumber(2);
+ result.flags |= ParseFlags.HaveTime;
+ return true;
+ }
+
+ //
+ // Processing terminal state: A Date suffix followed by one number.
+ //
+ private static Boolean GetDateOfDSN(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if (raw.numCount != 1 || result.Day != -1)
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ result.Day = raw.GetNumber(0);
+ return true;
+ }
+
+ private static Boolean GetDateOfNDS(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if (result.Month == -1)
+ {
+ //Should have a month suffix
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ if (result.Year != -1)
+ {
+ // Already has a year suffix
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ if (!TryAdjustYear(ref result, raw.GetNumber(0), out result.Year))
+ {
+ // the year value is out of range
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ result.Day = 1;
+ return true;
+ }
+
+ private static Boolean GetDateOfNNDS(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ // For partial CJK Dates, the only valid formats are with a specified year, followed by two numbers, which
+ // will be the Month and Day, and with a specified Month, when the numbers are either the year and day or
+ // day and year, depending on the short date pattern.
+
+ if ((result.flags & ParseFlags.HaveYear) != 0)
+ {
+ if (((result.flags & ParseFlags.HaveMonth) == 0) && ((result.flags & ParseFlags.HaveDay) == 0))
+ {
+ if (TryAdjustYear(ref result, raw.year, out result.Year) && SetDateYMD(ref result, result.Year, raw.GetNumber(0), raw.GetNumber(1)))
+ {
+ return true;
+ }
+ }
+ }
+ else if ((result.flags & ParseFlags.HaveMonth) != 0)
+ {
+ if (((result.flags & ParseFlags.HaveYear) == 0) && ((result.flags & ParseFlags.HaveDay) == 0))
+ {
+ int order;
+ if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.ShortDatePattern);
+ return false;
+ }
+ int year;
+ if (order == ORDER_YMD)
+ {
+ if (TryAdjustYear(ref result, raw.GetNumber(0), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(1)))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (TryAdjustYear(ref result, raw.GetNumber(1), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(0)))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ //
+ // A date suffix is found, use this method to put the number into the result.
+ //
+ private static bool ProcessDateTimeSuffix(ref DateTimeResult result, ref DateTimeRawInfo raw, ref DateTimeToken dtok)
+ {
+ switch (dtok.suffix)
+ {
+ case TokenType.SEP_YearSuff:
+ if ((result.flags & ParseFlags.HaveYear) != 0)
+ {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveYear;
+ result.Year = raw.year = dtok.num;
+ break;
+ case TokenType.SEP_MonthSuff:
+ if ((result.flags & ParseFlags.HaveMonth) != 0)
+ {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveMonth;
+ result.Month = raw.month = dtok.num;
+ break;
+ case TokenType.SEP_DaySuff:
+ if ((result.flags & ParseFlags.HaveDay) != 0)
+ {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveDay;
+ result.Day = dtok.num;
+ break;
+ case TokenType.SEP_HourSuff:
+ if ((result.flags & ParseFlags.HaveHour) != 0)
+ {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveHour;
+ result.Hour = dtok.num;
+ break;
+ case TokenType.SEP_MinuteSuff:
+ if ((result.flags & ParseFlags.HaveMinute) != 0)
+ {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveMinute;
+ result.Minute = dtok.num;
+ break;
+ case TokenType.SEP_SecondSuff:
+ if ((result.flags & ParseFlags.HaveSecond) != 0)
+ {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveSecond;
+ result.Second = dtok.num;
+ break;
+ }
+ return true;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // This is used by DateTime.Parse().
+ // Process the terminal state for the Hebrew calendar parsing.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static Boolean ProcessHebrewTerminalState(DS dps, ref __DTString str, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ // The following are accepted terminal state for Hebrew date.
+ switch (dps)
+ {
+ case DS.DX_MNN:
+ // Deal with the default long/short date format when the year number is ambigous (i.e. year < 100).
+ raw.year = raw.GetNumber(1);
+ if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ return false;
+ }
+ if (!GetDayOfMNN(ref result, ref raw, dtfi))
+ {
+ return false;
+ }
+ break;
+ case DS.DX_YMN:
+ // Deal with the default long/short date format when the year number is NOT ambigous (i.e. year >= 100).
+ if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ return false;
+ }
+ if (!GetDayOfYMN(ref result, ref raw))
+ {
+ return false;
+ }
+ break;
+ case DS.DX_NNY:
+ // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000.
+ // E.g. if the year is 5763, we only format as 763. so we do the reverse when parsing.
+ if (raw.year < 1000)
+ {
+ raw.year += 5000;
+ }
+ if (!GetDayOfNNY(ref result, ref raw, dtfi))
+ {
+ return false;
+ }
+ if (!dtfi.YearMonthAdjustment(ref result.Year, ref raw.month, true))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ return false;
+ }
+ break;
+ case DS.DX_NM:
+ case DS.DX_MN:
+ // Deal with Month/Day pattern.
+ GetDefaultYear(ref result, ref styles);
+ if (!dtfi.YearMonthAdjustment(ref result.Year, ref raw.month, true))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ return false;
+ }
+ if (!GetHebrewDayOfNM(ref result, ref raw, dtfi))
+ {
+ return false;
+ }
+ break;
+ case DS.DX_YM:
+ // Deal with Year/Month pattern.
+ if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ return false;
+ }
+ if (!GetDayOfYM(ref result, ref raw))
+ {
+ return false;
+ }
+ break;
+ case DS.TX_N:
+ // Deal hour + AM/PM
+ if (!GetTimeOfN(ref result, ref raw))
+ {
+ return false;
+ }
+ break;
+ case DS.TX_NN:
+ if (!GetTimeOfNN(ref result, ref raw))
+ {
+ return false;
+ }
+ break;
+ case DS.TX_NNN:
+ if (!GetTimeOfNNN(ref result, ref raw))
+ {
+ return false;
+ }
+ break;
+ default:
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ if (dps > DS.ERROR)
+ {
+ //
+ // We have reached a terminal state. Reset the raw num count.
+ //
+ raw.numCount = 0;
+ }
+ return true;
+ }
+
+ //
+ // A terminal state has been reached, call the appropriate function to fill in the parsing result.
+ // Return true if the state is a terminal state.
+ //
+ internal static Boolean ProcessTerminalState(DS dps, ref __DTString str, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ bool passed = true;
+ switch (dps)
+ {
+ case DS.DX_NN:
+ passed = GetDayOfNN(ref result, ref styles, ref raw, dtfi);
+ break;
+ case DS.DX_NNN:
+ passed = GetDayOfNNN(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_MN:
+ passed = GetDayOfMN(ref result, ref styles, ref raw, dtfi);
+ break;
+ case DS.DX_NM:
+ passed = GetDayOfNM(ref result, ref styles, ref raw, dtfi);
+ break;
+ case DS.DX_MNN:
+ passed = GetDayOfMNN(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_DS:
+ // The result has got the correct value. No need to process.
+ passed = true;
+ break;
+ case DS.DX_YNN:
+ passed = GetDayOfYNN(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_NNY:
+ passed = GetDayOfNNY(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_YMN:
+ passed = GetDayOfYMN(ref result, ref raw);
+ break;
+ case DS.DX_YN:
+ passed = GetDayOfYN(ref result, ref raw);
+ break;
+ case DS.DX_YM:
+ passed = GetDayOfYM(ref result, ref raw);
+ break;
+ case DS.TX_N:
+ passed = GetTimeOfN(ref result, ref raw);
+ break;
+ case DS.TX_NN:
+ passed = GetTimeOfNN(ref result, ref raw);
+ break;
+ case DS.TX_NNN:
+ passed = GetTimeOfNNN(ref result, ref raw);
+ break;
+ case DS.TX_TS:
+ // The result has got the correct value. Nothing to do.
+ passed = true;
+ break;
+ case DS.DX_DSN:
+ passed = GetDateOfDSN(ref result, ref raw);
+ break;
+ case DS.DX_NDS:
+ passed = GetDateOfNDS(ref result, ref raw);
+ break;
+ case DS.DX_NNDS:
+ passed = GetDateOfNNDS(ref result, ref raw, dtfi);
+ break;
+ }
+
+ PTSTraceExit(dps, passed);
+ if (!passed)
+ {
+ return false;
+ }
+
+ if (dps > DS.ERROR)
+ {
+ //
+ // We have reached a terminal state. Reset the raw num count.
+ //
+ raw.numCount = 0;
+ }
+ return true;
+ }
+
+ internal static DateTime Parse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles)
+ {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ result.Init(s);
+ if (TryParse(s, dtfi, styles, ref result))
+ {
+ return result.parsedDate;
+ }
+ else
+ {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+ internal static DateTime Parse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out TimeSpan offset)
+ {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ result.Init(s);
+ result.flags |= ParseFlags.CaptureOffset;
+ if (TryParse(s, dtfi, styles, ref result))
+ {
+ offset = result.timeZoneOffset;
+ return result.parsedDate;
+ }
+ else
+ {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+
+ internal static bool TryParse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result)
+ {
+ result = DateTime.MinValue;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init(s);
+ if (TryParse(s, dtfi, styles, ref resultData))
+ {
+ result = resultData.parsedDate;
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryParse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result, out TimeSpan offset)
+ {
+ result = DateTime.MinValue;
+ offset = TimeSpan.Zero;
+ DateTimeResult parseResult = new DateTimeResult(); // The buffer to store the parsing result.
+ parseResult.Init(s);
+ parseResult.flags |= ParseFlags.CaptureOffset;
+ if (TryParse(s, dtfi, styles, ref parseResult))
+ {
+ result = parseResult.parsedDate;
+ offset = parseResult.timeZoneOffset;
+ return true;
+ }
+ return false;
+ }
+
+
+ //
+ // This is the real method to do the parsing work.
+ //
+ internal static bool TryParse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result)
+ {
+ if (s.Length == 0)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDateTime));
+ return false;
+ }
+
+ Debug.Assert(dtfi != null, "dtfi == null");
+
+#if _LOGGING
+ DTFITrace(dtfi);
+#endif
+
+ DateTime time;
+ //
+ // First try the predefined format.
+ //
+
+ DS dps = DS.BEGIN; // Date Parsing State.
+ bool reachTerminalState = false;
+
+ DateTimeToken dtok = new DateTimeToken(); // The buffer to store the parsing token.
+ dtok.suffix = TokenType.SEP_Unk;
+ DateTimeRawInfo raw = new DateTimeRawInfo(); // The buffer to store temporary parsing information.
+ unsafe
+ {
+ Int32* numberPointer = stackalloc Int32[3];
+ raw.Init(numberPointer);
+ }
+ raw.hasSameDateAndTimeSeparators = dtfi.DateSeparator.Equals(dtfi.TimeSeparator, StringComparison.Ordinal);
+
+ result.calendar = dtfi.Calendar;
+ result.era = Calendar.CurrentEra;
+
+ //
+ // The string to be parsed. Use a __DTString wrapper so that we can trace the index which
+ // indicates the begining of next token.
+ //
+ __DTString str = new __DTString(s, dtfi);
+
+ str.GetNext();
+
+ //
+ // The following loop will break out when we reach the end of the str.
+ //
+ do
+ {
+ //
+ // Call the lexer to get the next token.
+ //
+ // If we find a era in Lex(), the era value will be in raw.era.
+ if (!Lex(dps, ref str, ref dtok, ref raw, ref result, ref dtfi, styles))
+ {
+ TPTraceExit("0000", dps);
+ return false;
+ }
+
+ //
+ // If the token is not unknown, process it.
+ // Otherwise, just discard it.
+ //
+ if (dtok.dtt != DTT.Unk)
+ {
+ //
+ // Check if we got any CJK Date/Time suffix.
+ // Since the Date/Time suffix tells us the number belongs to year/month/day/hour/minute/second,
+ // store the number in the appropriate field in the result.
+ //
+ if (dtok.suffix != TokenType.SEP_Unk)
+ {
+ if (!ProcessDateTimeSuffix(ref result, ref raw, ref dtok))
+ {
+ result.SetBadDateTimeFailure();
+ TPTraceExit("0010", dps);
+ return false;
+ }
+
+ dtok.suffix = TokenType.SEP_Unk; // Reset suffix to SEP_Unk;
+ }
+
+ if (dtok.dtt == DTT.NumLocalTimeMark)
+ {
+ if (dps == DS.D_YNd || dps == DS.D_YN)
+ {
+ // Consider this as ISO 8601 format:
+ // "yyyy-MM-dd'T'HH:mm:ss" 1999-10-31T02:00:00
+ TPTraceExit("0020", dps);
+ return (ParseISO8601(ref raw, ref str, styles, ref result));
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ TPTraceExit("0030", dps);
+ return false;
+ }
+ }
+
+ if (raw.hasSameDateAndTimeSeparators)
+ {
+ if (dtok.dtt == DTT.YearEnd || dtok.dtt == DTT.YearSpace || dtok.dtt == DTT.YearDateSep)
+ {
+ // When time and date separators are same and we are hitting a year number while the first parsed part of the string was recognized
+ // as part of time (and not a date) DS.T_Nt, DS.T_NNt then change the state to be a date so we try to parse it as a date instead
+ if (dps == DS.T_Nt)
+ {
+ dps = DS.D_Nd;
+ }
+ if (dps == DS.T_NNt)
+ {
+ dps = DS.D_NNd;
+ }
+ }
+
+ bool atEnd = str.AtEnd();
+ if (dateParsingStates[(int)dps][(int)dtok.dtt] == DS.ERROR || atEnd)
+ {
+ switch (dtok.dtt)
+ {
+ // we have the case of Serbia have dates in forms 'd.M.yyyy.' so we can expect '.' after the date parts.
+ // changing the token to end with space instead of Date Separator will avoid failing the parsing.
+
+ case DTT.YearDateSep: dtok.dtt = atEnd ? DTT.YearEnd : DTT.YearSpace; break;
+ case DTT.NumDatesep: dtok.dtt = atEnd ? DTT.NumEnd : DTT.NumSpace; break;
+ case DTT.NumTimesep: dtok.dtt = atEnd ? DTT.NumEnd : DTT.NumSpace; break;
+ case DTT.MonthDatesep: dtok.dtt = atEnd ? DTT.MonthEnd : DTT.MonthSpace; break;
+ }
+ }
+ }
+
+ //
+ // Advance to the next state, and continue
+ //
+ dps = dateParsingStates[(int)dps][(int)dtok.dtt];
+
+ if (dps == DS.ERROR)
+ {
+ result.SetBadDateTimeFailure();
+ TPTraceExit("0040 (invalid state transition)", dps);
+ return false;
+ }
+ else if (dps > DS.ERROR)
+ {
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0)
+ {
+ if (!ProcessHebrewTerminalState(dps, ref str, ref result, ref styles, ref raw, dtfi))
+ {
+ TPTraceExit("0050 (ProcessHebrewTerminalState)", dps);
+ return false;
+ }
+ }
+ else
+ {
+ if (!ProcessTerminalState(dps, ref str, ref result, ref styles, ref raw, dtfi))
+ {
+ TPTraceExit("0060 (ProcessTerminaltState)", dps);
+ return false;
+ }
+ }
+ reachTerminalState = true;
+
+ //
+ // If we have reached a terminal state, start over from DS.BEGIN again.
+ // For example, when we parsed "1999-12-23 13:30", we will reach a terminal state at "1999-12-23",
+ // and we start over so we can continue to parse "12:30".
+ //
+ dps = DS.BEGIN;
+ }
+ }
+ } while (dtok.dtt != DTT.End && dtok.dtt != DTT.NumEnd && dtok.dtt != DTT.MonthEnd);
+
+ if (!reachTerminalState)
+ {
+ result.SetBadDateTimeFailure();
+ TPTraceExit("0070 (did not reach terminal state)", dps);
+ return false;
+ }
+
+ AdjustTimeMark(dtfi, ref raw);
+ if (!AdjustHour(ref result.Hour, raw.timeMark))
+ {
+ result.SetBadDateTimeFailure();
+ TPTraceExit("0080 (AdjustHour)", dps);
+ return false;
+ }
+
+ // Check if the parased string only contains hour/minute/second values.
+ bool bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
+
+ //
+ // Check if any year/month/day is missing in the parsing string.
+ // If yes, get the default value from today's date.
+ //
+ if (!CheckDefaultDateTime(ref result, ref result.calendar, styles))
+ {
+ TPTraceExit("0090 (failed to fill in missing year/month/day defaults)", dps);
+ return false;
+ }
+
+ if (!result.calendar.TryToDateTime(result.Year, result.Month, result.Day,
+ result.Hour, result.Minute, result.Second, 0, result.era, out time))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ TPTraceExit("0100 (result.calendar.TryToDateTime)", dps);
+ return false;
+ }
+ if (raw.fraction > 0)
+ {
+ time = time.AddTicks((long)Math.Round(raw.fraction * Calendar.TicksPerSecond));
+ }
+
+ //
+ // We have to check day of week before we adjust to the time zone.
+ // Otherwise, the value of day of week may change after adjusting to the time zone.
+ //
+ if (raw.dayOfWeek != -1)
+ {
+ //
+ // Check if day of week is correct.
+ //
+ if (raw.dayOfWeek != (int)result.calendar.GetDayOfWeek(time))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTime, nameof(SR.Format_BadDayOfWeek));
+ TPTraceExit("0110 (dayOfWeek check)", dps);
+ return false;
+ }
+ }
+
+ result.parsedDate = time;
+
+ if (!DetermineTimeZoneAdjustments(ref str, ref result, styles, bTimeOnly))
+ {
+ TPTraceExit("0120 (DetermineTimeZoneAdjustments)", dps);
+ return false;
+ }
+ TPTraceExit("0130 (success)", dps);
+ return true;
+ }
+
+
+ // Handles time zone adjustments and sets DateTimeKind values as required by the styles
+ private static Boolean DetermineTimeZoneAdjustments(ref __DTString str, ref DateTimeResult result, DateTimeStyles styles, Boolean bTimeOnly)
+ {
+ if ((result.flags & ParseFlags.CaptureOffset) != 0)
+ {
+ // This is a DateTimeOffset parse, so the offset will actually be captured directly, and
+ // no adjustment is required in most cases
+ return DateTimeOffsetTimeZonePostProcessing(ref str, ref result, styles);
+ }
+ else
+ {
+ Int64 offsetTicks = result.timeZoneOffset.Ticks;
+
+ // the DateTime offset must be within +- 14:00 hours.
+ if (offsetTicks < DateTimeOffset.MinOffset || offsetTicks > DateTimeOffset.MaxOffset)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTime, nameof(SR.Format_OffsetOutOfRange));
+ return false;
+ }
+ }
+
+ // The flags AssumeUniveral and AssumeLocal only apply when the input does not have a time zone
+ if ((result.flags & ParseFlags.TimeZoneUsed) == 0)
+ {
+ // If AssumeLocal or AssumeLocal is used, there will always be a kind specified. As in the
+ // case when a time zone is present, it will default to being local unless AdjustToUniversal
+ // is present. These comparisons determine whether setting the kind is sufficient, or if a
+ // time zone adjustment is required. For consistentcy with the rest of parsing, it is desirable
+ // to fall through to the Adjust methods below, so that there is consist handling of boundary
+ // cases like wrapping around on time-only dates and temporarily allowing an adjusted date
+ // to exceed DateTime.MaxValue
+ if ((styles & DateTimeStyles.AssumeLocal) != 0)
+ {
+ if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
+ {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeZoneInfo.GetLocalUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+ else
+ {
+ result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Local);
+ return true;
+ }
+ }
+ else if ((styles & DateTimeStyles.AssumeUniversal) != 0)
+ {
+ if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
+ {
+ result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc);
+ return true;
+ }
+ else
+ {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+ }
+ else
+ {
+ // No time zone and no Assume flags, so DateTimeKind.Unspecified is fine
+ Debug.Assert(result.parsedDate.Kind == DateTimeKind.Unspecified, "result.parsedDate.Kind == DateTimeKind.Unspecified");
+ return true;
+ }
+ }
+
+ if (((styles & DateTimeStyles.RoundtripKind) != 0) && ((result.flags & ParseFlags.TimeZoneUtc) != 0))
+ {
+ result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc);
+ return true;
+ }
+
+ if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
+ {
+ return (AdjustTimeZoneToUniversal(ref result));
+ }
+ return (AdjustTimeZoneToLocal(ref result, bTimeOnly));
+ }
+
+ // Apply validation and adjustments specific to DateTimeOffset
+ private static Boolean DateTimeOffsetTimeZonePostProcessing(ref __DTString str, ref DateTimeResult result, DateTimeStyles styles)
+ {
+ // For DateTimeOffset, default to the Utc or Local offset when an offset was not specified by
+ // the input string.
+ if ((result.flags & ParseFlags.TimeZoneUsed) == 0)
+ {
+ if ((styles & DateTimeStyles.AssumeUniversal) != 0)
+ {
+ // AssumeUniversal causes the offset to default to zero (0)
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+ else
+ {
+ // AssumeLocal causes the offset to default to Local. This flag is on by default for DateTimeOffset.
+ result.timeZoneOffset = TimeZoneInfo.GetLocalUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+ }
+
+ Int64 offsetTicks = result.timeZoneOffset.Ticks;
+
+ // there should be no overflow, because the offset can be no more than -+100 hours and the date already
+ // fits within a DateTime.
+ Int64 utcTicks = result.parsedDate.Ticks - offsetTicks;
+
+ // For DateTimeOffset, both the parsed time and the corresponding UTC value must be within the boundaries
+ // of a DateTime instance.
+ if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTime, nameof(SR.Format_UTCOutOfRange));
+ return false;
+ }
+
+ // the offset must be within +- 14:00 hours.
+ if (offsetTicks < DateTimeOffset.MinOffset || offsetTicks > DateTimeOffset.MaxOffset)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTime, nameof(SR.Format_OffsetOutOfRange));
+ return false;
+ }
+
+ // DateTimeOffset should still honor the AdjustToUniversal flag for consistency with DateTime. It means you
+ // want to return an adjusted UTC value, so store the utcTicks in the DateTime and set the offset to zero
+ if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
+ {
+ if (((result.flags & ParseFlags.TimeZoneUsed) == 0) && ((styles & DateTimeStyles.AssumeUniversal) == 0))
+ {
+ // Handle the special case where the timeZoneOffset was defaulted to Local
+ Boolean toUtcResult = AdjustTimeZoneToUniversal(ref result);
+ result.timeZoneOffset = TimeSpan.Zero;
+ return toUtcResult;
+ }
+
+ // The constructor should always succeed because of the range check earlier in the function
+ // Although it is UTC, internally DateTimeOffset does not use this flag
+ result.parsedDate = new DateTime(utcTicks, DateTimeKind.Utc);
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+
+ return true;
+ }
+
+
+ //
+ // Adjust the specified time to universal time based on the supplied timezone.
+ // E.g. when parsing "2001/06/08 14:00-07:00",
+ // the time is 2001/06/08 14:00, and timeZoneOffset = -07:00.
+ // The result will be "2001/06/08 21:00"
+ //
+ private static Boolean AdjustTimeZoneToUniversal(ref DateTimeResult result)
+ {
+ long resultTicks = result.parsedDate.Ticks;
+ resultTicks -= result.timeZoneOffset.Ticks;
+ if (resultTicks < 0)
+ {
+ resultTicks += Calendar.TicksPerDay;
+ }
+
+ if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTime, nameof(SR.Format_DateOutOfRange));
+ return false;
+ }
+ result.parsedDate = new DateTime(resultTicks, DateTimeKind.Utc);
+ return true;
+ }
+
+ //
+ // Adjust the specified time to universal time based on the supplied timezone,
+ // and then convert to local time.
+ // E.g. when parsing "2001/06/08 14:00-04:00", and local timezone is GMT-7.
+ // the time is 2001/06/08 14:00, and timeZoneOffset = -05:00.
+ // The result will be "2001/06/08 11:00"
+ //
+ private static Boolean AdjustTimeZoneToLocal(ref DateTimeResult result, bool bTimeOnly)
+ {
+ long resultTicks = result.parsedDate.Ticks;
+ // Convert to local ticks
+ TimeZoneInfo tz = TimeZoneInfo.Local;
+ Boolean isAmbiguousLocalDst = false;
+ if (resultTicks < Calendar.TicksPerDay)
+ {
+ //
+ // This is time of day.
+ //
+
+ // Adjust timezone.
+ resultTicks -= result.timeZoneOffset.Ticks;
+ // If the time is time of day, use the current timezone offset.
+ resultTicks += tz.GetUtcOffset(bTimeOnly ? DateTime.Now : result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks;
+
+ if (resultTicks < 0)
+ {
+ resultTicks += Calendar.TicksPerDay;
+ }
+ }
+ else
+ {
+ // Adjust timezone to GMT.
+ resultTicks -= result.timeZoneOffset.Ticks;
+ if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks)
+ {
+ // If the result ticks is greater than DateTime.MaxValue, we can not create a DateTime from this ticks.
+ // In this case, keep using the old code.
+ resultTicks += tz.GetUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks;
+ }
+ else
+ {
+ // Convert the GMT time to local time.
+ DateTime utcDt = new DateTime(resultTicks, DateTimeKind.Utc);
+ Boolean isDaylightSavings = false;
+ resultTicks += TimeZoneInfo.GetUtcOffsetFromUtc(utcDt, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst).Ticks;
+ }
+ }
+ if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks)
+ {
+ result.parsedDate = DateTime.MinValue;
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTime, nameof(SR.Format_DateOutOfRange));
+ return false;
+ }
+ result.parsedDate = new DateTime(resultTicks, DateTimeKind.Local, isAmbiguousLocalDst);
+ return true;
+ }
+
+ //
+ // Parse the ISO8601 format string found during Parse();
+ //
+ //
+ private static bool ParseISO8601(ref DateTimeRawInfo raw, ref __DTString str, DateTimeStyles styles, ref DateTimeResult result)
+ {
+ if (raw.year < 0 || raw.GetNumber(0) < 0 || raw.GetNumber(1) < 0)
+ {
+ }
+ str.Index--;
+ int hour, minute;
+ int second = 0;
+ double partSecond = 0;
+
+ str.SkipWhiteSpaces();
+ if (!ParseDigits(ref str, 2, out hour))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ str.SkipWhiteSpaces();
+ if (!str.Match(':'))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ str.SkipWhiteSpaces();
+ if (!ParseDigits(ref str, 2, out minute))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ str.SkipWhiteSpaces();
+ if (str.Match(':'))
+ {
+ str.SkipWhiteSpaces();
+ if (!ParseDigits(ref str, 2, out second))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ if (str.Match('.'))
+ {
+ if (!ParseFraction(ref str, out partSecond))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ str.Index--;
+ }
+ str.SkipWhiteSpaces();
+ }
+ if (str.GetNext())
+ {
+ char ch = str.GetChar();
+ if (ch == '+' || ch == '-')
+ {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ if (!ParseTimeZone(ref str, ref result.timeZoneOffset))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+ else if (ch == 'Z' || ch == 'z')
+ {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ result.flags |= ParseFlags.TimeZoneUtc;
+ }
+ else
+ {
+ str.Index--;
+ }
+ str.SkipWhiteSpaces();
+ if (str.Match('#'))
+ {
+ if (!VerifyValidPunctuation(ref str))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ str.SkipWhiteSpaces();
+ }
+ if (str.Match('\0'))
+ {
+ if (!VerifyValidPunctuation(ref str))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+ if (str.GetNext())
+ {
+ // If this is true, there were non-white space characters remaining in the DateTime
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+
+ DateTime time;
+ Calendar calendar = GregorianCalendar.GetDefaultInstance();
+ if (!calendar.TryToDateTime(raw.year, raw.GetNumber(0), raw.GetNumber(1),
+ hour, minute, second, 0, result.era, out time))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ return false;
+ }
+
+ time = time.AddTicks((long)Math.Round(partSecond * Calendar.TicksPerSecond));
+ result.parsedDate = time;
+ if (!DetermineTimeZoneAdjustments(ref str, ref result, styles, false))
+ {
+ return false;
+ }
+ return true;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Parse the current word as a Hebrew number.
+ // This is used by DateTime.ParseExact().
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static bool MatchHebrewDigits(ref __DTString str, int digitLen, out int number)
+ {
+ number = 0;
+
+ // Create a context object so that we can parse the Hebrew number text character by character.
+ HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
+
+ // Set this to ContinueParsing so that we will run the following while loop in the first time.
+ HebrewNumberParsingState state = HebrewNumberParsingState.ContinueParsing;
+
+ while (state == HebrewNumberParsingState.ContinueParsing && str.GetNext())
+ {
+ state = HebrewNumber.ParseByChar(str.GetChar(), ref context);
+ }
+
+ if (state == HebrewNumberParsingState.FoundEndOfHebrewNumber)
+ {
+ // If we have reached a terminal state, update the result and returns.
+ number = context.result;
+ return (true);
+ }
+
+ // If we run out of the character before reaching FoundEndOfHebrewNumber, or
+ // the state is InvalidHebrewNumber or ContinueParsing, we fail to match a Hebrew number.
+ // Return an error.
+ return false;
+ }
+
+ /*=================================ParseDigits==================================
+ **Action: Parse the number string in __DTString that are formatted using
+ ** the following patterns:
+ ** "0", "00", and "000..0"
+ **Returns: the integer value
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if error in parsing number.
+ ==============================================================================*/
+
+ internal static bool ParseDigits(ref __DTString str, int digitLen, out int result)
+ {
+ if (digitLen == 1)
+ {
+ // 1 really means 1 or 2 for this call
+ return ParseDigits(ref str, 1, 2, out result);
+ }
+ else
+ {
+ return ParseDigits(ref str, digitLen, digitLen, out result);
+ }
+ }
+
+ internal static bool ParseDigits(ref __DTString str, int minDigitLen, int maxDigitLen, out int result)
+ {
+ Debug.Assert(minDigitLen > 0, "minDigitLen > 0");
+ Debug.Assert(maxDigitLen < 9, "maxDigitLen < 9");
+ Debug.Assert(minDigitLen <= maxDigitLen, "minDigitLen <= maxDigitLen");
+ int localResult = 0;
+ int startingIndex = str.Index;
+ int tokenLength = 0;
+ while (tokenLength < maxDigitLen)
+ {
+ if (!str.GetNextDigit())
+ {
+ str.Index--;
+ break;
+ }
+ localResult = localResult * 10 + str.GetDigit();
+ tokenLength++;
+ }
+ result = localResult;
+ if (tokenLength < minDigitLen)
+ {
+ str.Index = startingIndex;
+ return false;
+ }
+ return true;
+ }
+
+ /*=================================ParseFractionExact==================================
+ **Action: Parse the number string in __DTString that are formatted using
+ ** the following patterns:
+ ** "0", "00", and "000..0"
+ **Returns: the fraction value
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if error in parsing number.
+ ==============================================================================*/
+
+ private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, ref double result)
+ {
+ if (!str.GetNextDigit())
+ {
+ str.Index--;
+ return false;
+ }
+ result = str.GetDigit();
+
+ int digitLen = 1;
+ for (; digitLen < maxDigitLen; digitLen++)
+ {
+ if (!str.GetNextDigit())
+ {
+ str.Index--;
+ break;
+ }
+ result = result * 10 + str.GetDigit();
+ }
+
+ result /= TimeSpanParse.Pow10(digitLen);
+ return (digitLen == maxDigitLen);
+ }
+
+ /*=================================ParseSign==================================
+ **Action: Parse a positive or a negative sign.
+ **Returns: true if postive sign. flase if negative sign.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if end of string is encountered or a sign
+ ** symbol is not found.
+ ==============================================================================*/
+
+ private static bool ParseSign(ref __DTString str, ref bool result)
+ {
+ if (!str.GetNext())
+ {
+ // A sign symbol ('+' or '-') is expected. However, end of string is encountered.
+ return false;
+ }
+ char ch = str.GetChar();
+ if (ch == '+')
+ {
+ result = true;
+ return (true);
+ }
+ else if (ch == '-')
+ {
+ result = false;
+ return (true);
+ }
+ // A sign symbol ('+' or '-') is expected.
+ return false;
+ }
+
+ /*=================================ParseTimeZoneOffset==================================
+ **Action: Parse the string formatted using "z", "zz", "zzz" in DateTime.Format().
+ **Returns: the TimeSpan for the parsed timezone offset.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ ** len: the repeated number of the "z"
+ **Exceptions: FormatException if errors in parsing.
+ ==============================================================================*/
+
+ private static bool ParseTimeZoneOffset(ref __DTString str, int len, ref TimeSpan result)
+ {
+ bool isPositive = true;
+ int hourOffset;
+ int minuteOffset = 0;
+
+ switch (len)
+ {
+ case 1:
+ case 2:
+ if (!ParseSign(ref str, ref isPositive))
+ {
+ return (false);
+ }
+ if (!ParseDigits(ref str, len, out hourOffset))
+ {
+ return (false);
+ }
+ break;
+ default:
+ if (!ParseSign(ref str, ref isPositive))
+ {
+ return (false);
+ }
+
+ // Parsing 1 digit will actually parse 1 or 2.
+ if (!ParseDigits(ref str, 1, out hourOffset))
+ {
+ return (false);
+ }
+ // ':' is optional.
+ if (str.Match(":"))
+ {
+ // Found ':'
+ if (!ParseDigits(ref str, 2, out minuteOffset))
+ {
+ return (false);
+ }
+ }
+ else
+ {
+ // Since we can not match ':', put the char back.
+ str.Index--;
+ if (!ParseDigits(ref str, 2, out minuteOffset))
+ {
+ return (false);
+ }
+ }
+ break;
+ }
+ if (minuteOffset < 0 || minuteOffset >= 60)
+ {
+ return false;
+ }
+
+ result = (new TimeSpan(hourOffset, minuteOffset, 0));
+ if (!isPositive)
+ {
+ result = result.Negate();
+ }
+ return (true);
+ }
+
+ /*=================================MatchAbbreviatedMonthName==================================
+ **Action: Parse the abbreviated month name from string starting at str.Index.
+ **Returns: A value from 1 to 12 for the first month to the twelveth month.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if an abbreviated month name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchAbbreviatedMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
+ {
+ int maxMatchStrLen = 0;
+ result = -1;
+ if (str.GetNext())
+ {
+ //
+ // Scan the month names (note that some calendars has 13 months) and find
+ // the matching month name which has the max string length.
+ // We need to do this because some cultures (e.g. "cs-CZ") which have
+ // abbreviated month names with the same prefix.
+ //
+ int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12 : 13);
+ for (int i = 1; i <= monthsInYear; i++)
+ {
+ String searchStr = dtfi.GetAbbreviatedMonthName(i);
+ int matchStrLen = searchStr.Length;
+ if (dtfi.HasSpacesInMonthNames
+ ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
+ : str.MatchSpecifiedWord(searchStr))
+ {
+ if (matchStrLen > maxMatchStrLen)
+ {
+ maxMatchStrLen = matchStrLen;
+ result = i;
+ }
+ }
+ }
+
+ // Search leap year form.
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0)
+ {
+ int tempResult = str.MatchLongestWords(dtfi.internalGetLeapYearMonthNames(), ref maxMatchStrLen);
+ // We found a longer match in the leap year month name. Use this as the result.
+ // The result from MatchLongestWords is 0 ~ length of word array.
+ // So we increment the result by one to become the month value.
+ if (tempResult >= 0)
+ {
+ result = tempResult + 1;
+ }
+ }
+ }
+ if (result > 0)
+ {
+ str.Index += (maxMatchStrLen - 1);
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchMonthName==================================
+ **Action: Parse the month name from string starting at str.Index.
+ **Returns: A value from 1 to 12 indicating the first month to the twelveth month.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a month name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
+ {
+ int maxMatchStrLen = 0;
+ result = -1;
+ if (str.GetNext())
+ {
+ //
+ // Scan the month names (note that some calendars has 13 months) and find
+ // the matching month name which has the max string length.
+ // We need to do this because some cultures (e.g. "vi-VN") which have
+ // month names with the same prefix.
+ //
+ int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12 : 13);
+ for (int i = 1; i <= monthsInYear; i++)
+ {
+ String searchStr = dtfi.GetMonthName(i);
+ int matchStrLen = searchStr.Length;
+ if (dtfi.HasSpacesInMonthNames
+ ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
+ : str.MatchSpecifiedWord(searchStr))
+ {
+ if (matchStrLen > maxMatchStrLen)
+ {
+ maxMatchStrLen = matchStrLen;
+ result = i;
+ }
+ }
+ }
+
+ // Search genitive form.
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0)
+ {
+ int tempResult = str.MatchLongestWords(dtfi.MonthGenitiveNames, ref maxMatchStrLen);
+ // We found a longer match in the genitive month name. Use this as the result.
+ // The result from MatchLongestWords is 0 ~ length of word array.
+ // So we increment the result by one to become the month value.
+ if (tempResult >= 0)
+ {
+ result = tempResult + 1;
+ }
+ }
+
+ // Search leap year form.
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0)
+ {
+ int tempResult = str.MatchLongestWords(dtfi.internalGetLeapYearMonthNames(), ref maxMatchStrLen);
+ // We found a longer match in the leap year month name. Use this as the result.
+ // The result from MatchLongestWords is 0 ~ length of word array.
+ // So we increment the result by one to become the month value.
+ if (tempResult >= 0)
+ {
+ result = tempResult + 1;
+ }
+ }
+ }
+
+ if (result > 0)
+ {
+ str.Index += (maxMatchStrLen - 1);
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchAbbreviatedDayName==================================
+ **Action: Parse the abbreviated day of week name from string starting at str.Index.
+ **Returns: A value from 0 to 6 indicating Sunday to Saturday.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a abbreviated day of week name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchAbbreviatedDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
+ {
+ int maxMatchStrLen = 0;
+ result = -1;
+ if (str.GetNext())
+ {
+ for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++)
+ {
+ String searchStr = dtfi.GetAbbreviatedDayName(i);
+ int matchStrLen = searchStr.Length;
+ if (dtfi.HasSpacesInDayNames
+ ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
+ : str.MatchSpecifiedWord(searchStr))
+ {
+ if (matchStrLen > maxMatchStrLen)
+ {
+ maxMatchStrLen = matchStrLen;
+ result = (int)i;
+ }
+ }
+ }
+ }
+ if (result >= 0)
+ {
+ str.Index += maxMatchStrLen - 1;
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchDayName==================================
+ **Action: Parse the day of week name from string starting at str.Index.
+ **Returns: A value from 0 to 6 indicating Sunday to Saturday.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a day of week name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
+ {
+ // Turkish (tr-TR) got day names with the same prefix.
+ int maxMatchStrLen = 0;
+ result = -1;
+ if (str.GetNext())
+ {
+ for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++)
+ {
+ String searchStr = dtfi.GetDayName(i);
+ int matchStrLen = searchStr.Length;
+ if (dtfi.HasSpacesInDayNames
+ ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
+ : str.MatchSpecifiedWord(searchStr))
+ {
+ if (matchStrLen > maxMatchStrLen)
+ {
+ maxMatchStrLen = matchStrLen;
+ result = (int)i;
+ }
+ }
+ }
+ }
+ if (result >= 0)
+ {
+ str.Index += maxMatchStrLen - 1;
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchEraName==================================
+ **Action: Parse era name from string starting at str.Index.
+ **Returns: An era value.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if an era name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchEraName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
+ {
+ if (str.GetNext())
+ {
+ int[] eras = dtfi.Calendar.Eras;
+
+ if (eras != null)
+ {
+ for (int i = 0; i < eras.Length; i++)
+ {
+ String searchStr = dtfi.GetEraName(eras[i]);
+ if (str.MatchSpecifiedWord(searchStr))
+ {
+ str.Index += (searchStr.Length - 1);
+ result = eras[i];
+ return (true);
+ }
+ searchStr = dtfi.GetAbbreviatedEraName(eras[i]);
+ if (str.MatchSpecifiedWord(searchStr))
+ {
+ str.Index += (searchStr.Length - 1);
+ result = eras[i];
+ return (true);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /*=================================MatchTimeMark==================================
+ **Action: Parse the time mark (AM/PM) from string starting at str.Index.
+ **Returns: TM_AM or TM_PM.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a time mark can not be found.
+ ==============================================================================*/
+
+ private static bool MatchTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result)
+ {
+ result = TM.NotSet;
+ // In some cultures have empty strings in AM/PM mark. E.g. af-ZA (0x0436), the AM mark is "", and PM mark is "nm".
+ if (dtfi.AMDesignator.Length == 0)
+ {
+ result = TM.AM;
+ }
+ if (dtfi.PMDesignator.Length == 0)
+ {
+ result = TM.PM;
+ }
+
+ if (str.GetNext())
+ {
+ String searchStr = dtfi.AMDesignator;
+ if (searchStr.Length > 0)
+ {
+ if (str.MatchSpecifiedWord(searchStr))
+ {
+ // Found an AM timemark with length > 0.
+ str.Index += (searchStr.Length - 1);
+ result = TM.AM;
+ return (true);
+ }
+ }
+ searchStr = dtfi.PMDesignator;
+ if (searchStr.Length > 0)
+ {
+ if (str.MatchSpecifiedWord(searchStr))
+ {
+ // Found a PM timemark with length > 0.
+ str.Index += (searchStr.Length - 1);
+ result = TM.PM;
+ return (true);
+ }
+ }
+ str.Index--; // Undo the GetNext call.
+ }
+ if (result != TM.NotSet)
+ {
+ // If one of the AM/PM marks is empty string, return the result.
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchAbbreviatedTimeMark==================================
+ **Action: Parse the abbreviated time mark (AM/PM) from string starting at str.Index.
+ **Returns: TM_AM or TM_PM.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a abbreviated time mark can not be found.
+ ==============================================================================*/
+
+ private static bool MatchAbbreviatedTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result)
+ {
+ // NOTENOTE : the assumption here is that abbreviated time mark is the first
+ // character of the AM/PM designator. If this invariant changes, we have to
+ // change the code below.
+ if (str.GetNext())
+ {
+ string amDesignator = dtfi.AMDesignator;
+ if (amDesignator.Length > 0 && str.GetChar() == amDesignator[0])
+ {
+ result = TM.AM;
+ return true;
+ }
+
+ string pmDesignator = dtfi.PMDesignator;
+ if (pmDesignator.Length > 0 && str.GetChar() == pmDesignator[0])
+ {
+ result = TM.PM;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*=================================CheckNewValue==================================
+ **Action: Check if currentValue is initialized. If not, return the newValue.
+ ** If yes, check if the current value is equal to newValue. Return false
+ ** if they are not equal. This is used to check the case like "d" and "dd" are both
+ ** used to format a string.
+ **Returns: the correct value for currentValue.
+ **Arguments:
+ **Exceptions:
+ ==============================================================================*/
+
+ private static bool CheckNewValue(ref int currentValue, int newValue, char patternChar, ref DateTimeResult result)
+ {
+ if (currentValue == -1)
+ {
+ currentValue = newValue;
+ return (true);
+ }
+ else
+ {
+ if (newValue != currentValue)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), patternChar);
+ return (false);
+ }
+ }
+ return (true);
+ }
+
+ private static DateTime GetDateTimeNow(ref DateTimeResult result, ref DateTimeStyles styles)
+ {
+ if ((result.flags & ParseFlags.CaptureOffset) != 0)
+ {
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0)
+ {
+ // use the supplied offset to calculate 'Now'
+ return new DateTime(DateTime.UtcNow.Ticks + result.timeZoneOffset.Ticks, DateTimeKind.Unspecified);
+ }
+ else if ((styles & DateTimeStyles.AssumeUniversal) != 0)
+ {
+ // assume the offset is Utc
+ return DateTime.UtcNow;
+ }
+ }
+
+ // assume the offset is Local
+ return DateTime.Now;
+ }
+
+ private static bool CheckDefaultDateTime(ref DateTimeResult result, ref Calendar cal, DateTimeStyles styles)
+ {
+ if ((result.flags & ParseFlags.CaptureOffset) != 0)
+ {
+ // DateTimeOffset.Parse should allow dates without a year, but only if there is also no time zone marker;
+ // e.g. "May 1 5pm" is OK, but "May 1 5pm -08:30" is not. This is somewhat pragmatic, since we would
+ // have to rearchitect parsing completely to allow this one case to correctly handle things like leap
+ // years and leap months. Is is an extremely corner case, and DateTime is basically incorrect in that
+ // case today.
+ //
+ // values like "11:00Z" or "11:00 -3:00" are also acceptable
+ //
+ // if ((month or day is set) and (year is not set and time zone is set))
+ //
+ if (((result.Month != -1) || (result.Day != -1))
+ && ((result.Year == -1 || ((result.flags & ParseFlags.YearDefault) != 0)) && (result.flags & ParseFlags.TimeZoneUsed) != 0))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTime, nameof(SR.Format_MissingIncompleteDate));
+ return false;
+ }
+ }
+
+
+ if ((result.Year == -1) || (result.Month == -1) || (result.Day == -1))
+ {
+ /*
+ The following table describes the behaviors of getting the default value
+ when a certain year/month/day values are missing.
+
+ An "X" means that the value exists. And "--" means that value is missing.
+
+ Year Month Day => ResultYear ResultMonth ResultDay Note
+
+ X X X Parsed year Parsed month Parsed day
+ X X -- Parsed Year Parsed month First day If we have year and month, assume the first day of that month.
+ X -- X Parsed year First month Parsed day If the month is missing, assume first month of that year.
+ X -- -- Parsed year First month First day If we have only the year, assume the first day of that year.
+
+ -- X X CurrentYear Parsed month Parsed day If the year is missing, assume the current year.
+ -- X -- CurrentYear Parsed month First day If we have only a month value, assume the current year and current day.
+ -- -- X CurrentYear First month Parsed day If we have only a day value, assume current year and first month.
+ -- -- -- CurrentYear Current month Current day So this means that if the date string only contains time, you will get current date.
+
+ */
+
+ DateTime now = GetDateTimeNow(ref result, ref styles);
+ if (result.Month == -1 && result.Day == -1)
+ {
+ if (result.Year == -1)
+ {
+ if ((styles & DateTimeStyles.NoCurrentDateDefault) != 0)
+ {
+ // If there is no year/month/day values, and NoCurrentDateDefault flag is used,
+ // set the year/month/day value to the beginning year/month/day of DateTime().
+ // Note we should be using Gregorian for the year/month/day.
+ cal = GregorianCalendar.GetDefaultInstance();
+ result.Year = result.Month = result.Day = 1;
+ }
+ else
+ {
+ // Year/Month/Day are all missing.
+ result.Year = cal.GetYear(now);
+ result.Month = cal.GetMonth(now);
+ result.Day = cal.GetDayOfMonth(now);
+ }
+ }
+ else
+ {
+ // Month/Day are both missing.
+ result.Month = 1;
+ result.Day = 1;
+ }
+ }
+ else
+ {
+ if (result.Year == -1)
+ {
+ result.Year = cal.GetYear(now);
+ }
+ if (result.Month == -1)
+ {
+ result.Month = 1;
+ }
+ if (result.Day == -1)
+ {
+ result.Day = 1;
+ }
+ }
+ }
+ // Set Hour/Minute/Second to zero if these value are not in str.
+ if (result.Hour == -1) result.Hour = 0;
+ if (result.Minute == -1) result.Minute = 0;
+ if (result.Second == -1) result.Second = 0;
+ if (result.era == -1) result.era = Calendar.CurrentEra;
+ return true;
+ }
+
+ // Expand a pre-defined format string (like "D" for long date) to the real format that
+ // we are going to use in the date time parsing.
+ // This method also set the dtfi according/parseInfo to some special pre-defined
+ // formats.
+ //
+ private static String ExpandPredefinedFormat(ReadOnlySpan<char> format, ref DateTimeFormatInfo dtfi, ref ParsingInfo parseInfo, ref DateTimeResult result)
+ {
+ //
+ // Check the format to see if we need to override the dtfi to be InvariantInfo,
+ // and see if we need to set up the userUniversalTime flag.
+ //
+ switch (format[0])
+ {
+ case 'o':
+ case 'O': // Round Trip Format
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 'r':
+ case 'R': // RFC 1123 Standard. (in Universal time)
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+
+ if ((result.flags & ParseFlags.CaptureOffset) != 0)
+ {
+ result.flags |= ParseFlags.Rfc1123Pattern;
+ }
+ break;
+ case 's': // Sortable format (in local time)
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ break;
+ case 'u': // Universal time format in sortable format.
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+
+ if ((result.flags & ParseFlags.CaptureOffset) != 0)
+ {
+ result.flags |= ParseFlags.UtcSortPattern;
+ }
+ break;
+ case 'U': // Universal time format with culture-dependent format.
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = new TimeSpan(0);
+ result.flags |= ParseFlags.TimeZoneUtc;
+ if (dtfi.Calendar.GetType() != typeof(GregorianCalendar))
+ {
+ dtfi = (DateTimeFormatInfo)dtfi.Clone();
+ dtfi.Calendar = GregorianCalendar.GetDefaultInstance();
+ }
+ break;
+ }
+
+ //
+ // Expand the pre-defined format character to the real format from DateTimeFormatInfo.
+ //
+ return (DateTimeFormat.GetRealFormat(format, dtfi));
+ }
+
+
+
+
+
+ // Given a specified format character, parse and update the parsing result.
+ //
+ private static bool ParseByFormat(
+ ref __DTString str,
+ ref __DTString format,
+ ref ParsingInfo parseInfo,
+ DateTimeFormatInfo dtfi,
+ ref DateTimeResult result)
+ {
+ int tokenLen = 0;
+ int tempYear = 0, tempMonth = 0, tempDay = 0, tempDayOfWeek = 0, tempHour = 0, tempMinute = 0, tempSecond = 0;
+ double tempFraction = 0;
+ TM tempTimeMark = 0;
+
+ char ch = format.GetChar();
+
+ switch (ch)
+ {
+ case 'y':
+ tokenLen = format.GetRepeatCount();
+ bool parseResult;
+ if (dtfi.HasForceTwoDigitYears)
+ {
+ parseResult = ParseDigits(ref str, 1, 4, out tempYear);
+ }
+ else
+ {
+ if (tokenLen <= 2)
+ {
+ parseInfo.fUseTwoDigitYear = true;
+ }
+ parseResult = ParseDigits(ref str, tokenLen, out tempYear);
+ }
+ if (!parseResult && parseInfo.fCustomNumberParser)
+ {
+ parseResult = parseInfo.parseNumberDelegate(ref str, tokenLen, out tempYear);
+ }
+ if (!parseResult)
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Year, tempYear, ch, ref result))
+ {
+ return (false);
+ }
+ break;
+ case 'M':
+ tokenLen = format.GetRepeatCount();
+ if (tokenLen <= 2)
+ {
+ if (!ParseDigits(ref str, tokenLen, out tempMonth))
+ {
+ if (!parseInfo.fCustomNumberParser ||
+ !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempMonth))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+ }
+ else
+ {
+ if (tokenLen == 3)
+ {
+ if (!MatchAbbreviatedMonthName(ref str, dtfi, ref tempMonth))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+ else
+ {
+ if (!MatchMonthName(ref str, dtfi, ref tempMonth))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+ result.flags |= ParseFlags.ParsedMonthName;
+ }
+ if (!CheckNewValue(ref result.Month, tempMonth, ch, ref result))
+ {
+ return (false);
+ }
+ break;
+ case 'd':
+ // Day & Day of week
+ tokenLen = format.GetRepeatCount();
+ if (tokenLen <= 2)
+ {
+ // "d" & "dd"
+
+ if (!ParseDigits(ref str, tokenLen, out tempDay))
+ {
+ if (!parseInfo.fCustomNumberParser ||
+ !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempDay))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+ if (!CheckNewValue(ref result.Day, tempDay, ch, ref result))
+ {
+ return (false);
+ }
+ }
+ else
+ {
+ if (tokenLen == 3)
+ {
+ // "ddd"
+ if (!MatchAbbreviatedDayName(ref str, dtfi, ref tempDayOfWeek))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+ else
+ {
+ // "dddd*"
+ if (!MatchDayName(ref str, dtfi, ref tempDayOfWeek))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+ if (!CheckNewValue(ref parseInfo.dayOfWeek, tempDayOfWeek, ch, ref result))
+ {
+ return (false);
+ }
+ }
+ break;
+ case 'g':
+ tokenLen = format.GetRepeatCount();
+ // Put the era value in result.era.
+ if (!MatchEraName(ref str, dtfi, ref result.era))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ break;
+ case 'h':
+ parseInfo.fUseHour12 = true;
+ tokenLen = format.GetRepeatCount();
+ if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempHour))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result))
+ {
+ return (false);
+ }
+ break;
+ case 'H':
+ tokenLen = format.GetRepeatCount();
+ if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempHour))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result))
+ {
+ return (false);
+ }
+ break;
+ case 'm':
+ tokenLen = format.GetRepeatCount();
+ if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempMinute))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Minute, tempMinute, ch, ref result))
+ {
+ return (false);
+ }
+ break;
+ case 's':
+ tokenLen = format.GetRepeatCount();
+ if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempSecond))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Second, tempSecond, ch, ref result))
+ {
+ return (false);
+ }
+ break;
+ case 'f':
+ case 'F':
+ tokenLen = format.GetRepeatCount();
+ if (tokenLen <= DateTimeFormat.MaxSecondsFractionDigits)
+ {
+ if (!ParseFractionExact(ref str, tokenLen, ref tempFraction))
+ {
+ if (ch == 'f')
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+ if (result.fraction < 0)
+ {
+ result.fraction = tempFraction;
+ }
+ else
+ {
+ if (tempFraction != result.fraction)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), ch);
+ return (false);
+ }
+ }
+ }
+ else
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ break;
+ case 't':
+ // AM/PM designator
+ tokenLen = format.GetRepeatCount();
+ if (tokenLen == 1)
+ {
+ if (!MatchAbbreviatedTimeMark(ref str, dtfi, ref tempTimeMark))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+ else
+ {
+ if (!MatchTimeMark(ref str, dtfi, ref tempTimeMark))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ }
+
+ if (parseInfo.timeMark == TM.NotSet)
+ {
+ parseInfo.timeMark = tempTimeMark;
+ }
+ else
+ {
+ if (parseInfo.timeMark != tempTimeMark)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), ch);
+ return (false);
+ }
+ }
+ break;
+ case 'z':
+ // timezone offset
+ tokenLen = format.GetRepeatCount();
+ {
+ TimeSpan tempTimeZoneOffset = new TimeSpan(0);
+ if (!ParseTimeZoneOffset(ref str, tokenLen, ref tempTimeZoneOffset))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), 'z');
+ return (false);
+ }
+ result.timeZoneOffset = tempTimeZoneOffset;
+ result.flags |= ParseFlags.TimeZoneUsed;
+ }
+ break;
+ case 'Z':
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), 'Z');
+ return (false);
+ }
+
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = new TimeSpan(0);
+ result.flags |= ParseFlags.TimeZoneUtc;
+
+ // The updating of the indexes is to reflect that ParseExact MatchXXX methods assume that
+ // they need to increment the index and Parse GetXXX do not. Since we are calling a Parse
+ // method from inside ParseExact we need to adjust this. Long term, we should try to
+ // eliminate this discrepancy.
+ str.Index++;
+ if (!GetTimeZoneName(ref str))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ str.Index--;
+ break;
+ case 'K':
+ // This should parse either as a blank, the 'Z' character or a local offset like "-07:00"
+ if (str.Match('Z'))
+ {
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), 'K');
+ return (false);
+ }
+
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = new TimeSpan(0);
+ result.flags |= ParseFlags.TimeZoneUtc;
+ }
+ else if (str.Match('+') || str.Match('-'))
+ {
+ str.Index--; // Put the character back for the parser
+ TimeSpan tempTimeZoneOffset = new TimeSpan(0);
+ if (!ParseTimeZoneOffset(ref str, 3, ref tempTimeZoneOffset))
+ {
+ result.SetBadDateTimeFailure();
+ return (false);
+ }
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), 'K');
+ return (false);
+ }
+ result.timeZoneOffset = tempTimeZoneOffset;
+ result.flags |= ParseFlags.TimeZoneUsed;
+ }
+ // Otherwise it is unspecified and we consume no characters
+ break;
+ case ':':
+ // We match the separator in time pattern with the character in the time string if both equal to ':' or the date separator is matching the characters in the date string
+ // We have to exclude the case when the time separator is more than one character and starts with ':' something like "::" for instance.
+ if (((dtfi.TimeSeparator.Length > 1 && dtfi.TimeSeparator[0] == ':') || !str.Match(':')) &&
+ !str.Match(dtfi.TimeSeparator))
+ {
+ // A time separator is expected.
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ break;
+ case '/':
+ // We match the separator in date pattern with the character in the date string if both equal to '/' or the date separator is matching the characters in the date string
+ // We have to exclude the case when the date separator is more than one character and starts with '/' something like "//" for instance.
+ if (((dtfi.DateSeparator.Length > 1 && dtfi.DateSeparator[0] == '/') || !str.Match('/')) &&
+ !str.Match(dtfi.DateSeparator))
+ {
+ // A date separator is expected.
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ break;
+ case '\"':
+ case '\'':
+ StringBuilder enquotedString = StringBuilderCache.Acquire();
+ // Use ParseQuoteString so that we can handle escape characters within the quoted string.
+ if (!TryParseQuoteString(format.Value, format.Index, enquotedString, out tokenLen))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadQuote), ch);
+ StringBuilderCache.Release(enquotedString);
+ return (false);
+ }
+ format.Index += tokenLen - 1;
+
+ // Some cultures uses space in the quoted string. E.g. Spanish has long date format as:
+ // "dddd, dd' de 'MMMM' de 'yyyy". When inner spaces flag is set, we should skip whitespaces if there is space
+ // in the quoted string.
+ String quotedStr = StringBuilderCache.GetStringAndRelease(enquotedString);
+
+ for (int i = 0; i < quotedStr.Length; i++)
+ {
+ if (quotedStr[i] == ' ' && parseInfo.fAllowInnerWhite)
+ {
+ str.SkipWhiteSpaces();
+ }
+ else if (!str.Match(quotedStr[i]))
+ {
+ // Can not find the matching quoted string.
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+
+ // The "r" and "u" formats incorrectly quoted 'GMT' and 'Z', respectively. We cannot
+ // correct this mistake for DateTime.ParseExact for compatibility reasons, but we can
+ // fix it for DateTimeOffset.ParseExact as DateTimeOffset has not been publically released
+ // with this issue.
+ if ((result.flags & ParseFlags.CaptureOffset) != 0)
+ {
+ if ((result.flags & ParseFlags.Rfc1123Pattern) != 0 && quotedStr == GMTName)
+ {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+ else if ((result.flags & ParseFlags.UtcSortPattern) != 0 && quotedStr == ZuluName)
+ {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+ }
+
+ break;
+ case '%':
+ // Skip this so we can get to the next pattern character.
+ // Used in case like "%d", "%y"
+
+ // Make sure the next character is not a '%' again.
+ if (format.Index >= format.Value.Length - 1 || format.Value[format.Index + 1] == '%')
+ {
+ result.SetBadFormatSpecifierFailure(format.Value);
+ return false;
+ }
+ break;
+ case '\\':
+ // Escape character. For example, "\d".
+ // Get the next character in format, and see if we can
+ // find a match in str.
+ if (format.GetNext())
+ {
+ if (!str.Match(format.GetChar()))
+ {
+ // Can not find a match for the escaped character.
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+ else
+ {
+ result.SetBadFormatSpecifierFailure(format.Value);
+ return false;
+ }
+ break;
+ case '.':
+ if (!str.Match(ch))
+ {
+ if (format.GetNext())
+ {
+ // If we encounter the pattern ".F", and the dot is not present, it is an optional
+ // second fraction and we can skip this format.
+ if (format.Match('F'))
+ {
+ format.GetRepeatCount();
+ break;
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ break;
+ default:
+ if (ch == ' ')
+ {
+ if (parseInfo.fAllowInnerWhite)
+ {
+ // Skip whitespaces if AllowInnerWhite.
+ // Do nothing here.
+ }
+ else
+ {
+ if (!str.Match(ch))
+ {
+ // If the space does not match, and trailing space is allowed, we do
+ // one more step to see if the next format character can lead to
+ // successful parsing.
+ // This is used to deal with special case that a empty string can match
+ // a specific pattern.
+ // The example here is af-ZA, which has a time format like "hh:mm:ss tt". However,
+ // its AM symbol is "" (empty string). If fAllowTrailingWhite is used, and time is in
+ // the AM, we will trim the whitespaces at the end, which will lead to a failure
+ // when we are trying to match the space before "tt".
+ if (parseInfo.fAllowTrailingWhite)
+ {
+ if (format.GetNext())
+ {
+ if (ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result))
+ {
+ return (true);
+ }
+ }
+ }
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ // Found a macth.
+ }
+ }
+ else
+ {
+ if (format.MatchSpecifiedWord(GMTName))
+ {
+ format.Index += (GMTName.Length - 1);
+ // Found GMT string in format. This means the DateTime string
+ // is in GMT timezone.
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ if (!str.Match(GMTName))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+ else if (!str.Match(ch))
+ {
+ // ch is expected.
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+ break;
+ } // switch
+ return (true);
+ }
+
+ //
+ // The pos should point to a quote character. This method will
+ // get the string enclosed by the quote character.
+ //
+ internal static bool TryParseQuoteString(ReadOnlySpan<char> format, int pos, StringBuilder result, out int returnValue)
+ {
+ //
+ // NOTE : pos will be the index of the quote character in the 'format' string.
+ //
+ returnValue = 0;
+ int formatLen = format.Length;
+ int beginPos = pos;
+ char quoteChar = format[pos++]; // Get the character used to quote the following string.
+
+ bool foundQuote = false;
+ while (pos < formatLen)
+ {
+ char ch = format[pos++];
+ if (ch == quoteChar)
+ {
+ foundQuote = true;
+ break;
+ }
+ else if (ch == '\\')
+ {
+ // The following are used to support escaped character.
+ // Escaped character is also supported in the quoted string.
+ // Therefore, someone can use a format like "'minute:' mm\"" to display:
+ // minute: 45"
+ // because the second double quote is escaped.
+ if (pos < formatLen)
+ {
+ result.Append(format[pos++]);
+ }
+ else
+ {
+ //
+ // This means that '\' is at the end of the formatting string.
+ //
+ return false;
+ }
+ }
+ else
+ {
+ result.Append(ch);
+ }
+ }
+
+ if (!foundQuote)
+ {
+ // Here we can't find the matching quote.
+ return false;
+ }
+
+ //
+ // Return the character count including the begin/end quote characters and enclosed string.
+ //
+ returnValue = (pos - beginPos);
+ return true;
+ }
+
+
+
+
+ /*=================================DoStrictParse==================================
+ **Action: Do DateTime parsing using the format in formatParam.
+ **Returns: The parsed DateTime.
+ **Arguments:
+ **Exceptions:
+ **
+ **Notes:
+ ** When the following general formats are used, InvariantInfo is used in dtfi:
+ ** 'r', 'R', 's'.
+ ** When the following general formats are used, the time is assumed to be in Universal time.
+ **
+ **Limitations:
+ ** Only GregarianCalendar is supported for now.
+ ** Only support GMT timezone.
+ ==============================================================================*/
+
+ private static bool DoStrictParse(
+ ReadOnlySpan<char> s,
+ ReadOnlySpan<char> formatParam,
+ DateTimeStyles styles,
+ DateTimeFormatInfo dtfi,
+ ref DateTimeResult result)
+ {
+ ParsingInfo parseInfo = new ParsingInfo();
+ parseInfo.Init();
+
+ parseInfo.calendar = dtfi.Calendar;
+ parseInfo.fAllowInnerWhite = ((styles & DateTimeStyles.AllowInnerWhite) != 0);
+ parseInfo.fAllowTrailingWhite = ((styles & DateTimeStyles.AllowTrailingWhite) != 0);
+
+ if (formatParam.Length == 1)
+ {
+ if (((result.flags & ParseFlags.CaptureOffset) != 0) && formatParam[0] == 'U')
+ {
+ // The 'U' format is not allowed for DateTimeOffset
+ result.SetBadFormatSpecifierFailure(formatParam);
+ return false;
+ }
+ formatParam = ExpandPredefinedFormat(formatParam, ref dtfi, ref parseInfo, ref result);
+ }
+
+ bool bTimeOnly = false;
+ result.calendar = parseInfo.calendar;
+
+ if (parseInfo.calendar.ID == CalendarId.HEBREW)
+ {
+ parseInfo.parseNumberDelegate = m_hebrewNumberParser;
+ parseInfo.fCustomNumberParser = true;
+ }
+
+ // Reset these values to negative one so that we could throw exception
+ // if we have parsed every item twice.
+ result.Hour = result.Minute = result.Second = -1;
+
+ __DTString format = new __DTString(formatParam, dtfi, false);
+ __DTString str = new __DTString(s, dtfi, false);
+
+ if (parseInfo.fAllowTrailingWhite)
+ {
+ // Trim trailing spaces if AllowTrailingWhite.
+ format.TrimTail();
+ format.RemoveTrailingInQuoteSpaces();
+ str.TrimTail();
+ }
+
+ if ((styles & DateTimeStyles.AllowLeadingWhite) != 0)
+ {
+ format.SkipWhiteSpaces();
+ format.RemoveLeadingInQuoteSpaces();
+ str.SkipWhiteSpaces();
+ }
+
+ //
+ // Scan every character in format and match the pattern in str.
+ //
+ while (format.GetNext())
+ {
+ // We trim inner spaces here, so that we will not eat trailing spaces when
+ // AllowTrailingWhite is not used.
+ if (parseInfo.fAllowInnerWhite)
+ {
+ str.SkipWhiteSpaces();
+ }
+ if (!ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result))
+ {
+ return (false);
+ }
+ }
+
+ if (str.Index < str.Value.Length - 1)
+ {
+ // There are still remaining character in str.
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+
+ if (parseInfo.fUseTwoDigitYear && ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) == 0))
+ {
+ // A two digit year value is expected. Check if the parsed year value is valid.
+ if (result.Year >= 100)
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ try
+ {
+ result.Year = parseInfo.calendar.ToFourDigitYear(result.Year);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+
+ if (parseInfo.fUseHour12)
+ {
+ if (parseInfo.timeMark == TM.NotSet)
+ {
+ // hh is used, but no AM/PM designator is specified.
+ // Assume the time is AM.
+ // Don't throw exceptions in here becasue it is very confusing for the caller.
+ // I always got confused myself when I use "hh:mm:ss" to parse a time string,
+ // and ParseExact() throws on me (because I didn't use the 24-hour clock 'HH').
+ parseInfo.timeMark = TM.AM;
+ }
+ if (result.Hour > 12)
+ {
+ // AM/PM is used, but the value for HH is too big.
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ if (parseInfo.timeMark == TM.AM)
+ {
+ if (result.Hour == 12)
+ {
+ result.Hour = 0;
+ }
+ }
+ else
+ {
+ result.Hour = (result.Hour == 12) ? 12 : result.Hour + 12;
+ }
+ }
+ else
+ {
+ // Military (24-hour time) mode
+ //
+ // AM cannot be set with a 24-hour time like 17:15.
+ // PM cannot be set with a 24-hour time like 03:15.
+ if ((parseInfo.timeMark == TM.AM && result.Hour >= 12)
+ || (parseInfo.timeMark == TM.PM && result.Hour < 12))
+ {
+ result.SetBadDateTimeFailure();
+ return false;
+ }
+ }
+
+
+ // Check if the parased string only contains hour/minute/second values.
+ bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
+ if (!CheckDefaultDateTime(ref result, ref parseInfo.calendar, styles))
+ {
+ return false;
+ }
+
+ if (!bTimeOnly && dtfi.HasYearMonthAdjustment)
+ {
+ if (!dtfi.YearMonthAdjustment(ref result.Year, ref result.Month, ((result.flags & ParseFlags.ParsedMonthName) != 0)))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ return false;
+ }
+ }
+ if (!parseInfo.calendar.TryToDateTime(result.Year, result.Month, result.Day,
+ result.Hour, result.Minute, result.Second, 0, result.era, out result.parsedDate))
+ {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar));
+ return false;
+ }
+ if (result.fraction > 0)
+ {
+ result.parsedDate = result.parsedDate.AddTicks((long)Math.Round(result.fraction * Calendar.TicksPerSecond));
+ }
+
+ //
+ // We have to check day of week before we adjust to the time zone.
+ // It is because the value of day of week may change after adjusting
+ // to the time zone.
+ //
+ if (parseInfo.dayOfWeek != -1)
+ {
+ //
+ // Check if day of week is correct.
+ //
+ if (parseInfo.dayOfWeek != (int)parseInfo.calendar.GetDayOfWeek(result.parsedDate))
+ {
+ result.SetFailure(ParseFailureKind.FormatWithOriginalDateTime, nameof(SR.Format_BadDayOfWeek));
+ return false;
+ }
+ }
+
+
+ if (!DetermineTimeZoneAdjustments(ref str, ref result, styles, bTimeOnly))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ private static Exception GetDateTimeParseException(ref DateTimeResult result)
+ {
+ switch (result.failure)
+ {
+ case ParseFailureKind.ArgumentNull:
+ return new ArgumentNullException(result.failureArgumentName, SR.GetResourceString(result.failureMessageID));
+ case ParseFailureKind.Format:
+ return new FormatException(SR.GetResourceString(result.failureMessageID));
+ case ParseFailureKind.FormatWithParameter:
+ return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), result.failureMessageFormatArgument));
+ case ParseFailureKind.FormatBadDateTimeCalendar:
+ return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), new string(result.originalDateTimeString), result.calendar));
+ case ParseFailureKind.FormatWithOriginalDateTime:
+ return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), new string(result.originalDateTimeString)));
+ case ParseFailureKind.FormatWithFormatSpecifier:
+ return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), new string(result.failedFormatSpecifier)));
+ case ParseFailureKind.FormatWithOriginalDateTimeAndParameter:
+ return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), new string(result.originalDateTimeString), result.failureMessageFormatArgument));
+ default:
+ Debug.Fail("Unknown DateTimeParseFailure: " + result.failure.ToString());
+ return null;
+ }
+ }
+
+ [Conditional("_LOGGING")]
+ private static void LexTraceExit(string message, DS dps)
+ {
+#if _LOGGING
+ if (!_tracingEnabled)
+ return;
+ Trace($"Lex return {message}, DS.{dps}");
+#endif // _LOGGING
+ }
+ [Conditional("_LOGGING")]
+ private static void PTSTraceExit(DS dps, bool passed)
+ {
+#if _LOGGING
+ if (!_tracingEnabled)
+ return;
+ Trace($"ProcessTerminalState {(passed ? "passed" : "failed")} @ DS.{dps}");
+#endif // _LOGGING
+ }
+ [Conditional("_LOGGING")]
+ private static void TPTraceExit(string message, DS dps)
+ {
+#if _LOGGING
+ if (!_tracingEnabled)
+ return;
+ Trace($"TryParse return {message}, DS.{dps}");
+#endif // _LOGGING
+ }
+ [Conditional("_LOGGING")]
+ private static void DTFITrace(DateTimeFormatInfo dtfi)
+ {
+#if _LOGGING
+ if (!_tracingEnabled)
+ return;
+
+ Trace("DateTimeFormatInfo Properties");
+#if !FEATURE_COREFX_GLOBALIZATION
+ Trace($" NativeCalendarName {Hex(dtfi.NativeCalendarName)}");
+#endif
+ Trace($" AMDesignator {Hex(dtfi.AMDesignator)}");
+ Trace($" PMDesignator {Hex(dtfi.PMDesignator)}");
+ Trace($" TimeSeparator {Hex(dtfi.TimeSeparator)}");
+ Trace($" AbbrvDayNames {Hex(dtfi.AbbreviatedDayNames)}");
+ Trace($" ShortestDayNames {Hex(dtfi.ShortestDayNames)}");
+ Trace($" DayNames {Hex(dtfi.DayNames)}");
+ Trace($" AbbrvMonthNames {Hex(dtfi.AbbreviatedMonthNames)}");
+ Trace($" MonthNames {Hex(dtfi.MonthNames)}");
+ Trace($" AbbrvMonthGenNames {Hex(dtfi.AbbreviatedMonthGenitiveNames)}");
+ Trace($" MonthGenNames {Hex(dtfi.MonthGenitiveNames)}");
+#endif // _LOGGING
+ }
+#if _LOGGING
+ // return a string in the form: "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ private static string Hex(string[] strs)
+ {
+ if (strs == null || strs.Length == 0)
+ return String.Empty;
+ if (strs.Length == 1)
+ return Hex(strs[0]);
+
+ int curLineLength = 0;
+ int maxLineLength = 55;
+ int newLinePadding = 20;
+
+
+ //invariant: strs.Length >= 2
+ StringBuilder buffer = new StringBuilder();
+ buffer.Append(Hex(strs[0]));
+ curLineLength = buffer.Length;
+ String s;
+
+ for (int i = 1; i < strs.Length - 1; i++)
+ {
+ s = Hex(strs[i]);
+
+ if (s.Length > maxLineLength || (curLineLength + s.Length + 2) > maxLineLength)
+ {
+ buffer.Append(',');
+ buffer.Append(Environment.NewLine);
+ buffer.Append(' ', newLinePadding);
+ curLineLength = 0;
+ }
+ else
+ {
+ buffer.Append(", ");
+ curLineLength += 2;
+ }
+ buffer.Append(s);
+ curLineLength += s.Length;
+ }
+
+ buffer.Append(',');
+ s = Hex(strs[strs.Length - 1]);
+ if (s.Length > maxLineLength || (curLineLength + s.Length + 6) > maxLineLength)
+ {
+ buffer.Append(Environment.NewLine);
+ buffer.Append(' ', newLinePadding);
+ }
+ else
+ {
+ buffer.Append(' ');
+ }
+ buffer.Append(s);
+ return buffer.ToString();
+ }
+ // return a string in the form: "Sun"
+ private static string Hex(string str) => Hex((ReadOnlySpan<char>)str);
+ private static string Hex(ReadOnlySpan<char> str)
+ {
+ StringBuilder buffer = new StringBuilder();
+ buffer.Append("\"");
+ for (int i = 0; i < str.Length; i++)
+ {
+ if (str[i] <= '\x007f')
+ buffer.Append(str[i]);
+ else
+ buffer.Append("\\u" + ((int)str[i]).ToString("x4", CultureInfo.InvariantCulture));
+ }
+ buffer.Append("\"");
+ return buffer.ToString();
+ }
+ // return an unicode escaped string form of char c
+ private static String Hex(char c)
+ {
+ if (c <= '\x007f')
+ return c.ToString(CultureInfo.InvariantCulture);
+ else
+ return "\\u" + ((int)c).ToString("x4", CultureInfo.InvariantCulture);
+ }
+
+ private static void Trace(string s)
+ {
+ // Internal.Console.WriteLine(s);
+ }
+
+ private static bool _tracingEnabled = false;
+#endif // _LOGGING
+ }
+
+
+ //
+ // This is a string parsing helper which wraps a String object.
+ // It has a Index property which tracks
+ // the current parsing pointer of the string.
+ //
+ internal ref struct __DTString
+ {
+ //
+ // Value propery: stores the real string to be parsed.
+ //
+ internal ReadOnlySpan<char> Value;
+
+ //
+ // Index property: points to the character that we are currently parsing.
+ //
+ internal int Index;
+
+ // The length of Value string.
+ internal int Length => Value.Length;
+
+ // The current chracter to be looked at.
+ internal char m_current;
+
+ private CompareInfo m_info;
+ // Flag to indicate if we encouter an digit, we should check for token or not.
+ // In some cultures, such as mn-MN, it uses "\x0031\x00a0\x0434\x04af\x0433\x044d\x044d\x0440\x00a0\x0441\x0430\x0440" in month names.
+ private bool m_checkDigitToken;
+
+ internal __DTString(ReadOnlySpan<char> str, DateTimeFormatInfo dtfi, bool checkDigitToken) : this(str, dtfi)
+ {
+ m_checkDigitToken = checkDigitToken;
+ }
+
+ internal __DTString(ReadOnlySpan<char> str, DateTimeFormatInfo dtfi)
+ {
+ Index = -1;
+ Value = str;
+
+ m_current = '\0';
+ if (dtfi != null)
+ {
+ m_info = dtfi.CompareInfo;
+ m_checkDigitToken = ((dtfi.FormatFlags & DateTimeFormatFlags.UseDigitPrefixInTokens) != 0);
+ }
+ else
+ {
+ m_info = CultureInfo.CurrentCulture.CompareInfo;
+ m_checkDigitToken = false;
+ }
+ }
+
+ internal CompareInfo CompareInfo
+ {
+ get { return m_info; }
+ }
+
+ //
+ // Advance the Index.
+ // Return true if Index is NOT at the end of the string.
+ //
+ // Typical usage:
+ // while (str.GetNext())
+ // {
+ // char ch = str.GetChar()
+ // }
+ internal bool GetNext()
+ {
+ Index++;
+ if (Index < Length)
+ {
+ m_current = Value[Index];
+ return (true);
+ }
+ return (false);
+ }
+
+ internal bool AtEnd()
+ {
+ return Index < Length ? false : true;
+ }
+
+ internal bool Advance(int count)
+ {
+ Debug.Assert(Index + count <= Length, "__DTString::Advance: Index + count <= len");
+ Index += count;
+ if (Index < Length)
+ {
+ m_current = Value[Index];
+ return (true);
+ }
+ return (false);
+ }
+
+
+ // Used by DateTime.Parse() to get the next token.
+ internal void GetRegularToken(out TokenType tokenType, out int tokenValue, DateTimeFormatInfo dtfi)
+ {
+ tokenValue = 0;
+ if (Index >= Length)
+ {
+ tokenType = TokenType.EndOfString;
+ return;
+ }
+
+ tokenType = TokenType.UnknownToken;
+
+ Start:
+ if (DateTimeParse.IsDigit(m_current))
+ {
+ // This is a digit.
+ tokenValue = m_current - '0';
+ int value;
+ int start = Index;
+
+ //
+ // Collect other digits.
+ //
+ while (++Index < Length)
+ {
+ m_current = Value[Index];
+ value = m_current - '0';
+ if (value >= 0 && value <= 9)
+ {
+ tokenValue = tokenValue * 10 + value;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (Index - start > DateTimeParse.MaxDateTimeNumberDigits)
+ {
+ tokenType = TokenType.NumberToken;
+ tokenValue = -1;
+ }
+ else if (Index - start < 3)
+ {
+ tokenType = TokenType.NumberToken;
+ }
+ else
+ {
+ // If there are more than 3 digits, assume that it's a year value.
+ tokenType = TokenType.YearNumberToken;
+ }
+ if (m_checkDigitToken)
+ {
+ int save = Index;
+ char saveCh = m_current;
+ // Re-scan using the staring Index to see if this is a token.
+ Index = start; // To include the first digit.
+ m_current = Value[Index];
+ TokenType tempType;
+ int tempValue;
+ // This DTFI has tokens starting with digits.
+ // E.g. mn-MN has month name like "\x0031\x00a0\x0434\x04af\x0433\x044d\x044d\x0440\x00a0\x0441\x0430\x0440"
+ if (dtfi.Tokenize(TokenType.RegularTokenMask, out tempType, out tempValue, ref this))
+ {
+ tokenType = tempType;
+ tokenValue = tempValue;
+ // This is a token, so the Index has been advanced propertly in DTFI.Tokenizer().
+ }
+ else
+ {
+ // Use the number token value.
+ // Restore the index.
+ Index = save;
+ m_current = saveCh;
+ }
+ }
+ }
+ else if (Char.IsWhiteSpace(m_current))
+ {
+ // Just skip to the next character.
+ while (++Index < Length)
+ {
+ m_current = Value[Index];
+ if (!(Char.IsWhiteSpace(m_current)))
+ {
+ goto Start;
+ }
+ }
+ // We have reached the end of string.
+ tokenType = TokenType.EndOfString;
+ }
+ else
+ {
+ dtfi.Tokenize(TokenType.RegularTokenMask, out tokenType, out tokenValue, ref this);
+ }
+ }
+
+ internal TokenType GetSeparatorToken(DateTimeFormatInfo dtfi, out int indexBeforeSeparator, out char charBeforeSeparator)
+ {
+ indexBeforeSeparator = Index;
+ charBeforeSeparator = m_current;
+ TokenType tokenType;
+ if (!SkipWhiteSpaceCurrent())
+ {
+ // Reach the end of the string.
+ return (TokenType.SEP_End);
+ }
+ if (!DateTimeParse.IsDigit(m_current))
+ {
+ // Not a digit. Tokenize it.
+ int tokenValue;
+ bool found = dtfi.Tokenize(TokenType.SeparatorTokenMask, out tokenType, out tokenValue, ref this);
+ if (!found)
+ {
+ tokenType = TokenType.SEP_Space;
+ }
+ }
+ else
+ {
+ // Do nothing here. If we see a number, it will not be a separator. There is no need wasting time trying to find the
+ // separator token.
+ tokenType = TokenType.SEP_Space;
+ }
+ return (tokenType);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool MatchSpecifiedWord(String target) =>
+ Index + target.Length <= Length &&
+ m_info.Compare(Value.Slice(Index, target.Length), target, CompareOptions.IgnoreCase) == 0;
+
+ private static readonly Char[] WhiteSpaceChecks = new Char[] { ' ', '\u00A0' };
+
+ internal bool MatchSpecifiedWords(String target, bool checkWordBoundary, ref int matchLength)
+ {
+ int valueRemaining = Value.Length - Index;
+ matchLength = target.Length;
+
+ if (matchLength > valueRemaining || m_info.Compare(Value.Slice(Index, matchLength), target, CompareOptions.IgnoreCase) != 0)
+ {
+ // Check word by word
+ int targetPosition = 0; // Where we are in the target string
+ int thisPosition = Index; // Where we are in this string
+ int wsIndex = target.IndexOfAny(WhiteSpaceChecks, targetPosition);
+ if (wsIndex == -1)
+ {
+ return false;
+ }
+ do
+ {
+ int segmentLength = wsIndex - targetPosition;
+ if (thisPosition >= Value.Length - segmentLength)
+ { // Subtraction to prevent overflow.
+ return false;
+ }
+ if (segmentLength == 0)
+ {
+ // If segmentLength == 0, it means that we have leading space in the target string.
+ // In that case, skip the leading spaces in the target and this string.
+ matchLength--;
+ }
+ else
+ {
+ // Make sure we also have whitespace in the input string
+ if (!Char.IsWhiteSpace(Value[thisPosition + segmentLength]))
+ {
+ return false;
+ }
+ if (m_info.CompareOptionIgnoreCase(Value.Slice(thisPosition, segmentLength), target.AsSpan(targetPosition, segmentLength)) != 0)
+ {
+ return false;
+ }
+ // Advance the input string
+ thisPosition = thisPosition + segmentLength + 1;
+ }
+ // Advance our target string
+ targetPosition = wsIndex + 1;
+
+
+ // Skip past multiple whitespace
+ while (thisPosition < Value.Length && Char.IsWhiteSpace(Value[thisPosition]))
+ {
+ thisPosition++;
+ matchLength++;
+ }
+ } while ((wsIndex = target.IndexOfAny(WhiteSpaceChecks, targetPosition)) >= 0);
+ // now check the last segment;
+ if (targetPosition < target.Length)
+ {
+ int segmentLength = target.Length - targetPosition;
+ if (thisPosition > Value.Length - segmentLength)
+ {
+ return false;
+ }
+ if (m_info.CompareOptionIgnoreCase(Value.Slice(thisPosition, segmentLength), target.AsSpan(targetPosition, segmentLength)) != 0)
+ {
+ return false;
+ }
+ }
+ }
+
+ if (checkWordBoundary)
+ {
+ int nextCharIndex = Index + matchLength;
+ if (nextCharIndex < Value.Length)
+ {
+ if (Char.IsLetter(Value[nextCharIndex]))
+ {
+ return (false);
+ }
+ }
+ }
+ return (true);
+ }
+
+ //
+ // Check to see if the string starting from Index is a prefix of
+ // str.
+ // If a match is found, true value is returned and Index is updated to the next character to be parsed.
+ // Otherwise, Index is unchanged.
+ //
+ internal bool Match(String str)
+ {
+ if (++Index >= Length)
+ {
+ return (false);
+ }
+
+ if (str.Length > (Value.Length - Index))
+ {
+ return false;
+ }
+
+ if (m_info.Compare(Value.Slice(Index, str.Length), str, CompareOptions.Ordinal) == 0)
+ {
+ // Update the Index to the end of the matching string.
+ // So the following GetNext()/Match() opeartion will get
+ // the next character to be parsed.
+ Index += (str.Length - 1);
+ return (true);
+ }
+ return (false);
+ }
+
+ internal bool Match(char ch)
+ {
+ if (++Index >= Length)
+ {
+ return (false);
+ }
+ if (Value[Index] == ch)
+ {
+ m_current = ch;
+ return (true);
+ }
+ Index--;
+ return (false);
+ }
+
+ //
+ // Actions: From the current position, try matching the longest word in the specified string array.
+ // E.g. words[] = {"AB", "ABC", "ABCD"}, if the current position points to a substring like "ABC DEF",
+ // MatchLongestWords(words, ref MaxMatchStrLen) will return 1 (the index), and maxMatchLen will be 3.
+ // Returns:
+ // The index that contains the longest word to match
+ // Arguments:
+ // words The string array that contains words to search.
+ // maxMatchStrLen [in/out] the initailized maximum length. This parameter can be used to
+ // find the longest match in two string arrays.
+ //
+ internal int MatchLongestWords(String[] words, ref int maxMatchStrLen)
+ {
+ int result = -1;
+ for (int i = 0; i < words.Length; i++)
+ {
+ String word = words[i];
+ int matchLength = word.Length;
+ if (MatchSpecifiedWords(word, false, ref matchLength))
+ {
+ if (matchLength > maxMatchStrLen)
+ {
+ maxMatchStrLen = matchLength;
+ result = i;
+ }
+ }
+ }
+
+ return (result);
+ }
+
+ //
+ // Get the number of repeat character after the current character.
+ // For a string "hh:mm:ss" at Index of 3. GetRepeatCount() = 2, and Index
+ // will point to the second ':'.
+ //
+ internal int GetRepeatCount()
+ {
+ char repeatChar = Value[Index];
+ int pos = Index + 1;
+ while ((pos < Length) && (Value[pos] == repeatChar))
+ {
+ pos++;
+ }
+ int repeatCount = (pos - Index);
+ // Update the Index to the end of the repeated characters.
+ // So the following GetNext() opeartion will get
+ // the next character to be parsed.
+ Index = pos - 1;
+ return (repeatCount);
+ }
+
+ // Return false when end of string is encountered or a non-digit character is found.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool GetNextDigit() =>
+ ++Index < Length &&
+ DateTimeParse.IsDigit(Value[Index]);
+
+ //
+ // Get the current character.
+ //
+ internal char GetChar()
+ {
+ Debug.Assert(Index >= 0 && Index < Length, "Index >= 0 && Index < len");
+ return (Value[Index]);
+ }
+
+ //
+ // Convert the current character to a digit, and return it.
+ //
+ internal int GetDigit()
+ {
+ Debug.Assert(Index >= 0 && Index < Length, "Index >= 0 && Index < len");
+ Debug.Assert(DateTimeParse.IsDigit(Value[Index]), "IsDigit(Value[Index])");
+ return (Value[Index] - '0');
+ }
+
+ //
+ // Eat White Space ahead of the current position
+ //
+ // Return false if end of string is encountered.
+ //
+ internal void SkipWhiteSpaces()
+ {
+ // Look ahead to see if the next character
+ // is a whitespace.
+ while (Index + 1 < Length)
+ {
+ char ch = Value[Index + 1];
+ if (!Char.IsWhiteSpace(ch))
+ {
+ return;
+ }
+ Index++;
+ }
+ return;
+ }
+
+ //
+ // Skip white spaces from the current position
+ //
+ // Return false if end of string is encountered.
+ //
+ internal bool SkipWhiteSpaceCurrent()
+ {
+ if (Index >= Length)
+ {
+ return (false);
+ }
+
+ if (!Char.IsWhiteSpace(m_current))
+ {
+ return (true);
+ }
+
+ while (++Index < Length)
+ {
+ m_current = Value[Index];
+ if (!Char.IsWhiteSpace(m_current))
+ {
+ return (true);
+ }
+ // Nothing here.
+ }
+ return (false);
+ }
+
+ internal void TrimTail()
+ {
+ int i = Length - 1;
+ while (i >= 0 && Char.IsWhiteSpace(Value[i]))
+ {
+ i--;
+ }
+ Value = Value.Slice(0, i + 1);
+ }
+
+ // Trim the trailing spaces within a quoted string.
+ // Call this after TrimTail() is done.
+ internal void RemoveTrailingInQuoteSpaces()
+ {
+ int i = Length - 1;
+ if (i <= 1)
+ {
+ return;
+ }
+ char ch = Value[i];
+ // Check if the last character is a quote.
+ if (ch == '\'' || ch == '\"')
+ {
+ if (char.IsWhiteSpace(Value[i - 1]))
+ {
+ i--;
+ while (i >= 1 && char.IsWhiteSpace(Value[i - 1]))
+ {
+ i--;
+ }
+ Span<char> result = new char[i + 1];
+ result[i] = ch;
+ Value.Slice(0, i).CopyTo(result);
+ Value = result;
+ }
+ }
+ }
+
+ // Trim the leading spaces within a quoted string.
+ // Call this after the leading spaces before quoted string are trimmed.
+ internal void RemoveLeadingInQuoteSpaces()
+ {
+ if (Length <= 2)
+ {
+ return;
+ }
+ int i = 0;
+ char ch = Value[i];
+ // Check if the last character is a quote.
+ if (ch == '\'' || ch == '\"')
+ {
+ while ((i + 1) < Length && char.IsWhiteSpace(Value[i + 1]))
+ {
+ i++;
+ }
+ if (i != 0)
+ {
+ Span<char> result = new char[Value.Length - i];
+ result[0] = ch;
+ Value.Slice(i + 1).CopyTo(result.Slice(1));
+ Value = result;
+ }
+ }
+ }
+
+ internal DTSubString GetSubString()
+ {
+ DTSubString sub = new DTSubString();
+ sub.index = Index;
+ sub.s = Value;
+ while (Index + sub.length < Length)
+ {
+ DTSubStringType currentType;
+ Char ch = Value[Index + sub.length];
+ if (ch >= '0' && ch <= '9')
+ {
+ currentType = DTSubStringType.Number;
+ }
+ else
+ {
+ currentType = DTSubStringType.Other;
+ }
+
+ if (sub.length == 0)
+ {
+ sub.type = currentType;
+ }
+ else
+ {
+ if (sub.type != currentType)
+ {
+ break;
+ }
+ }
+ sub.length++;
+ if (currentType == DTSubStringType.Number)
+ {
+ // Incorporate the number into the value
+ // Limit the digits to prevent overflow
+ if (sub.length > DateTimeParse.MaxDateTimeNumberDigits)
+ {
+ sub.type = DTSubStringType.Invalid;
+ return sub;
+ }
+ int number = ch - '0';
+ Debug.Assert(number >= 0 && number <= 9, "number >= 0 && number <= 9");
+ sub.value = sub.value * 10 + number;
+ }
+ else
+ {
+ // For non numbers, just return this length 1 token. This should be expanded
+ // to more types of thing if this parsing approach is used for things other
+ // than numbers and single characters
+ break;
+ }
+ }
+ if (sub.length == 0)
+ {
+ sub.type = DTSubStringType.End;
+ return sub;
+ }
+
+ return sub;
+ }
+
+ internal void ConsumeSubString(DTSubString sub)
+ {
+ Debug.Assert(sub.index == Index, "sub.index == Index");
+ Debug.Assert(sub.index + sub.length <= Length, "sub.index + sub.length <= len");
+ Index = sub.index + sub.length;
+ if (Index < Length)
+ {
+ m_current = Value[Index];
+ }
+ }
+ }
+
+ internal enum DTSubStringType
+ {
+ Unknown = 0,
+ Invalid = 1,
+ Number = 2,
+ End = 3,
+ Other = 4,
+ }
+
+ internal ref struct DTSubString
+ {
+ internal ReadOnlySpan<char> s;
+ internal Int32 index;
+ internal Int32 length;
+ internal DTSubStringType type;
+ internal Int32 value;
+
+ internal Char this[Int32 relativeIndex]
+ {
+ get
+ {
+ return s[index + relativeIndex];
+ }
+ }
+ }
+
+ //
+ // The buffer to store the parsing token.
+ //
+ internal
+ struct DateTimeToken
+ {
+ internal DateTimeParse.DTT dtt; // Store the token
+ internal TokenType suffix; // Store the CJK Year/Month/Day suffix (if any)
+ internal int num; // Store the number that we are parsing (if any)
+ }
+
+ //
+ // The buffer to store temporary parsing information.
+ //
+ internal
+ unsafe struct DateTimeRawInfo
+ {
+ private int* num;
+ internal int numCount;
+ internal int month;
+ internal int year;
+ internal int dayOfWeek;
+ internal int era;
+ internal DateTimeParse.TM timeMark;
+ internal double fraction;
+ internal bool hasSameDateAndTimeSeparators;
+
+ internal void Init(int* numberBuffer)
+ {
+ month = -1;
+ year = -1;
+ dayOfWeek = -1;
+ era = -1;
+ timeMark = DateTimeParse.TM.NotSet;
+ fraction = -1;
+ num = numberBuffer;
+ }
+ internal unsafe void AddNumber(int value)
+ {
+ num[numCount++] = value;
+ }
+ internal unsafe int GetNumber(int index)
+ {
+ return num[index];
+ }
+ }
+
+ internal enum ParseFailureKind
+ {
+ None = 0,
+ ArgumentNull = 1,
+ Format = 2,
+ FormatWithParameter = 3,
+ FormatWithOriginalDateTime = 4,
+ FormatWithFormatSpecifier = 5,
+ FormatWithOriginalDateTimeAndParameter = 6,
+ FormatBadDateTimeCalendar = 7, // FormatException when ArgumentOutOfRange is thrown by a Calendar.TryToDateTime().
+ };
+
+ [Flags]
+ internal enum ParseFlags
+ {
+ HaveYear = 0x00000001,
+ HaveMonth = 0x00000002,
+ HaveDay = 0x00000004,
+ HaveHour = 0x00000008,
+ HaveMinute = 0x00000010,
+ HaveSecond = 0x00000020,
+ HaveTime = 0x00000040,
+ HaveDate = 0x00000080,
+ TimeZoneUsed = 0x00000100,
+ TimeZoneUtc = 0x00000200,
+ ParsedMonthName = 0x00000400,
+ CaptureOffset = 0x00000800,
+ YearDefault = 0x00001000,
+ Rfc1123Pattern = 0x00002000,
+ UtcSortPattern = 0x00004000,
+ }
+
+ //
+ // This will store the result of the parsing. And it will be eventually
+ // used to construct a DateTime instance.
+ //
+ internal
+ ref struct DateTimeResult
+ {
+ internal int Year;
+ internal int Month;
+ internal int Day;
+ //
+ // Set time defualt to 00:00:00.
+ //
+ internal int Hour;
+ internal int Minute;
+ internal int Second;
+ internal double fraction;
+
+ internal int era;
+
+ internal ParseFlags flags;
+
+ internal TimeSpan timeZoneOffset;
+
+ internal Calendar calendar;
+
+ internal DateTime parsedDate;
+
+ internal ParseFailureKind failure;
+ internal string failureMessageID;
+ internal object failureMessageFormatArgument;
+ internal string failureArgumentName;
+ internal ReadOnlySpan<char> originalDateTimeString;
+ internal ReadOnlySpan<char> failedFormatSpecifier;
+
+ internal void Init(ReadOnlySpan<char> originalDateTimeString)
+ {
+ this.originalDateTimeString = originalDateTimeString;
+ Year = -1;
+ Month = -1;
+ Day = -1;
+ fraction = -1;
+ era = -1;
+ }
+
+ internal void SetDate(int year, int month, int day)
+ {
+ Year = year;
+ Month = month;
+ Day = day;
+ }
+
+ internal void SetBadFormatSpecifierFailure()
+ {
+ SetBadFormatSpecifierFailure(ReadOnlySpan<char>.Empty);
+ }
+
+ internal void SetBadFormatSpecifierFailure(ReadOnlySpan<char> failedFormatSpecifier)
+ {
+ this.failure = ParseFailureKind.FormatWithFormatSpecifier;
+ this.failureMessageID = nameof(SR.Format_BadFormatSpecifier);
+ this.failedFormatSpecifier = failedFormatSpecifier;
+ }
+
+ internal void SetBadDateTimeFailure()
+ {
+ this.failure = ParseFailureKind.FormatWithOriginalDateTime;
+ this.failureMessageID = nameof(SR.Format_BadDateTime);
+ this.failureMessageFormatArgument = null;
+ }
+
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID)
+ {
+ this.failure = failure;
+ this.failureMessageID = failureMessageID;
+ this.failureMessageFormatArgument = null;
+ }
+
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument)
+ {
+ this.failure = failure;
+ this.failureMessageID = failureMessageID;
+ this.failureMessageFormatArgument = failureMessageFormatArgument;
+ }
+
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument, string failureArgumentName)
+ {
+ this.failure = failure;
+ this.failureMessageID = failureMessageID;
+ this.failureMessageFormatArgument = failureMessageFormatArgument;
+ this.failureArgumentName = failureArgumentName;
+ }
+ }
+
+ // This is the helper data structure used in ParseExact().
+ internal struct ParsingInfo
+ {
+ internal Calendar calendar;
+ internal int dayOfWeek;
+ internal DateTimeParse.TM timeMark;
+
+ internal bool fUseHour12;
+ internal bool fUseTwoDigitYear;
+ internal bool fAllowInnerWhite;
+ internal bool fAllowTrailingWhite;
+ internal bool fCustomNumberParser;
+ internal DateTimeParse.MatchNumberDelegate parseNumberDelegate;
+
+ internal void Init()
+ {
+ dayOfWeek = -1;
+ timeMark = DateTimeParse.TM.NotSet;
+ }
+ }
+
+ //
+ // The type of token that will be returned by DateTimeFormatInfo.Tokenize().
+ //
+ internal enum TokenType
+ {
+ // The valid token should start from 1.
+
+ // Regular tokens. The range is from 0x00 ~ 0xff.
+ NumberToken = 1, // The number. E.g. "12"
+ YearNumberToken = 2, // The number which is considered as year number, which has 3 or more digits. E.g. "2003"
+ Am = 3, // AM timemark. E.g. "AM"
+ Pm = 4, // PM timemark. E.g. "PM"
+ MonthToken = 5, // A word (or words) that represents a month name. E.g. "March"
+ EndOfString = 6, // End of string
+ DayOfWeekToken = 7, // A word (or words) that represents a day of week name. E.g. "Monday" or "Mon"
+ TimeZoneToken = 8, // A word that represents a timezone name. E.g. "GMT"
+ EraToken = 9, // A word that represents a era name. E.g. "A.D."
+ DateWordToken = 10, // A word that can appear in a DateTime string, but serves no parsing semantics. E.g. "de" in Spanish culture.
+ UnknownToken = 11, // An unknown word, which signals an error in parsing.
+ HebrewNumber = 12, // A number that is composed of Hebrew text. Hebrew calendar uses Hebrew digits for year values, month values, and day values.
+ JapaneseEraToken = 13, // Era name for JapaneseCalendar
+ TEraToken = 14, // Era name for TaiwanCalendar
+ IgnorableSymbol = 15, // A separator like "," that is equivalent to whitespace
+
+
+ // Separator tokens.
+ SEP_Unk = 0x100, // Unknown separator.
+ SEP_End = 0x200, // The end of the parsing string.
+ SEP_Space = 0x300, // Whitespace (including comma).
+ SEP_Am = 0x400, // AM timemark. E.g. "AM"
+ SEP_Pm = 0x500, // PM timemark. E.g. "PM"
+ SEP_Date = 0x600, // date separator. E.g. "/"
+ SEP_Time = 0x700, // time separator. E.g. ":"
+ SEP_YearSuff = 0x800, // Chinese/Japanese/Korean year suffix.
+ SEP_MonthSuff = 0x900, // Chinese/Japanese/Korean month suffix.
+ SEP_DaySuff = 0xa00, // Chinese/Japanese/Korean day suffix.
+ SEP_HourSuff = 0xb00, // Chinese/Japanese/Korean hour suffix.
+ SEP_MinuteSuff = 0xc00, // Chinese/Japanese/Korean minute suffix.
+ SEP_SecondSuff = 0xd00, // Chinese/Japanese/Korean second suffix.
+ SEP_LocalTimeMark = 0xe00, // 'T', used in ISO 8601 format.
+ SEP_DateOrOffset = 0xf00, // '-' which could be a date separator or start of a time zone offset
+
+ RegularTokenMask = 0x00ff,
+ SeparatorTokenMask = 0xff00,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeStyles.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeStyles.cs
new file mode 100644
index 0000000000..bf68ac91d5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeStyles.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Contains valid formats for DateTime recognized by
+** the DateTime class' parsing code.
+**
+**
+===========================================================*/
+
+namespace System.Globalization
+{
+ [Flags]
+ public enum DateTimeStyles
+ {
+ // Bit flag indicating that leading whitespace is allowed. Character values
+ // 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, and 0x0020 are considered to be
+ // whitespace.
+
+
+ None = 0x00000000,
+
+ AllowLeadingWhite = 0x00000001,
+
+ AllowTrailingWhite = 0x00000002, //Bitflag indicating trailing whitespace is allowed.
+
+ AllowInnerWhite = 0x00000004,
+
+ AllowWhiteSpaces = AllowLeadingWhite | AllowInnerWhite | AllowTrailingWhite,
+ // When parsing a date/time string, if all year/month/day are missing, set the default date
+ // to 0001/1/1, instead of the current year/month/day.
+
+ NoCurrentDateDefault = 0x00000008,
+ // When parsing a date/time string, if a timezone specifier ("GMT","Z","+xxxx", "-xxxx" exists), we will
+ // adjust the parsed time based to GMT.
+
+ AdjustToUniversal = 0x00000010,
+
+ AssumeLocal = 0x00000020,
+
+ AssumeUniversal = 0x00000040,
+ // Attempt to preserve whether the input is unspecified, local or UTC
+ RoundtripKind = 0x00000080,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DaylightTime.cs b/src/System.Private.CoreLib/shared/System/Globalization/DaylightTime.cs
new file mode 100644
index 0000000000..72a572c97d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/DaylightTime.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ // This class represents a starting/ending time for a period of daylight saving time.
+ public class DaylightTime
+ {
+ private readonly DateTime _start;
+ private readonly DateTime _end;
+ private readonly TimeSpan _delta;
+
+ public DaylightTime(DateTime start, DateTime end, TimeSpan delta)
+ {
+ _start = start;
+ _end = end;
+ _delta = delta;
+ }
+
+ // The start date of a daylight saving period.
+ public DateTime Start => _start;
+
+ // The end date of a daylight saving period.
+ public DateTime End => _end;
+
+ // Delta to stardard offset in ticks.
+ public TimeSpan Delta => _delta;
+ }
+
+ // Value type version of DaylightTime
+ internal readonly struct DaylightTimeStruct
+ {
+ public DaylightTimeStruct(DateTime start, DateTime end, TimeSpan delta)
+ {
+ Start = start;
+ End = end;
+ Delta = delta;
+ }
+
+ public readonly DateTime Start;
+ public readonly DateTime End;
+ public readonly TimeSpan Delta;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DigitShapes.cs b/src/System.Private.CoreLib/shared/System/Globalization/DigitShapes.cs
new file mode 100644
index 0000000000..1ce45dbeb6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/DigitShapes.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ public enum DigitShapes : int
+ {
+ Context = 0x0000, // The shape depends on the previous text in the same output.
+ None = 0x0001, // Gives full Unicode compatibility.
+ NativeNational = 0x0002 // National shapes
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/EastAsianLunisolarCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/EastAsianLunisolarCalendar.cs
new file mode 100644
index 0000000000..358f4df182
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/EastAsianLunisolarCalendar.cs
@@ -0,0 +1,703 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization
+{
+ public abstract class EastAsianLunisolarCalendar : Calendar
+ {
+ internal const int LeapMonth = 0;
+ internal const int Jan1Month = 1;
+ internal const int Jan1Date = 2;
+ internal const int nDaysPerMonth = 3;
+
+ // # of days so far in the solar year
+ internal static readonly int[] DaysToMonth365 =
+ {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+
+ internal static readonly int[] DaysToMonth366 =
+ {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
+ };
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.LunisolarCalendar;
+ }
+ }
+
+ // Return the year number in the 60-year cycle.
+ //
+
+ public virtual int GetSexagenaryYear(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+
+ int year = 0, month = 0, day = 0;
+ TimeToLunar(time, ref year, ref month, ref day);
+
+ return ((year - 4) % 60) + 1;
+ }
+
+ // Return the celestial year from the 60-year cycle.
+ // The returned value is from 1 ~ 10.
+ //
+
+ public int GetCelestialStem(int sexagenaryYear)
+ {
+ if ((sexagenaryYear < 1) || (sexagenaryYear > 60))
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(sexagenaryYear),
+ SR.Format(SR.ArgumentOutOfRange_Range, 1, 60));
+ }
+
+ return ((sexagenaryYear - 1) % 10) + 1;
+ }
+
+ // Return the Terrestial Branch from the 60-year cycle.
+ // The returned value is from 1 ~ 12.
+ //
+
+ public int GetTerrestrialBranch(int sexagenaryYear)
+ {
+ if ((sexagenaryYear < 1) || (sexagenaryYear > 60))
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(sexagenaryYear),
+ SR.Format(SR.ArgumentOutOfRange_Range, 1, 60));
+ }
+
+ return ((sexagenaryYear - 1) % 12) + 1;
+ }
+
+ internal abstract int GetYearInfo(int LunarYear, int Index);
+ internal abstract int GetYear(int year, DateTime time);
+ internal abstract int GetGregorianYear(int year, int era);
+
+ internal abstract int MinCalendarYear { get; }
+ internal abstract int MaxCalendarYear { get; }
+ internal abstract EraInfo[] CalEraInfo { get; }
+ internal abstract DateTime MinDate { get; }
+ internal abstract DateTime MaxDate { get; }
+
+ internal const int MaxCalendarMonth = 13;
+ internal const int MaxCalendarDay = 30;
+
+ internal int MinEraCalendarYear(int era)
+ {
+ EraInfo[] mEraInfo = CalEraInfo;
+ //ChineseLunisolarCalendar does not has m_EraInfo it is going to retuen null
+ if (mEraInfo == null)
+ {
+ return MinCalendarYear;
+ }
+
+ if (era == Calendar.CurrentEra)
+ {
+ era = CurrentEraValue;
+ }
+ //era has to be in the supported range otherwise we will throw exception in CheckEraRange()
+ if (era == GetEra(MinDate))
+ {
+ return (GetYear(MinCalendarYear, MinDate));
+ }
+
+ for (int i = 0; i < mEraInfo.Length; i++)
+ {
+ if (era == mEraInfo[i].era)
+ {
+ return (mEraInfo[i].minEraYear);
+ }
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal int MaxEraCalendarYear(int era)
+ {
+ EraInfo[] mEraInfo = CalEraInfo;
+ //ChineseLunisolarCalendar does not has m_EraInfo it is going to retuen null
+ if (mEraInfo == null)
+ {
+ return MaxCalendarYear;
+ }
+
+ if (era == Calendar.CurrentEra)
+ {
+ era = CurrentEraValue;
+ }
+ //era has to be in the supported range otherwise we will throw exception in CheckEraRange()
+ if (era == GetEra(MaxDate))
+ {
+ return (GetYear(MaxCalendarYear, MaxDate));
+ }
+
+ for (int i = 0; i < mEraInfo.Length; i++)
+ {
+ if (era == mEraInfo[i].era)
+ {
+ return (mEraInfo[i].maxEraYear);
+ }
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal EastAsianLunisolarCalendar()
+ {
+ }
+
+ internal void CheckTicksRange(long ticks)
+ {
+ if (ticks < MinSupportedDateTime.Ticks || ticks > MaxSupportedDateTime.Ticks)
+ {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(CultureInfo.InvariantCulture, SR.ArgumentOutOfRange_CalendarRange,
+ MinSupportedDateTime, MaxSupportedDateTime));
+ }
+ }
+
+ internal void CheckEraRange(int era)
+ {
+ if (era == Calendar.CurrentEra)
+ {
+ era = CurrentEraValue;
+ }
+
+ if ((era < GetEra(MinDate)) || (era > GetEra(MaxDate)))
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ internal int CheckYearRange(int year, int era)
+ {
+ CheckEraRange(era);
+ year = GetGregorianYear(year, era);
+
+ if ((year < MinCalendarYear) || (year > MaxCalendarYear))
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ SR.Format(SR.ArgumentOutOfRange_Range, MinEraCalendarYear(era), MaxEraCalendarYear(era)));
+ }
+ return year;
+ }
+
+ internal int CheckYearMonthRange(int year, int month, int era)
+ {
+ year = CheckYearRange(year, era);
+
+ if (month == 13)
+ {
+ //Reject if there is no leap month this year
+ if (GetYearInfo(year, LeapMonth) == 0)
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+
+ if (month < 1 || month > 13)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+ return year;
+ }
+
+ internal int InternalGetDaysInMonth(int year, int month)
+ {
+ int nDays;
+ int mask; // mask for extracting bits
+
+ mask = 0x8000;
+ // convert the lunar day into a lunar month/date
+ mask >>= (month - 1);
+ if ((GetYearInfo(year, nDaysPerMonth) & mask) == 0)
+ nDays = 29;
+ else
+ nDays = 30;
+ return nDays;
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ year = CheckYearMonthRange(year, month, era);
+ return InternalGetDaysInMonth(year, month);
+ }
+
+ private static int GregorianIsLeapYear(int y)
+ {
+ return ((((y) % 4) != 0) ? 0 : ((((y) % 100) != 0) ? 1 : ((((y) % 400) != 0) ? 0 : 1)));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ year = CheckYearMonthRange(year, month, era);
+ int daysInMonth = InternalGetDaysInMonth(year, month);
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ SR.Format(SR.ArgumentOutOfRange_Day, daysInMonth, month));
+ }
+
+ int gy = 0; int gm = 0; int gd = 0;
+
+ if (LunarToGregorian(year, month, day, ref gy, ref gm, ref gd))
+ {
+ return new DateTime(gy, gm, gd, hour, minute, second, millisecond);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+ }
+
+
+ //
+ // GregorianToLunar calculates lunar calendar info for the given gregorian year, month, date.
+ // The input date should be validated before calling this method.
+ //
+ internal void GregorianToLunar(int nSYear, int nSMonth, int nSDate, ref int nLYear, ref int nLMonth, ref int nLDate)
+ {
+ // unsigned int nLYear, nLMonth, nLDate; // lunar ymd
+ int nSolarDay; // day # in solar year
+ int nLunarDay; // day # in lunar year
+ int fLeap; // is it a solar leap year?
+ int LDpM; // lunar days/month bitfield
+ int mask; // mask for extracting bits
+ int nDays; // # days this lunar month
+ int nJan1Month, nJan1Date;
+
+ // calc the solar day of year
+ fLeap = GregorianIsLeapYear(nSYear);
+ nSolarDay = (fLeap == 1) ? DaysToMonth366[nSMonth - 1] : DaysToMonth365[nSMonth - 1];
+ nSolarDay += nSDate;
+
+ // init lunar year info
+ nLunarDay = nSolarDay;
+ nLYear = nSYear;
+ if (nLYear == (MaxCalendarYear + 1))
+ {
+ nLYear--;
+ nLunarDay += ((GregorianIsLeapYear(nLYear) == 1) ? 366 : 365);
+ nJan1Month = GetYearInfo(nLYear, Jan1Month);
+ nJan1Date = GetYearInfo(nLYear, Jan1Date);
+ }
+ else
+ {
+ nJan1Month = GetYearInfo(nLYear, Jan1Month);
+ nJan1Date = GetYearInfo(nLYear, Jan1Date);
+
+ // check if this solar date is actually part of the previous
+ // lunar year
+ if ((nSMonth < nJan1Month) ||
+ (nSMonth == nJan1Month && nSDate < nJan1Date))
+ {
+ // the corresponding lunar day is actually part of the previous
+ // lunar year
+ nLYear--;
+
+ // add a solar year to the lunar day #
+ nLunarDay += ((GregorianIsLeapYear(nLYear) == 1) ? 366 : 365);
+
+ // update the new start of year
+ nJan1Month = GetYearInfo(nLYear, Jan1Month);
+ nJan1Date = GetYearInfo(nLYear, Jan1Date);
+ }
+ }
+
+ // convert solar day into lunar day.
+ // subtract off the beginning part of the solar year which is not
+ // part of the lunar year. since this part is always in Jan or Feb,
+ // we don't need to handle Leap Year (LY only affects March
+ // and later).
+ nLunarDay -= DaysToMonth365[nJan1Month - 1];
+ nLunarDay -= (nJan1Date - 1);
+
+ // convert the lunar day into a lunar month/date
+ mask = 0x8000;
+ LDpM = GetYearInfo(nLYear, nDaysPerMonth);
+ nDays = ((LDpM & mask) != 0) ? 30 : 29;
+ nLMonth = 1;
+ while (nLunarDay > nDays)
+ {
+ nLunarDay -= nDays;
+ nLMonth++;
+ mask >>= 1;
+ nDays = ((LDpM & mask) != 0) ? 30 : 29;
+ }
+ nLDate = nLunarDay;
+ }
+
+ /*
+ //Convert from Lunar to Gregorian
+ //Highly inefficient, but it works based on the forward conversion
+ */
+ internal bool LunarToGregorian(int nLYear, int nLMonth, int nLDate, ref int nSolarYear, ref int nSolarMonth, ref int nSolarDay)
+ {
+ int numLunarDays;
+
+ if (nLDate < 1 || nLDate > 30)
+ return false;
+
+ numLunarDays = nLDate - 1;
+
+ //Add previous months days to form the total num of days from the first of the month.
+ for (int i = 1; i < nLMonth; i++)
+ {
+ numLunarDays += InternalGetDaysInMonth(nLYear, i);
+ }
+
+ //Get Gregorian First of year
+ int nJan1Month = GetYearInfo(nLYear, Jan1Month);
+ int nJan1Date = GetYearInfo(nLYear, Jan1Date);
+
+ // calc the solar day of year of 1 Lunar day
+ int fLeap = GregorianIsLeapYear(nLYear);
+ int[] days = (fLeap == 1) ? DaysToMonth366 : DaysToMonth365;
+
+ nSolarDay = nJan1Date;
+
+ if (nJan1Month > 1)
+ nSolarDay += days[nJan1Month - 1];
+
+ // Add the actual lunar day to get the solar day we want
+ nSolarDay = nSolarDay + numLunarDays;// - 1;
+
+ if (nSolarDay > (fLeap + 365))
+ {
+ nSolarYear = nLYear + 1;
+ nSolarDay -= (fLeap + 365);
+ }
+ else
+ {
+ nSolarYear = nLYear;
+ }
+
+ for (nSolarMonth = 1; nSolarMonth < 12; nSolarMonth++)
+ {
+ if (days[nSolarMonth] >= nSolarDay)
+ break;
+ }
+
+ nSolarDay -= days[nSolarMonth - 1];
+ return true;
+ }
+
+ internal DateTime LunarToTime(DateTime time, int year, int month, int day)
+ {
+ int gy = 0; int gm = 0; int gd = 0;
+ LunarToGregorian(year, month, day, ref gy, ref gm, ref gd);
+ return (GregorianCalendar.GetDefaultInstance().ToDateTime(gy, gm, gd, time.Hour, time.Minute, time.Second, time.Millisecond));
+ }
+
+ internal void TimeToLunar(DateTime time, ref int year, ref int month, ref int day)
+ {
+ int gy = 0; int gm = 0; int gd = 0;
+
+ Calendar Greg = GregorianCalendar.GetDefaultInstance();
+ gy = Greg.GetYear(time);
+ gm = Greg.GetMonth(time);
+ gd = Greg.GetDayOfMonth(time);
+
+ GregorianToLunar(gy, gm, gd, ref year, ref month, ref day);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(months),
+ SR.Format(SR.ArgumentOutOfRange_Range, -120000, 120000));
+ }
+
+ CheckTicksRange(time.Ticks);
+
+ int y = 0; int m = 0; int d = 0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ int i = m + months;
+ if (i > 0)
+ {
+ int monthsInYear = InternalIsLeapYear(y) ? 13 : 12;
+
+ while (i - monthsInYear > 0)
+ {
+ i -= monthsInYear;
+ y++;
+ monthsInYear = InternalIsLeapYear(y) ? 13 : 12;
+ }
+ m = i;
+ }
+ else
+ {
+ int monthsInYear;
+ while (i <= 0)
+ {
+ monthsInYear = InternalIsLeapYear(y - 1) ? 13 : 12;
+ i += monthsInYear;
+ y--;
+ }
+ m = i;
+ }
+
+ int days = InternalGetDaysInMonth(y, m);
+ if (d > days)
+ {
+ d = days;
+ }
+ DateTime dt = LunarToTime(time, y, m, d);
+
+ CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (dt);
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ CheckTicksRange(time.Ticks);
+
+ int y = 0; int m = 0; int d = 0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ y += years;
+
+ if (m == 13 && !InternalIsLeapYear(y))
+ {
+ m = 12;
+ d = InternalGetDaysInMonth(y, m);
+ }
+ int DaysInMonths = InternalGetDaysInMonth(y, m);
+ if (d > DaysInMonths)
+ {
+ d = DaysInMonths;
+ }
+
+ DateTime dt = LunarToTime(time, y, m, d);
+ CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (dt);
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and [354|355 |383|384].
+ //
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+
+ int y = 0; int m = 0; int d = 0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ for (int i = 1; i < m; i++)
+ {
+ d = d + InternalGetDaysInMonth(y, i);
+ }
+ return d;
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 29 or 30.
+ //
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+
+ int y = 0; int m = 0; int d = 0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ return d;
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ year = CheckYearRange(year, era);
+
+ int Days = 0;
+ int monthsInYear = InternalIsLeapYear(year) ? 13 : 12;
+
+ while (monthsInYear != 0)
+ Days += InternalGetDaysInMonth(year, monthsInYear--);
+
+ return Days;
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 13.
+ //
+
+ public override int GetMonth(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+
+ int y = 0; int m = 0; int d = 0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ return m;
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and MaxCalendarYear.
+ //
+
+ public override int GetYear(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+
+ int y = 0; int m = 0; int d = 0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ return GetYear(y, time);
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return ((DayOfWeek)((int)(time.Ticks / Calendar.TicksPerDay + 1) % 7));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ year = CheckYearRange(year, era);
+ return (InternalIsLeapYear(year) ? 13 : 12);
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ year = CheckYearMonthRange(year, month, era);
+ int daysInMonth = InternalGetDaysInMonth(year, month);
+
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ SR.Format(SR.ArgumentOutOfRange_Day, daysInMonth, month));
+ }
+ int m = GetYearInfo(year, LeapMonth);
+ return ((m != 0) && (month == (m + 1)));
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ year = CheckYearMonthRange(year, month, era);
+ int m = GetYearInfo(year, LeapMonth);
+ return ((m != 0) && (month == (m + 1)));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ year = CheckYearRange(year, era);
+ int month = GetYearInfo(year, LeapMonth);
+ if (month > 0)
+ {
+ return (month + 1);
+ }
+ return 0;
+ }
+
+ internal bool InternalIsLeapYear(int year)
+ {
+ return (GetYearInfo(year, LeapMonth) != 0);
+ }
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ year = CheckYearRange(year, era);
+ return InternalIsLeapYear(year);
+ }
+
+ private const int DEFAULT_GREGORIAN_TWO_DIGIT_YEAR_MAX = 2029;
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(BaseCalendarID, GetYear(new DateTime(DEFAULT_GREGORIAN_TWO_DIGIT_YEAR_MAX, 1, 1)));
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(value),
+ SR.Format(SR.ArgumentOutOfRange_Range, 99, MaxCalendarYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ year = base.ToFourDigitYear(year);
+ CheckYearRange(year, CurrentEra);
+ return (year);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/GlobalizationExtensions.cs b/src/System.Private.CoreLib/shared/System/Globalization/GlobalizationExtensions.cs
new file mode 100644
index 0000000000..007283aa6b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/GlobalizationExtensions.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ public static class GlobalizationExtensions
+ {
+ public static StringComparer GetStringComparer(this CompareInfo compareInfo, CompareOptions options)
+ {
+ if (compareInfo == null)
+ {
+ throw new ArgumentNullException(nameof(compareInfo));
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return StringComparer.Ordinal;
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return StringComparer.OrdinalIgnoreCase;
+ }
+
+ return new CultureAwareComparer(compareInfo, options);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendar.cs
new file mode 100644
index 0000000000..94963a19cc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendar.cs
@@ -0,0 +1,584 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Threading;
+
+namespace System.Globalization
+{
+ // This calendar recognizes two era values:
+ // 0 CurrentEra (AD)
+ // 1 BeforeCurrentEra (BC)
+ public class GregorianCalendar : Calendar
+ {
+ /*
+ A.D. = anno Domini
+ */
+
+ public const int ADEra = 1;
+
+ //
+ // This is the max Gregorian year can be represented by DateTime class. The limitation
+ // is derived from DateTime class.
+ //
+ internal const int MaxYear = 9999;
+
+ internal GregorianCalendarTypes m_type;
+
+ internal static readonly int[] DaysToMonth365 =
+ {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+ };
+
+ internal static readonly int[] DaysToMonth366 =
+ {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
+ };
+
+ private static volatile Calendar s_defaultInstance;
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of GregorianCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ internal static Calendar GetDefaultInstance()
+ {
+ if (s_defaultInstance == null)
+ {
+ s_defaultInstance = new GregorianCalendar();
+ }
+ return (s_defaultInstance);
+ }
+
+ // Construct an instance of gregorian calendar.
+
+ public GregorianCalendar() :
+ this(GregorianCalendarTypes.Localized)
+ {
+ }
+
+
+ public GregorianCalendar(GregorianCalendarTypes type)
+ {
+ if ((int)type < (int)GregorianCalendarTypes.Localized || (int)type > (int)GregorianCalendarTypes.TransliteratedFrench)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(type),
+ SR.Format(SR.ArgumentOutOfRange_Range,
+ GregorianCalendarTypes.Localized, GregorianCalendarTypes.TransliteratedFrench));
+ }
+ this.m_type = type;
+ }
+
+ public virtual GregorianCalendarTypes CalendarType
+ {
+ get
+ {
+ return (m_type);
+ }
+
+ set
+ {
+ VerifyWritable();
+
+ switch (value)
+ {
+ case GregorianCalendarTypes.Localized:
+ case GregorianCalendarTypes.USEnglish:
+ case GregorianCalendarTypes.MiddleEastFrench:
+ case GregorianCalendarTypes.Arabic:
+ case GregorianCalendarTypes.TransliteratedEnglish:
+ case GregorianCalendarTypes.TransliteratedFrench:
+ m_type = value;
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException("m_type", SR.ArgumentOutOfRange_Enum);
+ }
+ }
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ // By returning different ID for different variations of GregorianCalendar,
+ // we can support the Transliterated Gregorian calendar.
+ // DateTimeFormatInfo will use this ID to get formatting information about
+ // the calendar.
+ return ((CalendarId)m_type);
+ }
+ }
+
+
+ /*=================================GetAbsoluteDate==========================
+ **Action: Gets the absolute date for the given Gregorian date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns: the absolute date
+ **Arguments:
+ ** year the Gregorian year
+ ** month the Gregorian month
+ ** day the day
+ **Exceptions:
+ ** ArgumentOutOfRangException if year, month, day value is valid.
+ **Note:
+ ** This is an internal method used by DateToTicks() and the calculations of Hijri and Hebrew calendars.
+ ** Number of Days in Prior Years (both common and leap years) +
+ ** Number of Days in Prior Months of Current Year +
+ ** Number of Days in Current Month
+ **
+ ============================================================================*/
+
+ internal static long GetAbsoluteDate(int year, int month, int day)
+ {
+ if (year >= 1 && year <= MaxYear && month >= 1 && month <= 12)
+ {
+ int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) ? DaysToMonth366 : DaysToMonth365;
+ if (day >= 1 && (day <= days[month] - days[month - 1]))
+ {
+ int y = year - 1;
+ int absoluteDate = y * 365 + y / 4 - y / 100 + y / 400 + days[month - 1] + day - 1;
+ return (absoluteDate);
+ }
+ }
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+
+ // Returns the tick count corresponding to the given year, month, and day.
+ // Will check the if the parameters are valid.
+ internal virtual long DateToTicks(int year, int month, int day)
+ {
+ return (GetAbsoluteDate(year, month, day) * TicksPerDay);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ -120000,
+ 120000));
+ }
+ time.GetDatePart(out int y, out int m, out int d);
+ int i = m - 1 + months;
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int[] daysArray = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? DaysToMonth366 : DaysToMonth365;
+ int days = (daysArray[m] - daysArray[m - 1]);
+
+ if (d > days)
+ {
+ d = days;
+ }
+ long ticks = DateToTicks(y, m, d) + time.Ticks % TicksPerDay;
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+
+ return (new DateTime(ticks));
+ }
+
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return time.Day;
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return time.DayOfYear;
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ if (era == CurrentEra || era == ADEra)
+ {
+ if (year < 1 || year > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year), SR.Format(SR.ArgumentOutOfRange_Range,
+ 1, MaxYear));
+ }
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+ int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365);
+ return (days[month] - days[month - 1]);
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ if (era == CurrentEra || era == ADEra)
+ {
+ if (year >= 1 && year <= MaxYear)
+ {
+ return ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 366 : 365);
+ }
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxYear));
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ // Returns the era for the specified DateTime value.
+
+ public override int GetEra(DateTime time)
+ {
+ return (ADEra);
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { ADEra });
+ }
+ }
+
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+ public override int GetMonth(DateTime time)
+ {
+ return time.Month;
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ if (era == CurrentEra || era == ADEra)
+ {
+ if (year >= 1 && year <= MaxYear)
+ {
+ return (12);
+ }
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxYear));
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+
+ public override int GetYear(DateTime time)
+ {
+ return time.Year;
+ }
+
+ internal override bool IsValidYear(int year, int era) => year >= 1 && year <= MaxYear;
+
+ internal override bool IsValidDay(int year, int month, int day, int era)
+ {
+ if ((era != CurrentEra && era != ADEra) ||
+ year < 1 || year > MaxYear ||
+ month < 1 || month > 12 ||
+ day < 1)
+ {
+ return false;
+ }
+
+ int[] days = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365;
+ return day <= (days[month] - days[month - 1]);
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
+ 1, 12));
+ }
+
+ if (era != CurrentEra && era != ADEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ if (year < 1 || year > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxYear));
+ }
+
+ if (day < 1 || day > GetDaysInMonth(year, month))
+ {
+ throw new ArgumentOutOfRangeException(nameof(day), SR.Format(SR.ArgumentOutOfRange_Range,
+ 1, GetDaysInMonth(year, month)));
+ }
+ if (!IsLeapYear(year))
+ {
+ return (false);
+ }
+ if (month == 2 && day == 29)
+ {
+ return (true);
+ }
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ if (era != CurrentEra && era != ADEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ if (year < 1 || year > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, 1, MaxYear));
+ }
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ if (era != CurrentEra && era != ADEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ if (year < 1 || year > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, 1, MaxYear));
+ }
+
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.Format(SR.ArgumentOutOfRange_Range,
+ 1, 12));
+ }
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ if (era == CurrentEra || era == ADEra)
+ {
+ if (year >= 1 && year <= MaxYear)
+ {
+ return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
+ }
+
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, 1, MaxYear));
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ if (era == CurrentEra || era == ADEra)
+ {
+ return new DateTime(year, month, day, hour, minute, second, millisecond);
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal override bool TryToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era, out DateTime result)
+ {
+ if (era == CurrentEra || era == ADEra)
+ {
+ return DateTime.TryCreate(year, month, day, hour, minute, second, millisecond, out result);
+ }
+ result = DateTime.MinValue;
+ return false;
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 2029;
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ MaxYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (year > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, 1, MaxYear));
+ }
+ return (base.ToFourDigitYear(year));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendarHelper.cs b/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendarHelper.cs
new file mode 100644
index 0000000000..2842bd3a16
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendarHelper.cs
@@ -0,0 +1,651 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+
+namespace System.Globalization
+{
+ // Gregorian Calendars use Era Info
+ internal class EraInfo
+ {
+ internal int era; // The value of the era.
+ internal long ticks; // The time in ticks when the era starts
+ internal int yearOffset; // The offset to Gregorian year when the era starts.
+ // Gregorian Year = Era Year + yearOffset
+ // Era Year = Gregorian Year - yearOffset
+ internal int minEraYear; // Min year value in this era. Generally, this value is 1, but this may
+ // be affected by the DateTime.MinValue;
+ internal int maxEraYear; // Max year value in this era. (== the year length of the era + 1)
+
+ internal String eraName; // The era name
+ internal String abbrevEraName; // Abbreviated Era Name
+ internal String englishEraName; // English era name
+
+ internal EraInfo(int era, int startYear, int startMonth, int startDay, int yearOffset, int minEraYear, int maxEraYear)
+ {
+ this.era = era;
+ this.yearOffset = yearOffset;
+ this.minEraYear = minEraYear;
+ this.maxEraYear = maxEraYear;
+ this.ticks = new DateTime(startYear, startMonth, startDay).Ticks;
+ }
+
+ internal EraInfo(int era, int startYear, int startMonth, int startDay, int yearOffset, int minEraYear, int maxEraYear,
+ String eraName, String abbrevEraName, String englishEraName)
+ {
+ this.era = era;
+ this.yearOffset = yearOffset;
+ this.minEraYear = minEraYear;
+ this.maxEraYear = maxEraYear;
+ this.ticks = new DateTime(startYear, startMonth, startDay).Ticks;
+ this.eraName = eraName;
+ this.abbrevEraName = abbrevEraName;
+ this.englishEraName = englishEraName;
+ }
+ }
+
+ // This calendar recognizes two era values:
+ // 0 CurrentEra (AD)
+ // 1 BeforeCurrentEra (BC)
+ internal class GregorianCalendarHelper
+ {
+ // 1 tick = 100ns = 10E-7 second
+ // Number of ticks per time unit
+ internal const long TicksPerMillisecond = 10000;
+ internal const long TicksPerSecond = TicksPerMillisecond * 1000;
+ internal const long TicksPerMinute = TicksPerSecond * 60;
+ internal const long TicksPerHour = TicksPerMinute * 60;
+ internal const long TicksPerDay = TicksPerHour * 24;
+
+ // Number of milliseconds per time unit
+ internal const int MillisPerSecond = 1000;
+ internal const int MillisPerMinute = MillisPerSecond * 60;
+ internal const int MillisPerHour = MillisPerMinute * 60;
+ internal const int MillisPerDay = MillisPerHour * 24;
+
+ // Number of days in a non-leap year
+ internal const int DaysPerYear = 365;
+ // Number of days in 4 years
+ internal const int DaysPer4Years = DaysPerYear * 4 + 1;
+ // Number of days in 100 years
+ internal const int DaysPer100Years = DaysPer4Years * 25 - 1;
+ // Number of days in 400 years
+ internal const int DaysPer400Years = DaysPer100Years * 4 + 1;
+
+ // Number of days from 1/1/0001 to 1/1/10000
+ internal const int DaysTo10000 = DaysPer400Years * 25 - 366;
+
+ internal const long MaxMillis = (long)DaysTo10000 * MillisPerDay;
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+ //
+ // This is the max Gregorian year can be represented by DateTime class. The limitation
+ // is derived from DateTime class.
+ //
+ internal int MaxYear
+ {
+ get
+ {
+ return (m_maxYear);
+ }
+ }
+
+ internal static readonly int[] DaysToMonth365 =
+ {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+ };
+
+ internal static readonly int[] DaysToMonth366 =
+ {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
+ };
+
+ internal int m_maxYear = 9999;
+ internal int m_minYear;
+ internal Calendar m_Cal;
+
+ internal EraInfo[] m_EraInfo;
+ internal int[] m_eras = null;
+
+
+ // Construct an instance of gregorian calendar.
+ internal GregorianCalendarHelper(Calendar cal, EraInfo[] eraInfo)
+ {
+ m_Cal = cal;
+ m_EraInfo = eraInfo;
+ m_maxYear = m_EraInfo[0].maxEraYear;
+ m_minYear = m_EraInfo[0].minEraYear; ;
+ }
+
+ /*=================================GetGregorianYear==========================
+ **Action: Get the Gregorian year value for the specified year in an era.
+ **Returns: The Gregorian year value.
+ **Arguments:
+ ** year the year value in Japanese calendar
+ ** era the Japanese emperor era value.
+ **Exceptions:
+ ** ArgumentOutOfRangeException if year value is invalid or era value is invalid.
+ ============================================================================*/
+
+ internal int GetGregorianYear(int year, int era)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (era == Calendar.CurrentEra)
+ {
+ era = m_Cal.CurrentEraValue;
+ }
+
+ for (int i = 0; i < m_EraInfo.Length; i++)
+ {
+ if (era == m_EraInfo[i].era)
+ {
+ if (year < m_EraInfo[i].minEraYear || year > m_EraInfo[i].maxEraYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ m_EraInfo[i].minEraYear,
+ m_EraInfo[i].maxEraYear));
+ }
+ return (m_EraInfo[i].yearOffset + year);
+ }
+ }
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal bool IsValidYear(int year, int era)
+ {
+ if (year < 0)
+ {
+ return false;
+ }
+
+ if (era == Calendar.CurrentEra)
+ {
+ era = m_Cal.CurrentEraValue;
+ }
+
+ for (int i = 0; i < m_EraInfo.Length; i++)
+ {
+ if (era == m_EraInfo[i].era)
+ {
+ if (year < m_EraInfo[i].minEraYear || year > m_EraInfo[i].maxEraYear)
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ // Returns a given date part of this DateTime. This method is used
+ // to compute the year, day-of-year, month, or day part.
+ internal virtual int GetDatePart(long ticks, int part)
+ {
+ CheckTicksRange(ticks);
+ // n = number of days since 1/1/0001
+ int n = (int)(ticks / TicksPerDay);
+ // y400 = number of whole 400-year periods since 1/1/0001
+ int y400 = n / DaysPer400Years;
+ // n = day number within 400-year period
+ n -= y400 * DaysPer400Years;
+ // y100 = number of whole 100-year periods within 400-year period
+ int y100 = n / DaysPer100Years;
+ // Last 100-year period has an extra day, so decrement result if 4
+ if (y100 == 4) y100 = 3;
+ // n = day number within 100-year period
+ n -= y100 * DaysPer100Years;
+ // y4 = number of whole 4-year periods within 100-year period
+ int y4 = n / DaysPer4Years;
+ // n = day number within 4-year period
+ n -= y4 * DaysPer4Years;
+ // y1 = number of whole years within 4-year period
+ int y1 = n / DaysPerYear;
+ // Last year has an extra day, so decrement result if 4
+ if (y1 == 4) y1 = 3;
+ // If year was requested, compute and return it
+ if (part == DatePartYear)
+ {
+ return (y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1);
+ }
+ // n = day number within year
+ n -= y1 * DaysPerYear;
+ // If day-of-year was requested, return it
+ if (part == DatePartDayOfYear)
+ {
+ return (n + 1);
+ }
+ // Leap year calculation looks different from IsLeapYear since y1, y4,
+ // and y100 are relative to year 1, not year 0
+ bool leapYear = (y1 == 3 && (y4 != 24 || y100 == 3));
+ int[] days = leapYear ? DaysToMonth366 : DaysToMonth365;
+ // All months have less than 32 days, so n >> 5 is a good conservative
+ // estimate for the month
+ int m = (n >> 5) + 1;
+ // m = 1-based month number
+ while (n >= days[m]) m++;
+ // If month was requested, return it
+ if (part == DatePartMonth) return (m);
+ // Return 1-based day-of-month
+ return (n - days[m - 1] + 1);
+ }
+
+ /*=================================GetAbsoluteDate==========================
+ **Action: Gets the absolute date for the given Gregorian date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns: the absolute date
+ **Arguments:
+ ** year the Gregorian year
+ ** month the Gregorian month
+ ** day the day
+ **Exceptions:
+ ** ArgumentOutOfRangException if year, month, day value is valid.
+ **Note:
+ ** This is an internal method used by DateToTicks() and the calculations of Hijri and Hebrew calendars.
+ ** Number of Days in Prior Years (both common and leap years) +
+ ** Number of Days in Prior Months of Current Year +
+ ** Number of Days in Current Month
+ **
+ ============================================================================*/
+
+ internal static long GetAbsoluteDate(int year, int month, int day)
+ {
+ if (year >= 1 && year <= 9999 && month >= 1 && month <= 12)
+ {
+ int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) ? DaysToMonth366 : DaysToMonth365;
+ if (day >= 1 && (day <= days[month] - days[month - 1]))
+ {
+ int y = year - 1;
+ int absoluteDate = y * 365 + y / 4 - y / 100 + y / 400 + days[month - 1] + day - 1;
+ return (absoluteDate);
+ }
+ }
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+
+ // Returns the tick count corresponding to the given year, month, and day.
+ // Will check the if the parameters are valid.
+ internal static long DateToTicks(int year, int month, int day)
+ {
+ return (GetAbsoluteDate(year, month, day) * TicksPerDay);
+ }
+
+ // Return the tick count corresponding to the given hour, minute, second.
+ // Will check the if the parameters are valid.
+ internal static long TimeToTicks(int hour, int minute, int second, int millisecond)
+ {
+ //TimeSpan.TimeToTicks is a family access function which does no error checking, so
+ //we need to put some error checking out here.
+ if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60)
+ {
+ if (millisecond < 0 || millisecond >= MillisPerSecond)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(millisecond),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ MillisPerSecond - 1));
+ }
+ return (InternalGlobalizationHelper.TimeToTicks(hour, minute, second) + millisecond * TicksPerMillisecond); ;
+ }
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
+ }
+
+
+ internal void CheckTicksRange(long ticks)
+ {
+ if (ticks < m_Cal.MinSupportedDateTime.Ticks || ticks > m_Cal.MaxSupportedDateTime.Ticks)
+ {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ SR.ArgumentOutOfRange_CalendarRange,
+ m_Cal.MinSupportedDateTime,
+ m_Cal.MaxSupportedDateTime));
+ }
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+ public DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ -120000,
+ 120000));
+ }
+ CheckTicksRange(time.Ticks);
+
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int[] daysArray = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? DaysToMonth366 : DaysToMonth365;
+ int days = (daysArray[m] - daysArray[m - 1]);
+
+ if (d > days)
+ {
+ d = days;
+ }
+ long ticks = DateToTicks(y, m, d) + (time.Ticks % TicksPerDay);
+ Calendar.CheckAddResult(ticks, m_Cal.MinSupportedDateTime, m_Cal.MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+ public DateTime AddYears(DateTime time, int years)
+ {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+ public int GetDayOfMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+ public DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return ((DayOfWeek)((time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+ public int GetDayOfYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+ public int GetDaysInMonth(int year, int month, int era)
+ {
+ //
+ // Convert year/era value to Gregorain year value.
+ //
+ year = GetGregorianYear(year, era);
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+ int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365);
+ return (days[month] - days[month - 1]);
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public int GetDaysInYear(int year, int era)
+ {
+ //
+ // Convert year/era value to Gregorain year value.
+ //
+ year = GetGregorianYear(year, era);
+ return ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 366 : 365);
+ }
+
+ // Returns the era for the specified DateTime value.
+ public int GetEra(DateTime time)
+ {
+ long ticks = time.Ticks;
+ // The assumption here is that m_EraInfo is listed in reverse order.
+ for (int i = 0; i < m_EraInfo.Length; i++)
+ {
+ if (ticks >= m_EraInfo[i].ticks)
+ {
+ return (m_EraInfo[i].era);
+ }
+ }
+ throw new ArgumentOutOfRangeException(nameof(time), SR.ArgumentOutOfRange_Era);
+ }
+
+
+ public int[] Eras
+ {
+ get
+ {
+ if (m_eras == null)
+ {
+ m_eras = new int[m_EraInfo.Length];
+ for (int i = 0; i < m_EraInfo.Length; i++)
+ {
+ m_eras[i] = m_EraInfo[i].era;
+ }
+ }
+ return ((int[])m_eras.Clone());
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+ public int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+ public int GetMonthsInYear(int year, int era)
+ {
+ year = GetGregorianYear(year, era);
+ return (12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+ public int GetYear(DateTime time)
+ {
+ long ticks = time.Ticks;
+ int year = GetDatePart(ticks, DatePartYear);
+ for (int i = 0; i < m_EraInfo.Length; i++)
+ {
+ if (ticks >= m_EraInfo[i].ticks)
+ {
+ return (year - m_EraInfo[i].yearOffset);
+ }
+ }
+ throw new ArgumentException(SR.Argument_NoEra);
+ }
+
+ // Returns the year that match the specified Gregorian year. The returned value is an
+ // integer between 1 and 9999.
+ //
+ public int GetYear(int year, DateTime time)
+ {
+ long ticks = time.Ticks;
+ for (int i = 0; i < m_EraInfo.Length; i++)
+ {
+ // while calculating dates with JapaneseLuniSolarCalendar, we can run into cases right after the start of the era
+ // and still belong to the month which is started in previous era. Calculating equivalent calendar date will cause
+ // using the new era info which will have the year offset equal to the year we are calculating year = m_EraInfo[i].yearOffset
+ // which will end up with zero as calendar year.
+ // We should use the previous era info instead to get the right year number. Example of such date is Feb 2nd 1989
+ if (ticks >= m_EraInfo[i].ticks && year > m_EraInfo[i].yearOffset)
+ {
+ return (year - m_EraInfo[i].yearOffset);
+ }
+ }
+ throw new ArgumentException(SR.Argument_NoEra);
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+ public bool IsLeapDay(int year, int month, int day, int era)
+ {
+ // year/month/era checking is done in GetDaysInMonth()
+ if (day < 1 || day > GetDaysInMonth(year, month, era))
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ GetDaysInMonth(year, month, era)));
+ }
+
+ if (!IsLeapYear(year, era))
+ {
+ return (false);
+ }
+
+ if (month == 2 && day == 29)
+ {
+ return (true);
+ }
+
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+ public int GetLeapMonth(int year, int era)
+ {
+ year = GetGregorianYear(year, era);
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+ public bool IsLeapMonth(int year, int month, int era)
+ {
+ year = GetGregorianYear(year, era);
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(month),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ 12));
+ }
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+ public bool IsLeapYear(int year, int era)
+ {
+ year = GetGregorianYear(year, era);
+ return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+ public DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ year = GetGregorianYear(year, era);
+ long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second, millisecond);
+ CheckTicksRange(ticks);
+ return (new DateTime(ticks));
+ }
+
+ public virtual int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ CheckTicksRange(time.Ticks);
+ // Use GregorianCalendar to get around the problem that the implmentation in Calendar.GetWeekOfYear()
+ // can call GetYear() that exceeds the supported range of the Gregorian-based calendars.
+ return (GregorianCalendar.GetDefaultInstance().GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+
+ public int ToFourDigitYear(int year, int twoDigitYearMax)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedPosNum);
+ }
+
+ if (year < 100)
+ {
+ int y = year % 100;
+ return ((twoDigitYearMax / 100 - (y > twoDigitYearMax % 100 ? 1 : 0)) * 100 + y);
+ }
+
+ if (year < m_minYear || year > m_maxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, m_minYear, m_maxYear));
+ }
+ // If the year value is above 100, just return the year value. Don't have to do
+ // the TwoDigitYearMax comparison.
+ return (year);
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendarTypes.cs b/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendarTypes.cs
new file mode 100644
index 0000000000..46f13b00e0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/GregorianCalendarTypes.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ // Note: The values of the members of this enum must match the coresponding values
+ // in the CalendarId enum (since we cast between GregorianCalendarTypes and CalendarId).
+ public enum GregorianCalendarTypes
+ {
+ Localized = CalendarId.GREGORIAN,
+ USEnglish = CalendarId.GREGORIAN_US,
+ MiddleEastFrench = CalendarId.GREGORIAN_ME_FRENCH,
+ Arabic = CalendarId.GREGORIAN_ARABIC,
+ TransliteratedEnglish = CalendarId.GREGORIAN_XLIT_ENGLISH,
+ TransliteratedFrench = CalendarId.GREGORIAN_XLIT_FRENCH,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/HebrewCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/HebrewCalendar.cs
new file mode 100644
index 0000000000..533d3831bf
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/HebrewCalendar.cs
@@ -0,0 +1,1124 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Rules for the Hebrew calendar:
+ // - The Hebrew calendar is both a Lunar (months) and Solar (years)
+ // calendar, but allows for a week of seven days.
+ // - Days begin at sunset.
+ // - Leap Years occur in the 3, 6, 8, 11, 14, 17, & 19th years of a
+ // 19-year cycle. Year = leap iff ((7y+1) mod 19 < 7).
+ // - There are 12 months in a common year and 13 months in a leap year.
+ // - In a common year, the 6th month, Adar, has 29 days. In a leap
+ // year, the 6th month, Adar I, has 30 days and the leap month,
+ // Adar II, has 29 days.
+ // - Common years have 353-355 days. Leap years have 383-385 days.
+ // - The Hebrew new year (Rosh HaShanah) begins on the 1st of Tishri,
+ // the 7th month in the list below.
+ // - The new year may not begin on Sunday, Wednesday, or Friday.
+ // - If the new year would fall on a Tuesday and the conjunction of
+ // the following year were at midday or later, the new year is
+ // delayed until Thursday.
+ // - If the new year would fall on a Monday after a leap year, the
+ // new year is delayed until Tuesday.
+ // - The length of the 8th and 9th months vary from year to year,
+ // depending on the overall length of the year.
+ // - The length of a year is determined by the dates of the new
+ // years (Tishri 1) preceding and following the year in question.
+ // - The 2th month is long (30 days) if the year has 355 or 385 days.
+ // - The 3th month is short (29 days) if the year has 353 or 383 days.
+ // - The Hebrew months are:
+ // 1. Tishri (30 days)
+ // 2. Heshvan (29 or 30 days)
+ // 3. Kislev (29 or 30 days)
+ // 4. Teveth (29 days)
+ // 5. Shevat (30 days)
+ // 6. Adar I (30 days)
+ // 7. Adar {II} (29 days, this only exists if that year is a leap year)
+ // 8. Nisan (30 days)
+ // 9. Iyyar (29 days)
+ // 10. Sivan (30 days)
+ // 11. Tammuz (29 days)
+ // 12. Av (30 days)
+ // 13. Elul (29 days)
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1583/01/01 2239/09/29
+ ** Hebrew 5343/04/07 5999/13/29
+ */
+
+ // Includes CHebrew implemetation;i.e All the code necessary for converting
+ // Gregorian to Hebrew Lunar from 1583 to 2239.
+
+
+ public class HebrewCalendar : Calendar
+ {
+ public static readonly int HebrewEra = 1;
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+ internal const int DatePartDayOfWeek = 4;
+
+ //
+ // Hebrew Translation Table.
+ //
+ // This table is used to get the following Hebrew calendar information for a
+ // given Gregorian year:
+ // 1. The day of the Hebrew month corresponding to Gregorian January 1st
+ // for a given Gregorian year.
+ // 2. The month of the Hebrew month corresponding to Gregorian January 1st
+ // for a given Gregorian year.
+ // The information is not directly in the table. Instead, the info is decoded
+ // by special values (numbers above 29 and below 1).
+ // 3. The type of the Hebrew year for a given Gregorian year.
+ //
+
+ /*
+ More notes:
+
+ This table includes 2 numbers for each year.
+ The offset into the table determines the year. (offset 0 is Gregorian year 1500)
+ 1st number determines the day of the Hebrew month coresponeds to January 1st.
+ 2nd number determines the type of the Hebrew year. (the type determines how
+ many days are there in the year.)
+
+ normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
+ Leap years : 4 = 383 5 384 6 = 385 days.
+
+ A 99 means the year is not supported for translation.
+ for convenience the table was defined for 750 year,
+ but only 640 years are supported. (from 1583 to 2239)
+ the years before 1582 (starting of Georgian calander)
+ and after 2239, are filled with 99.
+
+ Greogrian January 1st falls usually in Tevet (4th month). Tevet has always 29 days.
+ That's why, there no nead to specify the lunar month in the table.
+ There are exceptions, these are coded by giving numbers above 29 and below 1.
+ Actual decoding is takenig place whenever fetching information from the table.
+ The function for decoding is in GetLunarMonthDay().
+
+ Example:
+ The data for 2000 - 2005 A.D. is:
+
+ 23,6,6,1,17,2,27,6,7,3, // 2000 - 2004
+
+ For year 2000, we know it has a Hebrew year type 6, which means it has 385 days.
+ And 1/1/2000 A.D. is Hebrew year 5760, 23rd day of 4th month.
+ */
+
+ //
+ // Jewish Era in use today is dated from the supposed year of the
+ // Creation with its beginning in 3761 B.C.
+ //
+
+ // The Hebrew year of Gregorian 1st year AD.
+ // 0001/01/01 AD is Hebrew 3760/01/01
+ private const int HebrewYearOf1AD = 3760;
+
+ // The first Gregorian year in HebrewTable.
+ private const int FirstGregorianTableYear = 1583; // == Hebrew Year 5343
+ // The last Gregorian year in HebrewTable.
+ private const int LastGregorianTableYear = 2239; // == Hebrew Year 5999
+ private const int TABLESIZE = (LastGregorianTableYear - FirstGregorianTableYear);
+
+ private const int MinHebrewYear = HebrewYearOf1AD + FirstGregorianTableYear; // == 5343
+ private const int MaxHebrewYear = HebrewYearOf1AD + LastGregorianTableYear; // == 5999
+
+ private static readonly byte[] s_hebrewTable = {
+ 7,3,17,3, // 1583-1584 (Hebrew year: 5343 - 5344)
+ 0,4,11,2,21,6,1,3,13,2, // 1585-1589
+ 25,4,5,3,16,2,27,6,9,1, // 1590-1594
+ 20,2,0,6,11,3,23,4,4,2, // 1595-1599
+ 14,3,27,4,8,2,18,3,28,6, // 1600
+ 11,1,22,5,2,3,12,3,25,4, // 1605
+ 6,2,16,3,26,6,8,2,20,1, // 1610
+ 0,6,11,2,24,4,4,3,15,2, // 1615
+ 25,6,8,1,19,2,29,6,9,3, // 1620
+ 22,4,3,2,13,3,25,4,6,3, // 1625
+ 17,2,27,6,7,3,19,2,31,4, // 1630
+ 11,3,23,4,5,2,15,3,25,6, // 1635
+ 6,2,19,1,29,6,10,2,22,4, // 1640
+ 3,3,14,2,24,6,6,1,17,3, // 1645
+ 28,5,8,3,20,1,32,5,12,3, // 1650
+ 22,6,4,1,16,2,26,6,6,3, // 1655
+ 17,2,0,4,10,3,22,4,3,2, // 1660
+ 14,3,24,6,5,2,17,1,28,6, // 1665
+ 9,2,19,3,31,4,13,2,23,6, // 1670
+ 3,3,15,1,27,5,7,3,17,3, // 1675
+ 29,4,11,2,21,6,3,1,14,2, // 1680
+ 25,6,5,3,16,2,28,4,9,3, // 1685
+ 20,2,0,6,12,1,23,6,4,2, // 1690
+ 14,3,26,4,8,2,18,3,0,4, // 1695
+ 10,3,21,5,1,3,13,1,24,5, // 1700
+ 5,3,15,3,27,4,8,2,19,3, // 1705
+ 29,6,10,2,22,4,3,3,14,2, // 1710
+ 26,4,6,3,18,2,28,6,10,1, // 1715
+ 20,6,2,2,12,3,24,4,5,2, // 1720
+ 16,3,28,4,8,3,19,2,0,6, // 1725
+ 12,1,23,5,3,3,14,3,26,4, // 1730
+ 7,2,17,3,28,6,9,2,21,4, // 1735
+ 1,3,13,2,25,4,5,3,16,2, // 1740
+ 27,6,9,1,19,3,0,5,11,3, // 1745
+ 23,4,4,2,14,3,25,6,7,1, // 1750
+ 18,2,28,6,9,3,21,4,2,2, // 1755
+ 12,3,25,4,6,2,16,3,26,6, // 1760
+ 8,2,20,1,0,6,11,2,22,6, // 1765
+ 4,1,15,2,25,6,6,3,18,1, // 1770
+ 29,5,9,3,22,4,2,3,13,2, // 1775
+ 23,6,4,3,15,2,27,4,7,3, // 1780
+ 19,2,31,4,11,3,21,6,3,2, // 1785
+ 15,1,25,6,6,2,17,3,29,4, // 1790
+ 10,2,20,6,3,1,13,3,24,5, // 1795
+ 4,3,16,1,27,5,7,3,17,3, // 1800
+ 0,4,11,2,21,6,1,3,13,2, // 1805
+ 25,4,5,3,16,2,29,4,9,3, // 1810
+ 19,6,30,2,13,1,23,6,4,2, // 1815
+ 14,3,27,4,8,2,18,3,0,4, // 1820
+ 11,3,22,5,2,3,14,1,26,5, // 1825
+ 6,3,16,3,28,4,10,2,20,6, // 1830
+ 30,3,11,2,24,4,4,3,15,2, // 1835
+ 25,6,8,1,19,2,29,6,9,3, // 1840
+ 22,4,3,2,13,3,25,4,7,2, // 1845
+ 17,3,27,6,9,1,21,5,1,3, // 1850
+ 11,3,23,4,5,2,15,3,25,6, // 1855
+ 6,2,19,1,29,6,10,2,22,4, // 1860
+ 3,3,14,2,24,6,6,1,18,2, // 1865
+ 28,6,8,3,20,4,2,2,12,3, // 1870
+ 24,4,4,3,16,2,26,6,6,3, // 1875
+ 17,2,0,4,10,3,22,4,3,2, // 1880
+ 14,3,24,6,5,2,17,1,28,6, // 1885
+ 9,2,21,4,1,3,13,2,23,6, // 1890
+ 5,1,15,3,27,5,7,3,19,1, // 1895
+ 0,5,10,3,22,4,2,3,13,2, // 1900
+ 24,6,4,3,15,2,27,4,8,3, // 1905
+ 20,4,1,2,11,3,22,6,3,2, // 1910
+ 15,1,25,6,7,2,17,3,29,4, // 1915
+ 10,2,21,6,1,3,13,1,24,5, // 1920
+ 5,3,15,3,27,4,8,2,19,6, // 1925
+ 1,1,12,2,22,6,3,3,14,2, // 1930
+ 26,4,6,3,18,2,28,6,10,1, // 1935
+ 20,6,2,2,12,3,24,4,5,2, // 1940
+ 16,3,28,4,9,2,19,6,30,3, // 1945
+ 12,1,23,5,3,3,14,3,26,4, // 1950
+ 7,2,17,3,28,6,9,2,21,4, // 1955
+ 1,3,13,2,25,4,5,3,16,2, // 1960
+ 27,6,9,1,19,6,30,2,11,3, // 1965
+ 23,4,4,2,14,3,27,4,7,3, // 1970
+ 18,2,28,6,11,1,22,5,2,3, // 1975
+ 12,3,25,4,6,2,16,3,26,6, // 1980
+ 8,2,20,4,30,3,11,2,24,4, // 1985
+ 4,3,15,2,25,6,8,1,18,3, // 1990
+ 29,5,9,3,22,4,3,2,13,3, // 1995
+ 23,6,6,1,17,2,27,6,7,3, // 2000 - 2004
+ 20,4,1,2,11,3,23,4,5,2, // 2005 - 2009
+ 15,3,25,6,6,2,19,1,29,6, // 2010
+ 10,2,20,6,3,1,14,2,24,6, // 2015
+ 4,3,17,1,28,5,8,3,20,4, // 2020
+ 1,3,12,2,22,6,2,3,14,2, // 2025
+ 26,4,6,3,17,2,0,4,10,3, // 2030
+ 20,6,1,2,14,1,24,6,5,2, // 2035
+ 15,3,28,4,9,2,19,6,1,1, // 2040
+ 12,3,23,5,3,3,15,1,27,5, // 2045
+ 7,3,17,3,29,4,11,2,21,6, // 2050
+ 1,3,12,2,25,4,5,3,16,2, // 2055
+ 28,4,9,3,19,6,30,2,12,1, // 2060
+ 23,6,4,2,14,3,26,4,8,2, // 2065
+ 18,3,0,4,10,3,22,5,2,3, // 2070
+ 14,1,25,5,6,3,16,3,28,4, // 2075
+ 9,2,20,6,30,3,11,2,23,4, // 2080
+ 4,3,15,2,27,4,7,3,19,2, // 2085
+ 29,6,11,1,21,6,3,2,13,3, // 2090
+ 25,4,6,2,17,3,27,6,9,1, // 2095
+ 20,5,30,3,10,3,22,4,3,2, // 2100
+ 14,3,24,6,5,2,17,1,28,6, // 2105
+ 9,2,21,4,1,3,13,2,23,6, // 2110
+ 5,1,16,2,27,6,7,3,19,4, // 2115
+ 30,2,11,3,23,4,3,3,14,2, // 2120
+ 25,6,5,3,16,2,28,4,9,3, // 2125
+ 21,4,2,2,12,3,23,6,4,2, // 2130
+ 16,1,26,6,8,2,20,4,30,3, // 2135
+ 11,2,22,6,4,1,14,3,25,5, // 2140
+ 6,3,18,1,29,5,9,3,22,4, // 2145
+ 2,3,13,2,23,6,4,3,15,2, // 2150
+ 27,4,7,3,20,4,1,2,11,3, // 2155
+ 21,6,3,2,15,1,25,6,6,2, // 2160
+ 17,3,29,4,10,2,20,6,3,1, // 2165
+ 13,3,24,5,4,3,17,1,28,5, // 2170
+ 8,3,18,6,1,1,12,2,22,6, // 2175
+ 2,3,14,2,26,4,6,3,17,2, // 2180
+ 28,6,10,1,20,6,1,2,12,3, // 2185
+ 24,4,5,2,15,3,28,4,9,2, // 2190
+ 19,6,33,3,12,1,23,5,3,3, // 2195
+ 13,3,25,4,6,2,16,3,26,6, // 2200
+ 8,2,20,4,30,3,11,2,24,4, // 2205
+ 4,3,15,2,25,6,8,1,18,6, // 2210
+ 33,2,9,3,22,4,3,2,13,3, // 2215
+ 25,4,6,3,17,2,27,6,9,1, // 2220
+ 21,5,1,3,11,3,23,4,5,2, // 2225
+ 15,3,25,6,6,2,19,4,33,3, // 2230
+ 10,2,22,4,3,3,14,2,24,6, // 2235
+ 6,1 // 2240 (Hebrew year: 6000)
+ };
+
+ private const int MaxMonthPlusOne = 14;
+
+ //
+ // The lunar calendar has 6 different variations of month lengths
+ // within a year.
+ //
+ private static readonly byte[] s_lunarMonthLen = {
+ 0,00,00,00,00,00,00,00,00,00,00,00,00,0,
+ 0,30,29,29,29,30,29,30,29,30,29,30,29,0, // 3 common year variations
+ 0,30,29,30,29,30,29,30,29,30,29,30,29,0,
+ 0,30,30,30,29,30,29,30,29,30,29,30,29,0,
+ 0,30,29,29,29,30,30,29,30,29,30,29,30,29, // 3 leap year variations
+ 0,30,29,30,29,30,30,29,30,29,30,29,30,29,
+ 0,30,30,30,29,30,30,29,30,29,30,29,30,29
+ };
+
+ internal static readonly DateTime calendarMinValue = new DateTime(1583, 1, 1);
+ // Gregorian 2239/9/29 = Hebrew 5999/13/29 (last day in Hebrew year 5999).
+ // We can only format/parse Hebrew numbers up to 999, so we limit the max range to Hebrew year 5999.
+ internal static readonly DateTime calendarMaxValue = new DateTime((new DateTime(2239, 9, 29, 23, 59, 59, 999)).Ticks + 9999);
+
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (calendarMaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.LunisolarCalendar;
+ }
+ }
+
+ public HebrewCalendar()
+ {
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.HEBREW);
+ }
+ }
+
+
+ /*=================================CheckHebrewYearValue==========================
+ **Action: Check if the Hebrew year value is supported in this class.
+ **Returns: None.
+ **Arguments: y Hebrew year value
+ ** ear Hebrew era value
+ **Exceptions: ArgumentOutOfRange_Range if the year value is not supported.
+ **Note:
+ ** We use a table for the Hebrew calendar calculation, so the year supported is limited.
+ ============================================================================*/
+
+ private static void CheckHebrewYearValue(int y, int era, String varName)
+ {
+ CheckEraRange(era);
+ if (y > MaxHebrewYear || y < MinHebrewYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ varName,
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MinHebrewYear,
+ MaxHebrewYear));
+ }
+ }
+
+ /*=================================CheckHebrewMonthValue==========================
+ **Action: Check if the Hebrew month value is valid.
+ **Returns: None.
+ **Arguments: year Hebrew year value
+ ** month Hebrew month value
+ **Exceptions: ArgumentOutOfRange_Range if the month value is not valid.
+ **Note:
+ ** Call CheckHebrewYearValue() before calling this to verify the year value is supported.
+ ============================================================================*/
+
+ private void CheckHebrewMonthValue(int year, int month, int era)
+ {
+ int monthsInYear = GetMonthsInYear(year, era);
+ if (month < 1 || month > monthsInYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(month),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ monthsInYear));
+ }
+ }
+
+ /*=================================CheckHebrewDayValue==========================
+ **Action: Check if the Hebrew day value is valid.
+ **Returns: None.
+ **Arguments: year Hebrew year value
+ ** month Hebrew month value
+ ** day Hebrew day value.
+ **Exceptions: ArgumentOutOfRange_Range if the day value is not valid.
+ **Note:
+ ** Call CheckHebrewYearValue()/CheckHebrewMonthValue() before calling this to verify the year/month values are valid.
+ ============================================================================*/
+
+ private void CheckHebrewDayValue(int year, int month, int day, int era)
+ {
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ daysInMonth));
+ }
+ }
+
+ internal static void CheckEraRange(int era)
+ {
+ if (era != CurrentEra && era != HebrewEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ private static void CheckTicksRange(long ticks)
+ {
+ if (ticks < calendarMinValue.Ticks || ticks > calendarMaxValue.Ticks)
+ {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ // Print out the date in Gregorian using InvariantCulture since the DateTime is based on GreograinCalendar.
+ String.Format(
+ CultureInfo.InvariantCulture,
+ SR.ArgumentOutOfRange_CalendarRange,
+ calendarMinValue,
+ calendarMaxValue));
+ }
+ }
+
+ internal static int GetResult(__DateBuffer result, int part)
+ {
+ switch (part)
+ {
+ case DatePartYear:
+ return (result.year);
+ case DatePartMonth:
+ return (result.month);
+ case DatePartDay:
+ return (result.day);
+ }
+
+ throw new InvalidOperationException(SR.InvalidOperation_DateTimeParsing);
+ }
+
+ /*=================================GetLunarMonthDay==========================
+ **Action: Using the Hebrew table (HebrewTable) to get the Hebrew month/day value for Gregorian January 1st
+ ** in a given Gregorian year.
+ ** Greogrian January 1st falls usually in Tevet (4th month). Tevet has always 29 days.
+ ** That's why, there no nead to specify the lunar month in the table. There are exceptions, and these
+ ** are coded by giving numbers above 29 and below 1.
+ ** Actual decoding is takenig place in the switch statement below.
+ **Returns:
+ ** The Hebrew year type. The value is from 1 to 6.
+ ** normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
+ ** Leap years : 4 = 383 5 384 6 = 385 days.
+ **Arguments:
+ ** gregorianYear The year value in Gregorian calendar. The value should be between 1500 and 2239.
+ ** lunarDate Object to take the result of the Hebrew year/month/day.
+ **Exceptions:
+ ============================================================================*/
+
+ internal static int GetLunarMonthDay(int gregorianYear, __DateBuffer lunarDate)
+ {
+ //
+ // Get the offset into the LunarMonthLen array and the lunar day
+ // for January 1st.
+ //
+ int index = gregorianYear - FirstGregorianTableYear;
+ if (index < 0 || index > TABLESIZE)
+ {
+ throw new ArgumentOutOfRangeException(nameof(gregorianYear));
+ }
+
+ index *= 2;
+ lunarDate.day = s_hebrewTable[index];
+
+ // Get the type of the year. The value is from 1 to 6
+ int LunarYearType = s_hebrewTable[index + 1];
+
+ //
+ // Get the Lunar Month.
+ //
+ switch (lunarDate.day)
+ {
+ case (0): // 1/1 is on Shvat 1
+ lunarDate.month = 5;
+ lunarDate.day = 1;
+ break;
+ case (30): // 1/1 is on Kislev 30
+ lunarDate.month = 3;
+ break;
+ case (31): // 1/1 is on Shvat 2
+ lunarDate.month = 5;
+ lunarDate.day = 2;
+ break;
+ case (32): // 1/1 is on Shvat 3
+ lunarDate.month = 5;
+ lunarDate.day = 3;
+ break;
+ case (33): // 1/1 is on Kislev 29
+ lunarDate.month = 3;
+ lunarDate.day = 29;
+ break;
+ default: // 1/1 is on Tevet (This is the general case)
+ lunarDate.month = 4;
+ break;
+ }
+ return (LunarYearType);
+ }
+
+ // Returns a given date part of this DateTime. This method is used
+ // to compute the year, day-of-year, month, or day part.
+
+ internal virtual int GetDatePart(long ticks, int part)
+ {
+ // The Gregorian year, month, day value for ticks.
+ int gregorianYear, gregorianMonth, gregorianDay;
+ int hebrewYearType; // lunar year type
+ long AbsoluteDate; // absolute date - absolute date 1/1/1600
+
+ //
+ // Make sure we have a valid Gregorian date that will fit into our
+ // Hebrew conversion limits.
+ //
+ CheckTicksRange(ticks);
+
+ DateTime time = new DateTime(ticks);
+
+ //
+ // Save the Gregorian date values.
+ //
+ time.GetDatePart(out gregorianYear, out gregorianMonth, out gregorianDay);
+
+ __DateBuffer lunarDate = new __DateBuffer(); // lunar month and day for Jan 1
+
+ // From the table looking-up value of HebrewTable[index] (stored in lunarDate.day), we get the the
+ // lunar month and lunar day where the Gregorian date 1/1 falls.
+ lunarDate.year = gregorianYear + HebrewYearOf1AD;
+ hebrewYearType = GetLunarMonthDay(gregorianYear, lunarDate);
+
+ // This is the buffer used to store the result Hebrew date.
+ __DateBuffer result = new __DateBuffer();
+
+ //
+ // Store the values for the start of the new year - 1/1.
+ //
+ result.year = lunarDate.year;
+ result.month = lunarDate.month;
+ result.day = lunarDate.day;
+
+ //
+ // Get the absolute date from 1/1/1600.
+ //
+ AbsoluteDate = GregorianCalendar.GetAbsoluteDate(gregorianYear, gregorianMonth, gregorianDay);
+
+ //
+ // If the requested date was 1/1, then we're done.
+ //
+ if ((gregorianMonth == 1) && (gregorianDay == 1))
+ {
+ return (GetResult(result, part));
+ }
+
+ //
+ // Calculate the number of days between 1/1 and the requested date.
+ //
+ long NumDays; // number of days since 1/1
+ NumDays = AbsoluteDate - GregorianCalendar.GetAbsoluteDate(gregorianYear, 1, 1);
+
+ //
+ // If the requested date is within the current lunar month, then
+ // we're done.
+ //
+ if ((NumDays + (long)lunarDate.day) <= (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + lunarDate.month]))
+ {
+ result.day += (int)NumDays;
+ return (GetResult(result, part));
+ }
+
+ //
+ // Adjust for the current partial month.
+ //
+ result.month++;
+ result.day = 1;
+
+ //
+ // Adjust the Lunar Month and Year (if necessary) based on the number
+ // of days between 1/1 and the requested date.
+ //
+ // Assumes Jan 1 can never translate to the last Lunar month, which
+ // is true.
+ //
+ NumDays -= (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + lunarDate.month] - lunarDate.day);
+ Debug.Assert(NumDays >= 1, "NumDays >= 1");
+
+ // If NumDays is 1, then we are done. Otherwise, find the correct Hebrew month
+ // and day.
+ if (NumDays > 1)
+ {
+ //
+ // See if we're on the correct Lunar month.
+ //
+ while (NumDays > (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month]))
+ {
+ //
+ // Adjust the number of days and move to the next month.
+ //
+ NumDays -= (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month++]);
+
+ //
+ // See if we need to adjust the Year.
+ // Must handle both 12 and 13 month years.
+ //
+ if ((result.month > 13) || (s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month] == 0))
+ {
+ //
+ // Adjust the Year.
+ //
+ result.year++;
+ hebrewYearType = s_hebrewTable[(gregorianYear + 1 - FirstGregorianTableYear) * 2 + 1];
+
+ //
+ // Adjust the Month.
+ //
+ result.month = 1;
+ }
+ }
+ //
+ // Found the right Lunar month.
+ //
+ result.day += (int)(NumDays - 1);
+ }
+ return (GetResult(result, part));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ try
+ {
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+
+
+ int monthsInYear;
+ int i;
+ if (months >= 0)
+ {
+ i = m + months;
+ while (i > (monthsInYear = GetMonthsInYear(y, CurrentEra)))
+ {
+ y++;
+ i -= monthsInYear;
+ }
+ }
+ else
+ {
+ if ((i = m + months) <= 0)
+ {
+ months = -months;
+ months -= m;
+ y--;
+
+ while (months > (monthsInYear = GetMonthsInYear(y, CurrentEra)))
+ {
+ y--;
+ months -= monthsInYear;
+ }
+ monthsInYear = GetMonthsInYear(y, CurrentEra);
+ i = monthsInYear - months;
+ }
+ }
+
+ int days = GetDaysInMonth(y, i);
+ if (d > days)
+ {
+ d = days;
+ }
+ return (new DateTime(ToDateTime(y, i, d, 0, 0, 0, 0).Ticks + (time.Ticks % TicksPerDay)));
+ }
+ // We expect ArgumentException and ArgumentOutOfRangeException (which is subclass of ArgumentException)
+ // If exception is thrown in the calls above, we are out of the supported range of this calendar.
+ catch (ArgumentException)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_AddValue));
+ }
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+
+ y += years;
+ CheckHebrewYearValue(y, Calendar.CurrentEra, nameof(years));
+
+ int months = GetMonthsInYear(y, CurrentEra);
+ if (m > months)
+ {
+ m = months;
+ }
+
+ int days = GetDaysInMonth(y, m);
+ if (d > days)
+ {
+ d = days;
+ }
+
+ long ticks = ToDateTime(y, m, d, 0, 0, 0, 0).Ticks + (time.Ticks % TicksPerDay);
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ // If we calculate back, the Hebrew day of week for Gregorian 0001/1/1 is Monday (1).
+ // Therfore, the fomula is:
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ internal static int GetHebrewYearType(int year, int era)
+ {
+ CheckHebrewYearValue(year, era, nameof(year));
+ // The HebrewTable is indexed by Gregorian year and starts from FirstGregorianYear.
+ // So we need to convert year (Hebrew year value) to Gregorian Year below.
+ return (s_hebrewTable[(year - HebrewYearOf1AD - FirstGregorianTableYear) * 2 + 1]);
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ // Get Hebrew year value of the specified time.
+ int year = GetYear(time);
+ DateTime beginOfYearDate;
+ if (year == 5343)
+ {
+ // Gregorian 1583/01/01 corresponds to Hebrew 5343/04/07 (MinSupportedDateTime)
+ // To figure out the Gregorian date associated with Hebrew 5343/01/01, we need to
+ // count the days from 5343/01/01 to 5343/04/07 and subtract that from Gregorian
+ // 1583/01/01.
+ // 1. Tishri (30 days)
+ // 2. Heshvan (30 days since 5343 has 355 days)
+ // 3. Kislev (30 days since 5343 has 355 days)
+ // 96 days to get from 5343/01/01 to 5343/04/07
+ // Gregorian 1583/01/01 - 96 days = 1582/9/27
+
+ // the beginning of Hebrew year 5343 corresponds to Gregorian September 27, 1582.
+ beginOfYearDate = new DateTime(1582, 9, 27);
+ }
+ else
+ {
+ // following line will fail when year is 5343 (first supported year)
+ beginOfYearDate = ToDateTime(year, 1, 1, 0, 0, 0, 0, CurrentEra);
+ }
+ return ((int)((time.Ticks - beginOfYearDate.Ticks) / TicksPerDay) + 1);
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ CheckEraRange(era);
+ int hebrewYearType = GetHebrewYearType(year, era);
+ CheckHebrewMonthValue(year, month, era);
+
+ Debug.Assert(hebrewYearType >= 1 && hebrewYearType <= 6,
+ "hebrewYearType should be from 1 to 6, but now hebrewYearType = " + hebrewYearType + " for hebrew year " + year);
+ int monthDays = s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + month];
+ if (monthDays == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+ return (monthDays);
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ CheckEraRange(era);
+ // normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
+ // Leap years : 4 = 383 5 384 6 = 385 days.
+
+ // LunarYearType is from 1 to 6
+ int LunarYearType = GetHebrewYearType(year, era);
+ if (LunarYearType < 4)
+ {
+ // common year: LunarYearType = 1, 2, 3
+ return (352 + LunarYearType);
+ }
+ return (382 + (LunarYearType - 3));
+ }
+
+ // Returns the era for the specified DateTime value.
+
+ public override int GetEra(DateTime time)
+ {
+ return (HebrewEra);
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { HebrewEra });
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+ public override int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ return (IsLeapYear(year, era) ? 13 : 12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+
+ public override int GetYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ if (IsLeapMonth(year, month, era))
+ {
+ // Every day in a leap month is a leap day.
+ CheckHebrewDayValue(year, month, day, era);
+ return (true);
+ }
+ else if (IsLeapYear(year, Calendar.CurrentEra))
+ {
+ // There is an additional day in the 6th month in the leap year (the extra day is the 30th day in the 6th month),
+ // so we should return true for 6/30 if that's in a leap year.
+ if (month == 6 && day == 30)
+ {
+ return (true);
+ }
+ }
+ CheckHebrewDayValue(year, month, day, era);
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ // Year/era values are checked in IsLeapYear().
+ if (IsLeapYear(year, era))
+ {
+ // The 7th month in a leap year is a leap month.
+ return (7);
+ }
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ // Year/era values are checked in IsLeapYear().
+ bool isLeapYear = IsLeapYear(year, era);
+ CheckHebrewMonthValue(year, month, era);
+ // The 7th month in a leap year is a leap month.
+ if (isLeapYear)
+ {
+ if (month == 7)
+ {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ CheckHebrewYearValue(year, era, nameof(year));
+ return (((7 * (long)year + 1) % 19) < 7);
+ }
+
+ // (month1, day1) - (month2, day2)
+ private static int GetDayDifference(int lunarYearType, int month1, int day1, int month2, int day2)
+ {
+ if (month1 == month2)
+ {
+ return (day1 - day2);
+ }
+
+ // Make sure that (month1, day1) < (month2, day2)
+ bool swap = (month1 > month2);
+ if (swap)
+ {
+ // (month1, day1) < (month2, day2). Swap the values.
+ // The result will be a negative number.
+ int tempMonth, tempDay;
+ tempMonth = month1; tempDay = day1;
+ month1 = month2; day1 = day2;
+ month2 = tempMonth; day2 = tempDay;
+ }
+
+ // Get the number of days from (month1,day1) to (month1, end of month1)
+ int days = s_lunarMonthLen[lunarYearType * MaxMonthPlusOne + month1] - day1;
+
+ // Move to next month.
+ month1++;
+
+ // Add up the days.
+ while (month1 < month2)
+ {
+ days += s_lunarMonthLen[lunarYearType * MaxMonthPlusOne + month1++];
+ }
+ days += day2;
+
+ return (swap ? days : -days);
+ }
+
+ /*=================================HebrewToGregorian==========================
+ **Action: Convert Hebrew date to Gregorian date.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ** The algorithm is like this:
+ ** The hebrew year has an offset to the Gregorian year, so we can guess the Gregorian year for
+ ** the specified Hebrew year. That is, GreogrianYear = HebrewYear - FirstHebrewYearOf1AD.
+ **
+ ** From the Gregorian year and HebrewTable, we can get the Hebrew month/day value
+ ** of the Gregorian date January 1st. Let's call this month/day value [hebrewDateForJan1]
+ **
+ ** If the requested Hebrew month/day is less than [hebrewDateForJan1], we know the result
+ ** Gregorian date falls in previous year. So we decrease the Gregorian year value, and
+ ** retrieve the Hebrew month/day value of the Gregorian date january 1st again.
+ **
+ ** Now, we get the answer of the Gregorian year.
+ **
+ ** The next step is to get the number of days between the requested Hebrew month/day
+ ** and [hebrewDateForJan1]. When we get that, we can create the DateTime by adding/subtracting
+ ** the ticks value of the number of days.
+ **
+ ============================================================================*/
+
+
+ private static DateTime HebrewToGregorian(int hebrewYear, int hebrewMonth, int hebrewDay, int hour, int minute, int second, int millisecond)
+ {
+ // Get the rough Gregorian year for the specified hebrewYear.
+ //
+ int gregorianYear = hebrewYear - HebrewYearOf1AD;
+
+ __DateBuffer hebrewDateOfJan1 = new __DateBuffer(); // year value is unused.
+ int lunarYearType = GetLunarMonthDay(gregorianYear, hebrewDateOfJan1);
+
+ if ((hebrewMonth == hebrewDateOfJan1.month) && (hebrewDay == hebrewDateOfJan1.day))
+ {
+ return (new DateTime(gregorianYear, 1, 1, hour, minute, second, millisecond));
+ }
+
+ int days = GetDayDifference(lunarYearType, hebrewMonth, hebrewDay, hebrewDateOfJan1.month, hebrewDateOfJan1.day);
+
+ DateTime gregorianNewYear = new DateTime(gregorianYear, 1, 1);
+ return (new DateTime(gregorianNewYear.Ticks + days * TicksPerDay
+ + TimeToTicks(hour, minute, second, millisecond)));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ CheckHebrewYearValue(year, era, nameof(year));
+ CheckHebrewMonthValue(year, month, era);
+ CheckHebrewDayValue(year, month, day, era);
+ DateTime dt = HebrewToGregorian(year, month, day, hour, minute, second, millisecond);
+ CheckTicksRange(dt.Ticks);
+ return (dt);
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 5790;
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value == 99)
+ {
+ // Do nothing here. Year 99 is allowed so that TwoDitYearMax is disabled.
+ }
+ else
+ {
+ CheckHebrewYearValue(value, HebrewEra, nameof(value));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (year < 100)
+ {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if (year > MaxHebrewYear || year < MinHebrewYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MinHebrewYear,
+ MaxHebrewYear));
+ }
+ return (year);
+ }
+
+ internal class __DateBuffer
+ {
+ internal int year;
+ internal int month;
+ internal int day;
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/HebrewNumber.cs b/src/System.Private.CoreLib/shared/System/Globalization/HebrewNumber.cs
new file mode 100644
index 0000000000..1e8fff2bcb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/HebrewNumber.cs
@@ -0,0 +1,457 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Used in HebrewNumber.ParseByChar to maintain the context information (
+ // the state in the state machine and current Hebrew number values, etc.)
+ // when parsing Hebrew number character by character.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal struct HebrewNumberParsingContext
+ {
+ // The current state of the state machine for parsing Hebrew numbers.
+ internal HebrewNumber.HS state;
+ // The current value of the Hebrew number.
+ // The final value is determined when state is FoundEndOfHebrewNumber.
+ internal int result;
+
+ public HebrewNumberParsingContext(int result)
+ {
+ // Set the start state of the state machine for parsing Hebrew numbers.
+ state = HebrewNumber.HS.Start;
+ this.result = result;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Please see ParseByChar() for comments about different states defined here.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal enum HebrewNumberParsingState
+ {
+ InvalidHebrewNumber,
+ NotHebrewDigit,
+ FoundEndOfHebrewNumber,
+ ContinueParsing,
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // class HebrewNumber
+ //
+ // Provides static methods for formatting integer values into
+ // Hebrew text and parsing Hebrew number text.
+ //
+ // Limitations:
+ // Parse can only handles value 1 ~ 999.
+ // ToString() can only handles 1 ~ 999. If value is greater than 5000,
+ // 5000 will be subtracted from the value.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal class HebrewNumber
+ {
+ // This class contains only static methods. Add a private ctor so that
+ // compiler won't generate a default one for us.
+ private HebrewNumber()
+ {
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Converts the given number to Hebrew letters according to the numeric
+ // value of each Hebrew letter. Basically, this converts the lunar year
+ // and the lunar month to letters.
+ //
+ // The character of a year is described by three letters of the Hebrew
+ // alphabet, the first and third giving, respectively, the days of the
+ // weeks on which the New Year occurs and Passover begins, while the
+ // second is the initial of the Hebrew word for defective, normal, or
+ // complete.
+ //
+ // Defective Year : Both Heshvan and Kislev are defective (353 or 383 days)
+ // Normal Year : Heshvan is defective, Kislev is full (354 or 384 days)
+ // Complete Year : Both Heshvan and Kislev are full (355 or 385 days)
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal static String ToString(int Number)
+ {
+ char cTens = '\x0';
+ char cUnits; // tens and units chars
+ int Hundreds, Tens; // hundreds and tens values
+ StringBuilder szHebrew = new StringBuilder();
+
+
+ //
+ // Adjust the number if greater than 5000.
+ //
+ if (Number > 5000)
+ {
+ Number -= 5000;
+ }
+
+ Debug.Assert(Number > 0 && Number <= 999, "Number is out of range."); ;
+
+ //
+ // Get the Hundreds.
+ //
+ Hundreds = Number / 100;
+
+ if (Hundreds > 0)
+ {
+ Number -= Hundreds * 100;
+ // \x05e7 = 100
+ // \x05e8 = 200
+ // \x05e9 = 300
+ // \x05ea = 400
+ // If the number is greater than 400, use the multiples of 400.
+ for (int i = 0; i < (Hundreds / 4); i++)
+ {
+ szHebrew.Append('\x05ea');
+ }
+
+ int remains = Hundreds % 4;
+ if (remains > 0)
+ {
+ szHebrew.Append((char)((int)'\x05e6' + remains));
+ }
+ }
+
+ //
+ // Get the Tens.
+ //
+ Tens = Number / 10;
+ Number %= 10;
+
+ switch (Tens)
+ {
+ case (0):
+ cTens = '\x0';
+ break;
+ case (1):
+ cTens = '\x05d9'; // Hebrew Letter Yod
+ break;
+ case (2):
+ cTens = '\x05db'; // Hebrew Letter Kaf
+ break;
+ case (3):
+ cTens = '\x05dc'; // Hebrew Letter Lamed
+ break;
+ case (4):
+ cTens = '\x05de'; // Hebrew Letter Mem
+ break;
+ case (5):
+ cTens = '\x05e0'; // Hebrew Letter Nun
+ break;
+ case (6):
+ cTens = '\x05e1'; // Hebrew Letter Samekh
+ break;
+ case (7):
+ cTens = '\x05e2'; // Hebrew Letter Ayin
+ break;
+ case (8):
+ cTens = '\x05e4'; // Hebrew Letter Pe
+ break;
+ case (9):
+ cTens = '\x05e6'; // Hebrew Letter Tsadi
+ break;
+ }
+
+ //
+ // Get the Units.
+ //
+ cUnits = (char)(Number > 0 ? ((int)'\x05d0' + Number - 1) : 0);
+
+ if ((cUnits == '\x05d4') && // Hebrew Letter He (5)
+ (cTens == '\x05d9'))
+ { // Hebrew Letter Yod (10)
+ cUnits = '\x05d5'; // Hebrew Letter Vav (6)
+ cTens = '\x05d8'; // Hebrew Letter Tet (9)
+ }
+
+ if ((cUnits == '\x05d5') && // Hebrew Letter Vav (6)
+ (cTens == '\x05d9'))
+ { // Hebrew Letter Yod (10)
+ cUnits = '\x05d6'; // Hebrew Letter Zayin (7)
+ cTens = '\x05d8'; // Hebrew Letter Tet (9)
+ }
+
+ //
+ // Copy the appropriate info to the given buffer.
+ //
+
+ if (cTens != '\x0')
+ {
+ szHebrew.Append(cTens);
+ }
+
+ if (cUnits != '\x0')
+ {
+ szHebrew.Append(cUnits);
+ }
+
+ if (szHebrew.Length > 1)
+ {
+ szHebrew.Insert(szHebrew.Length - 1, '"');
+ }
+ else
+ {
+ szHebrew.Append('\'');
+ }
+
+ //
+ // Return success.
+ //
+ return (szHebrew.ToString());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Token used to tokenize a Hebrew word into tokens so that we can use in the
+ // state machine.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private enum HebrewToken : short
+ {
+ Invalid = -1,
+ Digit400 = 0,
+ Digit200_300 = 1,
+ Digit100 = 2,
+ Digit10 = 3, // 10 ~ 90
+ Digit1 = 4, // 1, 2, 3, 4, 5, 8,
+ Digit6_7 = 5,
+ Digit7 = 6,
+ Digit9 = 7,
+ SingleQuote = 8,
+ DoubleQuote = 9,
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // This class is used to map a token into its Hebrew digit value.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private struct HebrewValue
+ {
+ internal HebrewToken token;
+ internal short value;
+ internal HebrewValue(HebrewToken token, short value)
+ {
+ this.token = token;
+ this.value = value;
+ }
+ }
+
+ //
+ // Map a Hebrew character from U+05D0 ~ U+05EA to its digit value.
+ // The value is -1 if the Hebrew character does not have a associated value.
+ //
+ private static readonly HebrewValue[] s_hebrewValues = {
+ new HebrewValue(HebrewToken.Digit1, 1) , // '\x05d0
+ new HebrewValue(HebrewToken.Digit1, 2) , // '\x05d1
+ new HebrewValue(HebrewToken.Digit1, 3) , // '\x05d2
+ new HebrewValue(HebrewToken.Digit1, 4) , // '\x05d3
+ new HebrewValue(HebrewToken.Digit1, 5) , // '\x05d4
+ new HebrewValue(HebrewToken.Digit6_7,6) , // '\x05d5
+ new HebrewValue(HebrewToken.Digit6_7,7) , // '\x05d6
+ new HebrewValue(HebrewToken.Digit1, 8) , // '\x05d7
+ new HebrewValue(HebrewToken.Digit9, 9) , // '\x05d8
+ new HebrewValue(HebrewToken.Digit10, 10) , // '\x05d9; // Hebrew Letter Yod
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05da;
+ new HebrewValue(HebrewToken.Digit10, 20) , // '\x05db; // Hebrew Letter Kaf
+ new HebrewValue(HebrewToken.Digit10, 30) , // '\x05dc; // Hebrew Letter Lamed
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05dd;
+ new HebrewValue(HebrewToken.Digit10, 40) , // '\x05de; // Hebrew Letter Mem
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05df;
+ new HebrewValue(HebrewToken.Digit10, 50) , // '\x05e0; // Hebrew Letter Nun
+ new HebrewValue(HebrewToken.Digit10, 60) , // '\x05e1; // Hebrew Letter Samekh
+ new HebrewValue(HebrewToken.Digit10, 70) , // '\x05e2; // Hebrew Letter Ayin
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05e3;
+ new HebrewValue(HebrewToken.Digit10, 80) , // '\x05e4; // Hebrew Letter Pe
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05e5;
+ new HebrewValue(HebrewToken.Digit10, 90) , // '\x05e6; // Hebrew Letter Tsadi
+ new HebrewValue(HebrewToken.Digit100, 100) , // '\x05e7;
+ new HebrewValue(HebrewToken.Digit200_300, 200) , // '\x05e8;
+ new HebrewValue(HebrewToken.Digit200_300, 300) , // '\x05e9;
+ new HebrewValue(HebrewToken.Digit400, 400) , // '\x05ea;
+ };
+
+ private const int minHebrewNumberCh = 0x05d0;
+ private static char s_maxHebrewNumberCh = (char)(minHebrewNumberCh + s_hebrewValues.Length - 1);
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Hebrew number parsing State
+ // The current state and the next token will lead to the next state in the state machine.
+ // DQ = Double Quote
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal enum HS : sbyte
+ {
+ _err = -1, // an error state
+ Start = 0,
+ S400 = 1, // a Hebrew digit 400
+ S400_400 = 2, // Two Hebrew digit 400
+ S400_X00 = 3, // Two Hebrew digit 400 and followed by 100
+ S400_X0 = 4, // Hebrew digit 400 and followed by 10 ~ 90
+ X00_DQ = 5, // A hundred number and followed by a double quote.
+ S400_X00_X0 = 6,
+ X0_DQ = 7, // A two-digit number and followed by a double quote.
+ X = 8, // A single digit Hebrew number.
+ X0 = 9, // A two-digit Hebrew number
+ X00 = 10, // A three-digit Hebrew number
+ S400_DQ = 11, // A Hebrew digit 400 and followed by a double quote.
+ S400_400_DQ = 12,
+ S400_400_100 = 13,
+ S9 = 14, // Hebrew digit 9
+ X00_S9 = 15, // A hundered number and followed by a digit 9
+ S9_DQ = 16, // Hebrew digit 9 and followed by a double quote
+ END = 100, // A terminial state is reached.
+ }
+
+ //
+ // The state machine for Hebrew number pasing.
+ //
+ private static readonly HS[] s_numberPasingState =
+ {
+ // 400 300/200 100 90~10 8~1 6, 7, 9, ' "
+ /* 0 */
+ HS.S400, HS.X00, HS.X00, HS.X0, HS.X, HS.X, HS.X, HS.S9, HS._err, HS._err,
+ /* 1: S400 */
+ HS.S400_400, HS.S400_X00, HS.S400_X00, HS.S400_X0, HS._err, HS._err, HS._err, HS.X00_S9 ,HS.END, HS.S400_DQ,
+ /* 2: S400_400 */
+ HS._err, HS._err, HS.S400_400_100,HS.S400_X0, HS._err, HS._err, HS._err, HS.X00_S9 ,HS._err, HS.S400_400_DQ,
+ /* 3: S400_X00 */
+ HS._err, HS._err, HS._err, HS.S400_X00_X0, HS._err, HS._err, HS._err, HS.X00_S9 ,HS._err, HS.X00_DQ,
+ /* 4: S400_X0 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.X0_DQ,
+ /* 5: X00_DQ */
+ HS._err, HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err,
+ /* 6: S400_X00_X0 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.X0_DQ,
+ /* 7: X0_DQ */
+ HS._err, HS._err, HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err,
+ /* 8: X */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS._err,
+ /* 9: X0 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS.X0_DQ,
+ /* 10: X00 */
+ HS._err, HS._err, HS._err, HS.S400_X0, HS._err, HS._err, HS._err, HS.X00_S9, HS.END, HS.X00_DQ,
+ /* 11: S400_DQ */
+ HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err,
+ /* 12: S400_400_DQ*/
+ HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err,
+ /* 13: S400_400_100*/
+ HS._err, HS._err, HS._err, HS.S400_X00_X0, HS._err, HS._err, HS._err, HS.X00_S9, HS._err, HS.X00_DQ,
+ /* 14: S9 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS.S9_DQ,
+ /* 15: X00_S9 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.S9_DQ,
+ /* 16: S9_DQ */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS.END, HS._err, HS._err, HS._err
+ };
+
+ // Count of valid HebrewToken, column count in the NumberPasingState array
+ private const int HebrewTokenCount = 10;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Parse the Hebrew number by passing one character at a time.
+ // The state between characters are maintained at HebrewNumberPasingContext.
+ // Returns:
+ // Return a enum of HebrewNumberParsingState.
+ // NotHebrewDigit: The specified ch is not a valid Hebrew digit.
+ // InvalidHebrewNumber: After parsing the specified ch, it will lead into
+ // an invalid Hebrew number text.
+ // FoundEndOfHebrewNumber: A terminal state is reached. This means that
+ // we find a valid Hebrew number text after the specified ch is parsed.
+ // ContinueParsing: The specified ch is a valid Hebrew digit, and
+ // it will lead into a valid state in the state machine, we should
+ // continue to parse incoming characters.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static HebrewNumberParsingState ParseByChar(char ch, ref HebrewNumberParsingContext context)
+ {
+ Debug.Assert(s_numberPasingState.Length == HebrewTokenCount * ((int)HS.S9_DQ + 1));
+
+ HebrewToken token;
+ if (ch == '\'')
+ {
+ token = HebrewToken.SingleQuote;
+ }
+ else if (ch == '\"')
+ {
+ token = HebrewToken.DoubleQuote;
+ }
+ else
+ {
+ int index = (int)ch - minHebrewNumberCh;
+ if (index >= 0 && index < s_hebrewValues.Length)
+ {
+ token = s_hebrewValues[index].token;
+ if (token == HebrewToken.Invalid)
+ {
+ return (HebrewNumberParsingState.NotHebrewDigit);
+ }
+ context.result += s_hebrewValues[index].value;
+ }
+ else
+ {
+ // Not in valid Hebrew digit range.
+ return (HebrewNumberParsingState.NotHebrewDigit);
+ }
+ }
+ context.state = s_numberPasingState[(int)context.state * (int)HebrewTokenCount + (int)token];
+ if (context.state == HS._err)
+ {
+ // Invalid Hebrew state. This indicates an incorrect Hebrew number.
+ return (HebrewNumberParsingState.InvalidHebrewNumber);
+ }
+ if (context.state == HS.END)
+ {
+ // Reach a terminal state.
+ return (HebrewNumberParsingState.FoundEndOfHebrewNumber);
+ }
+ // We should continue to parse.
+ return (HebrewNumberParsingState.ContinueParsing);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Check if the ch is a valid Hebrew number digit.
+ // This function will return true if the specified char is a legal Hebrew
+ // digit character, single quote, or double quote.
+ // Returns:
+ // true if the specified character is a valid Hebrew number character.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static bool IsDigit(char ch)
+ {
+ if (ch >= minHebrewNumberCh && ch <= s_maxHebrewNumberCh)
+ {
+ return (s_hebrewValues[ch - minHebrewNumberCh].value >= 0);
+ }
+ return (ch == '\'' || ch == '\"');
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Unix.cs
new file mode 100644
index 0000000000..a6e8f73d3e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Unix.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ public partial class HijriCalendar : Calendar
+ {
+ private static int GetHijriDateAdjustment()
+ {
+ // this setting is not supported on Unix, so always return 0
+ return 0;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs
new file mode 100644
index 0000000000..64a73fb81d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.Win32.cs
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32;
+
+namespace System.Globalization
+{
+ public partial class HijriCalendar : Calendar
+ {
+ private int GetHijriDateAdjustment()
+ {
+ if (_hijriAdvance == Int32.MinValue)
+ {
+ // Never been set before. Use the system value from registry.
+ _hijriAdvance = GetAdvanceHijriDate();
+ }
+ return (_hijriAdvance);
+ }
+
+ private const String InternationalRegKey = "Control Panel\\International";
+ private const String HijriAdvanceRegKeyEntry = "AddHijriDate";
+
+ /*=================================GetAdvanceHijriDate==========================
+ **Action: Gets the AddHijriDate value from the registry.
+ **Returns:
+ **Arguments: None.
+ **Exceptions:
+ **Note:
+ ** The HijriCalendar has a user-overidable calculation. That is, use can set a value from the control
+ ** panel, so that the calculation of the Hijri Calendar can move ahead or backwards from -2 to +2 days.
+ **
+ ** The valid string values in the registry are:
+ ** "AddHijriDate-2" => Add -2 days to the current calculated Hijri date.
+ ** "AddHijriDate" => Add -1 day to the current calculated Hijri date.
+ ** "" => Add 0 day to the current calculated Hijri date.
+ ** "AddHijriDate+1" => Add +1 days to the current calculated Hijri date.
+ ** "AddHijriDate+2" => Add +2 days to the current calculated Hijri date.
+ ============================================================================*/
+ private static int GetAdvanceHijriDate()
+ {
+ int hijriAdvance = 0;
+ Microsoft.Win32.RegistryKey key = null;
+
+ try
+ {
+ // Open in read-only mode.
+ key = RegistryKey.GetBaseKey(RegistryKey.HKEY_CURRENT_USER).OpenSubKey(InternationalRegKey, false);
+ }
+ //If this fails for any reason, we'll just return 0.
+ catch (ObjectDisposedException) { return 0; }
+ catch (ArgumentException) { return 0; }
+
+ if (key != null)
+ {
+ try
+ {
+ Object value = key.InternalGetValue(HijriAdvanceRegKeyEntry, null, false, false);
+ if (value == null)
+ {
+ return (0);
+ }
+ String str = value.ToString();
+ if (String.Compare(str, 0, HijriAdvanceRegKeyEntry, 0, HijriAdvanceRegKeyEntry.Length, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ if (str.Length == HijriAdvanceRegKeyEntry.Length)
+ hijriAdvance = -1;
+ else
+ {
+ try
+ {
+ int advance = Int32.Parse(str.AsSpan(HijriAdvanceRegKeyEntry.Length), provider:CultureInfo.InvariantCulture);
+ if ((advance >= MinAdvancedHijri) && (advance <= MaxAdvancedHijri))
+ {
+ hijriAdvance = advance;
+ }
+ }
+ // If we got garbage from registry just ignore it.
+ // hijriAdvance = 0 because of declaraction assignment up above.
+ catch (ArgumentException) { }
+ catch (FormatException) { }
+ catch (OverflowException) { }
+ }
+ }
+ }
+ finally
+ {
+ key.Close();
+ }
+ }
+ return (hijriAdvance);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.WinRT.cs b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.WinRT.cs
new file mode 100644
index 0000000000..fb91c27ef6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.WinRT.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Internal.Runtime.Augments;
+
+namespace System.Globalization
+{
+ public partial class HijriCalendar : Calendar
+ {
+ private static int GetHijriDateAdjustment()
+ {
+ return WinRTInterop.Callbacks.GetHijriDateAdjustment();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.cs
new file mode 100644
index 0000000000..6755844620
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/HijriCalendar.cs
@@ -0,0 +1,671 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization
+{
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Rules for the Hijri calendar:
+ // - The Hijri calendar is a strictly Lunar calendar.
+ // - Days begin at sunset.
+ // - Islamic Year 1 (Muharram 1, 1 A.H.) is equivalent to absolute date
+ // 227015 (Friday, July 16, 622 C.E. - Julian).
+ // - Leap Years occur in the 2, 5, 7, 10, 13, 16, 18, 21, 24, 26, & 29th
+ // years of a 30-year cycle. Year = leap iff ((11y+14) mod 30 < 11).
+ // - There are 12 months which contain alternately 30 and 29 days.
+ // - The 12th month, Dhu al-Hijjah, contains 30 days instead of 29 days
+ // in a leap year.
+ // - Common years have 354 days. Leap years have 355 days.
+ // - There are 10,631 days in a 30-year cycle.
+ // - The Islamic months are:
+ // 1. Muharram (30 days) 7. Rajab (30 days)
+ // 2. Safar (29 days) 8. Sha'ban (29 days)
+ // 3. Rabi I (30 days) 9. Ramadan (30 days)
+ // 4. Rabi II (29 days) 10. Shawwal (29 days)
+ // 5. Jumada I (30 days) 11. Dhu al-Qada (30 days)
+ // 6. Jumada II (29 days) 12. Dhu al-Hijjah (29 days) {30}
+ //
+ // NOTENOTE
+ // The calculation of the HijriCalendar is based on the absolute date. And the
+ // absolute date means the number of days from January 1st, 1 A.D.
+ // Therefore, we do not support the days before the January 1st, 1 A.D.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 0622/07/18 9999/12/31
+ ** Hijri 0001/01/01 9666/04/03
+ */
+
+ public partial class HijriCalendar : Calendar
+ {
+ public static readonly int HijriEra = 1;
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+ internal const int MinAdvancedHijri = -2;
+ internal const int MaxAdvancedHijri = 2;
+
+ internal static readonly int[] HijriMonthDays = { 0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325, 355 };
+
+ private int _hijriAdvance = Int32.MinValue;
+
+ // DateTime.MaxValue = Hijri calendar (year:9666, month: 4, day: 3).
+ internal const int MaxCalendarYear = 9666;
+ internal const int MaxCalendarMonth = 4;
+ internal const int MaxCalendarDay = 3;
+ // Hijri calendar (year: 1, month: 1, day:1 ) = Gregorian (year: 622, month: 7, day: 18)
+ // This is the minimal Gregorian date that we support in the HijriCalendar.
+ internal static readonly DateTime calendarMinValue = new DateTime(622, 7, 18);
+ internal static readonly DateTime calendarMaxValue = DateTime.MaxValue;
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (calendarMaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.LunarCalendar;
+ }
+ }
+
+ public HijriCalendar()
+ {
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return CalendarId.HIJRI;
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // the year before the 1st year of the cycle would have been the 30th year
+ // of the previous cycle which is not a leap year. Common years have 354 days.
+ return 354;
+ }
+ }
+
+
+
+ /*=================================GetAbsoluteDateHijri==========================
+ **Action: Gets the Absolute date for the given Hijri date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ private long GetAbsoluteDateHijri(int y, int m, int d)
+ {
+ return (long)(DaysUpToHijriYear(y) + HijriMonthDays[m - 1] + d - 1 - HijriAdjustment);
+ }
+
+ /*=================================DaysUpToHijriYear==========================
+ **Action: Gets the total number of days (absolute date) up to the given Hijri Year.
+ ** The absolute date means the number of days from January 1st, 1 A.D.
+ **Returns: Gets the total number of days (absolute date) up to the given Hijri Year.
+ **Arguments: HijriYear year value in Hijri calendar.
+ **Exceptions: None
+ **Notes:
+ ============================================================================*/
+
+ private long DaysUpToHijriYear(int HijriYear)
+ {
+ long NumDays; // number of absolute days
+ int NumYear30; // number of years up to current 30 year cycle
+ int NumYearsLeft; // number of years into 30 year cycle
+
+ //
+ // Compute the number of years up to the current 30 year cycle.
+ //
+ NumYear30 = ((HijriYear - 1) / 30) * 30;
+
+ //
+ // Compute the number of years left. This is the number of years
+ // into the 30 year cycle for the given year.
+ //
+ NumYearsLeft = HijriYear - NumYear30 - 1;
+
+ //
+ // Compute the number of absolute days up to the given year.
+ //
+ NumDays = ((NumYear30 * 10631L) / 30L) + 227013L;
+ while (NumYearsLeft > 0)
+ {
+ // Common year is 354 days, and leap year is 355 days.
+ NumDays += 354 + (IsLeapYear(NumYearsLeft, CurrentEra) ? 1 : 0);
+ NumYearsLeft--;
+ }
+
+ //
+ // Return the number of absolute days.
+ //
+ return (NumDays);
+ }
+
+ public int HijriAdjustment
+ {
+ get
+ {
+ if (_hijriAdvance == Int32.MinValue)
+ {
+ // Never been set before. Use the system value from registry.
+ _hijriAdvance = GetHijriDateAdjustment();
+ }
+ return (_hijriAdvance);
+ }
+
+ set
+ {
+ // NOTE: Check the value of Min/MaxAdvancedHijri with Arabic speakers to see if the assumption is good.
+ if (value < MinAdvancedHijri || value > MaxAdvancedHijri)
+ {
+ throw new ArgumentOutOfRangeException(
+ "HijriAdjustment",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Bounds_Lower_Upper,
+ MinAdvancedHijri,
+ MaxAdvancedHijri));
+ }
+ VerifyWritable();
+
+ _hijriAdvance = value;
+ }
+ }
+
+ internal static void CheckTicksRange(long ticks)
+ {
+ if (ticks < calendarMinValue.Ticks || ticks > calendarMaxValue.Ticks)
+ {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ SR.ArgumentOutOfRange_CalendarRange,
+ calendarMinValue,
+ calendarMaxValue));
+ }
+ }
+
+ internal static void CheckEraRange(int era)
+ {
+ if (era != CurrentEra && era != HijriEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ internal static void CheckYearRange(int year, int era)
+ {
+ CheckEraRange(era);
+ if (year < 1 || year > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarYear));
+ }
+ }
+
+ internal static void CheckYearMonthRange(int year, int month, int era)
+ {
+ CheckYearRange(year, era);
+ if (year == MaxCalendarYear)
+ {
+ if (month > MaxCalendarMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(month),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarMonth));
+ }
+ }
+
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+ }
+
+ /*=================================GetDatePart==========================
+ **Action: Returns a given date part of this <i>DateTime</i>. This method is used
+ ** to compute the year, day-of-year, month, or day part.
+ **Returns:
+ **Arguments:
+ **Exceptions: ArgumentException if part is incorrect.
+ **Notes:
+ ** First, we get the absolute date (the number of days from January 1st, 1 A.C) for the given ticks.
+ ** Use the formula (((AbsoluteDate - 227013) * 30) / 10631) + 1, we can a rough value for the Hijri year.
+ ** In order to get the exact Hijri year, we compare the exact absolute date for HijriYear and (HijriYear + 1).
+ ** From here, we can get the correct Hijri year.
+ ============================================================================*/
+
+ internal virtual int GetDatePart(long ticks, int part)
+ {
+ int HijriYear; // Hijri year
+ int HijriMonth; // Hijri month
+ int HijriDay; // Hijri day
+ long NumDays; // The calculation buffer in number of days.
+
+ CheckTicksRange(ticks);
+
+ //
+ // Get the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
+ // 1/1/0001 is absolute date 1.
+ //
+ NumDays = ticks / GregorianCalendar.TicksPerDay + 1;
+
+ //
+ // See how much we need to backup or advance
+ //
+ NumDays += HijriAdjustment;
+
+ //
+ // Calculate the appromixate Hijri Year from this magic formula.
+ //
+ HijriYear = (int)(((NumDays - 227013) * 30) / 10631) + 1;
+
+ long daysToHijriYear = DaysUpToHijriYear(HijriYear); // The absolute date for HijriYear
+ long daysOfHijriYear = GetDaysInYear(HijriYear, CurrentEra); // The number of days for (HijriYear+1) year.
+
+ if (NumDays < daysToHijriYear)
+ {
+ daysToHijriYear -= daysOfHijriYear;
+ HijriYear--;
+ }
+ else if (NumDays == daysToHijriYear)
+ {
+ HijriYear--;
+ daysToHijriYear -= GetDaysInYear(HijriYear, CurrentEra);
+ }
+ else
+ {
+ if (NumDays > daysToHijriYear + daysOfHijriYear)
+ {
+ daysToHijriYear += daysOfHijriYear;
+ HijriYear++;
+ }
+ }
+ if (part == DatePartYear)
+ {
+ return (HijriYear);
+ }
+
+ //
+ // Calculate the Hijri Month.
+ //
+
+ HijriMonth = 1;
+ NumDays -= daysToHijriYear;
+
+ if (part == DatePartDayOfYear)
+ {
+ return ((int)NumDays);
+ }
+
+ while ((HijriMonth <= 12) && (NumDays > HijriMonthDays[HijriMonth - 1]))
+ {
+ HijriMonth++;
+ }
+ HijriMonth--;
+
+ if (part == DatePartMonth)
+ {
+ return (HijriMonth);
+ }
+
+ //
+ // Calculate the Hijri Day.
+ //
+ HijriDay = (int)(NumDays - HijriMonthDays[HijriMonth - 1]);
+
+ if (part == DatePartDay)
+ {
+ return (HijriDay);
+ }
+ // Incorrect part value.
+ throw new InvalidOperationException(SR.InvalidOperation_DateTimeParsing);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ -120000,
+ 120000));
+ }
+ // Get the date in Hijri calendar.
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int days = GetDaysInMonth(y, m);
+ if (d > days)
+ {
+ d = days;
+ }
+ long ticks = GetAbsoluteDateHijri(y, m, d) * TicksPerDay + (time.Ticks % TicksPerDay);
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ CheckYearMonthRange(year, month, era);
+ if (month == 12)
+ {
+ // For the 12th month, leap year has 30 days, and common year has 29 days.
+ return (IsLeapYear(year, CurrentEra) ? 30 : 29);
+ }
+ // Other months contain 30 and 29 days alternatively. The 1st month has 30 days.
+ return (((month % 2) == 1) ? 30 : 29);
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ // Common years have 354 days. Leap years have 355 days.
+ return (IsLeapYear(year, CurrentEra) ? 355 : 354);
+ }
+
+ // Returns the era for the specified DateTime value.
+
+ public override int GetEra(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return (HijriEra);
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { HijriEra });
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+ public override int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and MaxCalendarYear.
+ //
+
+ public override int GetYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ // The year/month/era value checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Day,
+ daysInMonth,
+ month));
+ }
+ return (IsLeapYear(year, era) && month == 12 && day == 30);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ CheckYearMonthRange(year, month, era);
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return ((((year * 11) + 14) % 30) < 11);
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ // The year/month/era checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Day,
+ daysInMonth,
+ month));
+ }
+
+ long lDate = GetAbsoluteDateHijri(year, month, day);
+
+ if (lDate >= 0)
+ {
+ return (new DateTime(lDate * GregorianCalendar.TicksPerDay + TimeToTicks(hour, minute, second, millisecond)));
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 1451;
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(value),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ MaxCalendarYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (year < 100)
+ {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if (year > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarYear));
+ }
+ return (year);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.Unix.cs
new file mode 100644
index 0000000000..20f753e986
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.Unix.cs
@@ -0,0 +1,145 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ sealed partial class IdnMapping
+ {
+ private unsafe string GetAsciiCore(string unicodeString, char* unicode, int count)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(unicodeString != null && unicodeString.Length >= count);
+
+ uint flags = Flags;
+ CheckInvalidIdnCharacters(unicode, count, flags, nameof(unicode));
+
+ const int StackallocThreshold = 512;
+ // Each unicode character is represented by up to 3 ASCII chars
+ // and the whole string is prefixed by "xn--" (length 4)
+ int estimatedLength = (int)Math.Min(count * 3L + 4, StackallocThreshold);
+ int actualLength;
+ if (estimatedLength < StackallocThreshold)
+ {
+ char* outputStack = stackalloc char[estimatedLength];
+ actualLength = Interop.Globalization.ToAscii(flags, unicode, count, outputStack, estimatedLength);
+ if (actualLength > 0 && actualLength <= estimatedLength)
+ {
+ return GetStringForOutput(unicodeString, unicode, count, outputStack, actualLength);
+ }
+ }
+ else
+ {
+ actualLength = Interop.Globalization.ToAscii(flags, unicode, count, null, 0);
+ }
+ if (actualLength == 0)
+ {
+ throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode));
+ }
+
+ char[] outputHeap = new char[actualLength];
+ fixed (char* pOutputHeap = &outputHeap[0])
+ {
+ actualLength = Interop.Globalization.ToAscii(flags, unicode, count, pOutputHeap, actualLength);
+ if (actualLength == 0 || actualLength > outputHeap.Length)
+ {
+ throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode));
+ }
+ return GetStringForOutput(unicodeString, unicode, count, pOutputHeap, actualLength);
+ }
+ }
+
+ private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(asciiString != null && asciiString.Length >= count);
+
+ uint flags = Flags;
+ CheckInvalidIdnCharacters(ascii, count, flags, nameof(ascii));
+
+ const int StackAllocThreshold = 512;
+ if (count < StackAllocThreshold)
+ {
+ char* output = stackalloc char[count];
+ return GetUnicodeCore(asciiString, ascii, count, flags, output, count, reattempt: true);
+ }
+ else
+ {
+ char[] output = new char[count];
+ fixed (char* pOutput = &output[0])
+ {
+ return GetUnicodeCore(asciiString, ascii, count, flags, pOutput, count, reattempt: true);
+ }
+ }
+ }
+
+ private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count, uint flags, char* output, int outputLength, bool reattempt)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(asciiString != null && asciiString.Length >= count);
+
+ int realLen = Interop.Globalization.ToUnicode(flags, ascii, count, output, outputLength);
+
+ if (realLen == 0)
+ {
+ throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii));
+ }
+ else if (realLen <= outputLength)
+ {
+ return GetStringForOutput(asciiString, ascii, count, output, realLen);
+ }
+ else if (reattempt)
+ {
+ char[] newOutput = new char[realLen];
+ fixed (char* pNewOutput = newOutput)
+ {
+ return GetUnicodeCore(asciiString, ascii, count, flags, pNewOutput, realLen, reattempt: false);
+ }
+ }
+
+ throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii));
+ }
+
+ // -----------------------------
+ // ---- PAL layer ends here ----
+ // -----------------------------
+
+ private uint Flags
+ {
+ get
+ {
+ int flags =
+ (AllowUnassigned ? Interop.Globalization.AllowUnassigned : 0) |
+ (UseStd3AsciiRules ? Interop.Globalization.UseStd3AsciiRules : 0);
+ return (uint)flags;
+ }
+ }
+
+ /// <summary>
+ /// ICU doesn't check for invalid characters unless the STD3 rules option
+ /// is enabled.
+ ///
+ /// To match Windows behavior, we walk the string ourselves looking for these
+ /// bad characters so we can continue to throw ArgumentException in these cases.
+ /// </summary>
+ private static unsafe void CheckInvalidIdnCharacters(char* s, int count, uint flags, string paramName)
+ {
+ if ((flags & Interop.Globalization.UseStd3AsciiRules) == 0)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ char c = s[i];
+
+ // These characters are prohibited regardless of the UseStd3AsciiRules property.
+ // See https://msdn.microsoft.com/en-us/library/system.globalization.idnmapping.usestd3asciirules(v=vs.110).aspx
+ if (c <= 0x1F || c == 0x7F)
+ {
+ throw new ArgumentException(SR.Argument_IdnIllegalName, paramName);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.Windows.cs
new file mode 100644
index 0000000000..9d491dfbb8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.Windows.cs
@@ -0,0 +1,128 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Globalization
+{
+ public sealed partial class IdnMapping
+ {
+ private unsafe string GetAsciiCore(string unicodeString, char* unicode, int count)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(unicodeString != null && unicodeString.Length >= count);
+
+ uint flags = Flags;
+
+ // Determine the required length
+ int length = Interop.Normaliz.IdnToAscii(flags, unicode, count, null, 0);
+ if (length == 0)
+ {
+ ThrowForZeroLength(unicode: true);
+ }
+
+ // Do the conversion
+ const int StackAllocThreshold = 512; // arbitrary limit to switch from stack to heap allocation
+ if (length < StackAllocThreshold)
+ {
+ char* output = stackalloc char[length];
+ return GetAsciiCore(unicodeString, unicode, count, flags, output, length);
+ }
+ else
+ {
+ char[] output = new char[length];
+ fixed (char* pOutput = &output[0])
+ {
+ return GetAsciiCore(unicodeString, unicode, count, flags, pOutput, length);
+ }
+ }
+ }
+
+ private unsafe string GetAsciiCore(string unicodeString, char* unicode, int count, uint flags, char* output, int outputLength)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(unicodeString != null && unicodeString.Length >= count);
+
+ int length = Interop.Normaliz.IdnToAscii(flags, unicode, count, output, outputLength);
+ if (length == 0)
+ {
+ ThrowForZeroLength(unicode: true);
+ }
+ Debug.Assert(length == outputLength);
+ return GetStringForOutput(unicodeString, unicode, count, output, length);
+ }
+
+ private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(asciiString != null && asciiString.Length >= count);
+
+ uint flags = Flags;
+
+ // Determine the required length
+ int length = Interop.Normaliz.IdnToUnicode(flags, ascii, count, null, 0);
+ if (length == 0)
+ {
+ ThrowForZeroLength(unicode: false);
+ }
+
+ // Do the conversion
+ const int StackAllocThreshold = 512; // arbitrary limit to switch from stack to heap allocation
+ if (length < StackAllocThreshold)
+ {
+ char* output = stackalloc char[length];
+ return GetUnicodeCore(asciiString, ascii, count, flags, output, length);
+ }
+ else
+ {
+ char[] output = new char[length];
+ fixed (char* pOutput = &output[0])
+ {
+ return GetUnicodeCore(asciiString, ascii, count, flags, pOutput, length);
+ }
+ }
+ }
+
+ private unsafe string GetUnicodeCore(string asciiString, char* ascii, int count, uint flags, char* output, int outputLength)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(asciiString != null && asciiString.Length >= count);
+
+ int length = Interop.Normaliz.IdnToUnicode(flags, ascii, count, output, outputLength);
+ if (length == 0)
+ {
+ ThrowForZeroLength(unicode: false);
+ }
+ Debug.Assert(length == outputLength);
+ return GetStringForOutput(asciiString, ascii, count, output, length);
+ }
+
+ // -----------------------------
+ // ---- PAL layer ends here ----
+ // -----------------------------
+
+ private uint Flags
+ {
+ get
+ {
+ int flags =
+ (AllowUnassigned ? Interop.Normaliz.IDN_ALLOW_UNASSIGNED : 0) |
+ (UseStd3AsciiRules ? Interop.Normaliz.IDN_USE_STD3_ASCII_RULES : 0);
+ return (uint)flags;
+ }
+ }
+
+ private static void ThrowForZeroLength(bool unicode)
+ {
+ int lastError = Marshal.GetLastWin32Error();
+
+ throw new ArgumentException(
+ lastError == Interop.Errors.ERROR_INVALID_NAME ? SR.Argument_IdnIllegalName :
+ (unicode ? SR.Argument_InvalidCharSequenceNoIndex : SR.Argument_IdnBadPunycode),
+ unicode ? "unicode" : "ascii");
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.cs b/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.cs
new file mode 100644
index 0000000000..6da6f79f24
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/IdnMapping.cs
@@ -0,0 +1,902 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// This file contains the IDN functions and implementation.
+//
+// This allows encoding of non-ASCII domain names in a "punycode" form,
+// for example:
+//
+// \u5B89\u5BA4\u5948\u7F8E\u6075-with-SUPER-MONKEYS
+//
+// is encoded as:
+//
+// xn---with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n
+//
+// Additional options are provided to allow unassigned IDN characters and
+// to validate according to the Std3ASCII Rules (like DNS names).
+//
+// There are also rules regarding bidirectionality of text and the length
+// of segments.
+//
+// For additional rules see also:
+// RFC 3490 - Internationalizing Domain Names in Applications (IDNA)
+// RFC 3491 - Nameprep: A Stringprep Profile for Internationalized Domain Names (IDN)
+// RFC 3492 - Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace System.Globalization
+{
+ // IdnMapping class used to map names to Punycode
+ public sealed partial class IdnMapping
+ {
+ private bool _allowUnassigned;
+ private bool _useStd3AsciiRules;
+
+ public IdnMapping()
+ {
+ }
+
+ public bool AllowUnassigned
+ {
+ get { return _allowUnassigned; }
+ set { _allowUnassigned = value; }
+ }
+
+ public bool UseStd3AsciiRules
+ {
+ get { return _useStd3AsciiRules; }
+ set { _useStd3AsciiRules = value; }
+ }
+
+ // Gets ASCII (Punycode) version of the string
+ public string GetAscii(string unicode)
+ {
+ return GetAscii(unicode, 0);
+ }
+
+ public string GetAscii(string unicode, int index)
+ {
+ if (unicode == null)
+ throw new ArgumentNullException(nameof(unicode));
+ return GetAscii(unicode, index, unicode.Length - index);
+ }
+
+ public string GetAscii(string unicode, int index, int count)
+ {
+ if (unicode == null)
+ throw new ArgumentNullException(nameof(unicode));
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0) ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (index > unicode.Length)
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ if (index > unicode.Length - count)
+ throw new ArgumentOutOfRangeException(nameof(unicode), SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (count == 0)
+ {
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+ }
+ if (unicode[index + count - 1] == 0)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidCharSequence, index + count - 1), nameof(unicode));
+ }
+
+ if (GlobalizationMode.Invariant)
+ {
+ return GetAsciiInvariant(unicode, index, count);
+ }
+
+ unsafe
+ {
+ fixed (char* pUnicode = unicode)
+ {
+ return GetAsciiCore(unicode, pUnicode + index, count);
+ }
+ }
+ }
+
+ // Gets Unicode version of the string. Normalized and limited to IDNA characters.
+ public string GetUnicode(string ascii)
+ {
+ return GetUnicode(ascii, 0);
+ }
+
+ public string GetUnicode(string ascii, int index)
+ {
+ if (ascii == null)
+ throw new ArgumentNullException(nameof(ascii));
+ return GetUnicode(ascii, index, ascii.Length - index);
+ }
+
+ public string GetUnicode(string ascii, int index, int count)
+ {
+ if (ascii == null)
+ throw new ArgumentNullException(nameof(ascii));
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0) ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (index > ascii.Length)
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ if (index > ascii.Length - count)
+ throw new ArgumentOutOfRangeException(nameof(ascii), SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // This is a case (i.e. explicitly null-terminated input) where behavior in .NET and Win32 intentionally differ.
+ // The .NET APIs should (and did in v4.0 and earlier) throw an ArgumentException on input that includes a terminating null.
+ // The Win32 APIs fail on an embedded null, but not on a terminating null.
+ if (count > 0 && ascii[index + count - 1] == (char)0)
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+
+ if (GlobalizationMode.Invariant)
+ {
+ return GetUnicodeInvariant(ascii, index, count);
+ }
+
+ unsafe
+ {
+ fixed (char* pAscii = ascii)
+ {
+ return GetUnicodeCore(ascii, pAscii + index, count);
+ }
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ IdnMapping that = obj as IdnMapping;
+ return
+ that != null &&
+ _allowUnassigned == that._allowUnassigned &&
+ _useStd3AsciiRules == that._useStd3AsciiRules;
+ }
+
+ public override int GetHashCode()
+ {
+ return (_allowUnassigned ? 100 : 200) + (_useStd3AsciiRules ? 1000 : 2000);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe string GetStringForOutput(string originalString, char* input, int inputLength, char* output, int outputLength)
+ {
+ return originalString.Length == inputLength && new ReadOnlySpan<char>(input, inputLength).SequenceEqual(new ReadOnlySpan<char>(output, outputLength)) ?
+ originalString :
+ new string(output, 0, outputLength);
+ }
+
+ //
+ // Invariant implementation
+ //
+
+ private const char c_delimiter = '-';
+ private const string c_strAcePrefix = "xn--";
+ private const int c_labelLimit = 63; // Not including dots
+ private const int c_defaultNameLimit = 255; // Including dots
+ private const int c_initialN = 0x80;
+ private const int c_maxint = 0x7ffffff;
+ private const int c_initialBias = 72;
+ private const int c_punycodeBase = 36;
+ private const int c_tmin = 1;
+ private const int c_tmax = 26;
+ private const int c_skew = 38;
+ private const int c_damp = 700;
+
+
+ // Legal "dot" separators (i.e: . in www.microsoft.com)
+ private static char[] c_Dots = { '.', '\u3002', '\uFF0E', '\uFF61' };
+
+ private string GetAsciiInvariant(string unicode, int index, int count)
+ {
+ if (index > 0 || count < unicode.Length)
+ {
+ unicode = unicode.Substring(index, count);
+ }
+
+ // Check for ASCII only string, which will be unchanged
+ if (ValidateStd3AndAscii(unicode, UseStd3AsciiRules, true))
+ {
+ return unicode;
+ }
+
+ // Cannot be null terminated (normalization won't help us with this one, and
+ // may have returned false before checking the whole string above)
+ Debug.Assert(count >= 1, "[IdnMapping.GetAscii] Expected 0 length strings to fail before now.");
+ if (unicode[unicode.Length - 1] <= 0x1f)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidCharSequence, unicode.Length - 1), nameof(unicode));
+ }
+
+ // Have to correctly IDNA normalize the string and Unassigned flags
+ bool bHasLastDot = (unicode.Length > 0) && IsDot(unicode[unicode.Length - 1]);
+
+ // Make sure we didn't normalize away something after a last dot
+ if ((!bHasLastDot) && unicode.Length > 0 && IsDot(unicode[unicode.Length - 1]))
+ {
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+ }
+
+ // May need to check Std3 rules again for non-ascii
+ if (UseStd3AsciiRules)
+ {
+ ValidateStd3AndAscii(unicode, true, false);
+ }
+
+ // Go ahead and encode it
+ return PunycodeEncode(unicode);
+ }
+
+ // See if we're only ASCII
+ static bool ValidateStd3AndAscii(string unicode, bool bUseStd3, bool bCheckAscii)
+ {
+ // If its empty, then its too small
+ if (unicode.Length == 0)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+
+ int iLastDot = -1;
+
+ // Loop the whole string
+ for (int i = 0; i < unicode.Length; i++)
+ {
+ // Aren't allowing control chars (or 7f, but idn tables catch that, they don't catch \0 at end though)
+ if (unicode[i] <= 0x1f)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidCharSequence, i ), nameof(unicode));
+ }
+
+ // If its Unicode or a control character, return false (non-ascii)
+ if (bCheckAscii && unicode[i] >= 0x7f)
+ return false;
+
+ // Check for dots
+ if (IsDot(unicode[i]))
+ {
+ // Can't have 2 dots in a row
+ if (i == iLastDot + 1)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+
+ // If its too far between dots then fail
+ if (i - iLastDot > c_labelLimit + 1)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+
+ // If validating Std3, then char before dot can't be - char
+ if (bUseStd3 && i > 0)
+ ValidateStd3(unicode[i - 1], true);
+
+ // Remember where the last dot is
+ iLastDot = i;
+ continue;
+ }
+
+ // If necessary, make sure its a valid std3 character
+ if (bUseStd3)
+ {
+ ValidateStd3(unicode[i], (i == iLastDot + 1));
+ }
+ }
+
+ // If we never had a dot, then we need to be shorter than the label limit
+ if (iLastDot == -1 && unicode.Length > c_labelLimit)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+
+ // Need to validate entire string length, 1 shorter if last char wasn't a dot
+ if (unicode.Length > c_defaultNameLimit - (IsDot(unicode[unicode.Length - 1]) ? 0 : 1))
+ throw new ArgumentException(SR.Format(SR.Argument_IdnBadNameSize,
+ c_defaultNameLimit - (IsDot(unicode[unicode.Length - 1]) ? 0 : 1)), nameof(unicode));
+
+ // If last char wasn't a dot we need to check for trailing -
+ if (bUseStd3 && !IsDot(unicode[unicode.Length - 1]))
+ ValidateStd3(unicode[unicode.Length - 1], true);
+
+ return true;
+ }
+
+ /* PunycodeEncode() converts Unicode to Punycode. The input */
+ /* is represented as an array of Unicode code points (not code */
+ /* units; surrogate pairs are not allowed), and the output */
+ /* will be represented as an array of ASCII code points. The */
+ /* output string is *not* null-terminated; it will contain */
+ /* zeros if and only if the input contains zeros. (Of course */
+ /* the caller can leave room for a terminator and add one if */
+ /* needed.) The input_length is the number of code points in */
+ /* the input. The output_length is an in/out argument: the */
+ /* caller passes in the maximum number of code points that it */
+
+ /* can receive, and on successful return it will contain the */
+ /* number of code points actually output. The case_flags array */
+ /* holds input_length boolean values, where nonzero suggests that */
+ /* the corresponding Unicode character be forced to uppercase */
+ /* after being decoded (if possible), and zero suggests that */
+ /* it be forced to lowercase (if possible). ASCII code points */
+ /* are encoded literally, except that ASCII letters are forced */
+ /* to uppercase or lowercase according to the corresponding */
+ /* uppercase flags. If case_flags is a null pointer then ASCII */
+ /* letters are left as they are, and other code points are */
+ /* treated as if their uppercase flags were zero. The return */
+ /* value can be any of the punycode_status values defined above */
+ /* except punycode_bad_input; if not punycode_success, then */
+ /* output_size and output might contain garbage. */
+ static string PunycodeEncode(string unicode)
+ {
+ // 0 length strings aren't allowed
+ if (unicode.Length == 0)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+
+ StringBuilder output = new StringBuilder(unicode.Length);
+ int iNextDot = 0;
+ int iAfterLastDot = 0;
+ int iOutputAfterLastDot = 0;
+
+ // Find the next dot
+ while (iNextDot < unicode.Length)
+ {
+ // Find end of this segment
+ iNextDot = unicode.IndexOfAny(c_Dots, iAfterLastDot);
+ Debug.Assert(iNextDot <= unicode.Length, "[IdnMapping.punycode_encode]IndexOfAny is broken");
+ if (iNextDot < 0)
+ iNextDot = unicode.Length;
+
+ // Only allowed to have empty . section at end (www.microsoft.com.)
+ if (iNextDot == iAfterLastDot)
+ {
+ // Only allowed to have empty sections as trailing .
+ if (iNextDot != unicode.Length)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+ // Last dot, stop
+ break;
+ }
+
+ // We'll need an Ace prefix
+ output.Append(c_strAcePrefix);
+
+ // Everything resets every segment.
+ bool bRightToLeft = false;
+
+ // Check for RTL. If right-to-left, then 1st & last chars must be RTL
+ BidiCategory eBidi = CharUnicodeInfo.GetBidiCategory(unicode, iAfterLastDot);
+ if (eBidi == BidiCategory.RightToLeft || eBidi == BidiCategory.RightToLeftArabic)
+ {
+ // It has to be right to left.
+ bRightToLeft = true;
+
+ // Check last char
+ int iTest = iNextDot - 1;
+ if (Char.IsLowSurrogate(unicode, iTest))
+ {
+ iTest--;
+ }
+
+ eBidi = CharUnicodeInfo.GetBidiCategory(unicode, iTest);
+ if (eBidi != BidiCategory.RightToLeft && eBidi != BidiCategory.RightToLeftArabic)
+ {
+ // Oops, last wasn't RTL, last should be RTL if first is RTL
+ throw new ArgumentException(SR.Argument_IdnBadBidi, nameof(unicode));
+ }
+ }
+
+ // Handle the basic code points
+ int basicCount;
+ int numProcessed = 0; // Num code points that have been processed so far (this segment)
+ for (basicCount = iAfterLastDot; basicCount < iNextDot; basicCount++)
+ {
+ // Can't be lonely surrogate because it would've thrown in normalization
+ Debug.Assert(Char.IsLowSurrogate(unicode, basicCount) == false, "[IdnMapping.punycode_encode]Unexpected low surrogate");
+
+ // Double check our bidi rules
+ BidiCategory testBidi = CharUnicodeInfo.GetBidiCategory(unicode, basicCount);
+
+ // If we're RTL, we can't have LTR chars
+ if (bRightToLeft && testBidi == BidiCategory.LeftToRight)
+ {
+ // Oops, throw error
+ throw new ArgumentException(SR.Argument_IdnBadBidi, nameof(unicode));
+ }
+
+ // If we're not RTL we can't have RTL chars
+ if (!bRightToLeft && (testBidi == BidiCategory.RightToLeft || testBidi == BidiCategory.RightToLeftArabic))
+ {
+ // Oops, throw error
+ throw new ArgumentException(SR.Argument_IdnBadBidi, nameof(unicode));
+ }
+
+ // If its basic then add it
+ if (Basic(unicode[basicCount]))
+ {
+ output.Append(EncodeBasic(unicode[basicCount]));
+ numProcessed++;
+ }
+ // If its a surrogate, skip the next since our bidi category tester doesn't handle it.
+ else if (Char.IsSurrogatePair(unicode, basicCount))
+ basicCount++;
+ }
+
+ int numBasicCodePoints = numProcessed; // number of basic code points
+
+ // Stop if we ONLY had basic code points
+ if (numBasicCodePoints == iNextDot - iAfterLastDot)
+ {
+ // Get rid of xn-- and this segments done
+ output.Remove(iOutputAfterLastDot, c_strAcePrefix.Length);
+ }
+ else
+ {
+ // If it has some non-basic code points the input cannot start with xn--
+ if (unicode.Length - iAfterLastDot >= c_strAcePrefix.Length &&
+ unicode.Substring(iAfterLastDot, c_strAcePrefix.Length).Equals(
+ c_strAcePrefix, StringComparison.OrdinalIgnoreCase))
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(unicode));
+
+ // Need to do ACE encoding
+ int numSurrogatePairs = 0; // number of surrogate pairs so far
+
+ // Add a delimiter (-) if we had any basic code points (between basic and encoded pieces)
+ if (numBasicCodePoints > 0)
+ {
+ output.Append(c_delimiter);
+ }
+
+ // Initialize the state
+ int n = c_initialN;
+ int delta = 0;
+ int bias = c_initialBias;
+
+ // Main loop
+ while (numProcessed < (iNextDot - iAfterLastDot))
+ {
+ /* All non-basic code points < n have been */
+ /* handled already. Find the next larger one: */
+ int j;
+ int m;
+ int test = 0;
+ for (m = c_maxint, j = iAfterLastDot;
+ j < iNextDot;
+ j += IsSupplementary(test) ? 2 : 1)
+ {
+ test = Char.ConvertToUtf32(unicode, j);
+ if (test >= n && test < m) m = test;
+ }
+
+ /* Increase delta enough to advance the decoder's */
+ /* <n,i> state to <m,0>, but guard against overflow: */
+ delta += (int)((m - n) * ((numProcessed - numSurrogatePairs) + 1));
+ Debug.Assert(delta > 0, "[IdnMapping.cs]1 punycode_encode - delta overflowed int");
+ n = m;
+
+ for (j = iAfterLastDot; j < iNextDot; j+= IsSupplementary(test) ? 2 : 1)
+ {
+ // Make sure we're aware of surrogates
+ test = Char.ConvertToUtf32(unicode, j);
+
+ // Adjust for character position (only the chars in our string already, some
+ // haven't been processed.
+
+ if (test < n)
+ {
+ delta++;
+ Debug.Assert(delta > 0, "[IdnMapping.cs]2 punycode_encode - delta overflowed int");
+ }
+
+ if (test == n)
+ {
+ // Represent delta as a generalized variable-length integer:
+ int q, k;
+ for (q = delta, k = c_punycodeBase; ; k += c_punycodeBase)
+ {
+ int t = k <= bias ? c_tmin : k >= bias + c_tmax ? c_tmax : k - bias;
+ if (q < t) break;
+ Debug.Assert(c_punycodeBase != t, "[IdnMapping.punycode_encode]Expected c_punycodeBase (36) to be != t");
+ output.Append(EncodeDigit(t + (q - t) % (c_punycodeBase - t)));
+ q = (q - t) / (c_punycodeBase - t);
+ }
+
+ output.Append(EncodeDigit(q));
+ bias = Adapt(delta, (numProcessed - numSurrogatePairs) + 1, numProcessed == numBasicCodePoints);
+ delta = 0;
+ numProcessed++;
+
+ if (IsSupplementary(m))
+ {
+ numProcessed++;
+ numSurrogatePairs++;
+ }
+ }
+ }
+ ++delta;
+ ++n;
+ Debug.Assert(delta > 0, "[IdnMapping.cs]3 punycode_encode - delta overflowed int");
+ }
+ }
+
+ // Make sure its not too big
+ if (output.Length - iOutputAfterLastDot > c_labelLimit)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode));
+
+ // Done with this segment, add dot if necessary
+ if (iNextDot != unicode.Length)
+ output.Append('.');
+
+ iAfterLastDot = iNextDot + 1;
+ iOutputAfterLastDot = output.Length;
+ }
+
+ // Throw if we're too long
+ if (output.Length > c_defaultNameLimit - (IsDot(unicode[unicode.Length-1]) ? 0 : 1))
+ throw new ArgumentException(SR.Format(SR.Argument_IdnBadNameSize,
+ c_defaultNameLimit - (IsDot(unicode[unicode.Length-1]) ? 0 : 1)), nameof(unicode));
+ // Return our output string
+ return output.ToString();
+ }
+
+ // Is it a dot?
+ // are we U+002E (., full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), or
+ // U+FF61 (halfwidth ideographic full stop).
+ // Note: IDNA Normalization gets rid of dots now, but testing for last dot is before normalization
+ private static bool IsDot(char c)
+ {
+ return c == '.' || c == '\u3002' || c == '\uFF0E' || c == '\uFF61';
+ }
+
+ private static bool IsSupplementary(int cTest)
+ {
+ return cTest >= 0x10000;
+ }
+
+ private static bool Basic(uint cp)
+ {
+ // Is it in ASCII range?
+ return cp < 0x80;
+ }
+
+ // Validate Std3 rules for a character
+ private static void ValidateStd3(char c, bool bNextToDot)
+ {
+ // Check for illegal characters
+ if ((c <= ',' || c == '/' || (c >= ':' && c <= '@') || // Lots of characters not allowed
+ (c >= '[' && c <= '`') || (c >= '{' && c <= (char)0x7F)) ||
+ (c == '-' && bNextToDot))
+ throw new ArgumentException(SR.Format(SR.Argument_IdnBadStd3, c), nameof(c));
+ }
+
+ private string GetUnicodeInvariant(string ascii, int index, int count)
+ {
+ if (index > 0 || count < ascii.Length)
+ {
+ // We're only using part of the string
+ ascii = ascii.Substring(index, count);
+ }
+ // Convert Punycode to Unicode
+ string strUnicode = PunycodeDecode(ascii);
+
+ // Output name MUST obey IDNA rules & round trip (casing differences are allowed)
+ if (!ascii.Equals(GetAscii(strUnicode), StringComparison.OrdinalIgnoreCase))
+ throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii));
+
+ return strUnicode;
+ }
+
+ /* PunycodeDecode() converts Punycode to Unicode. The input is */
+ /* represented as an array of ASCII code points, and the output */
+ /* will be represented as an array of Unicode code points. The */
+ /* input_length is the number of code points in the input. The */
+ /* output_length is an in/out argument: the caller passes in */
+ /* the maximum number of code points that it can receive, and */
+ /* on successful return it will contain the actual number of */
+ /* code points output. The case_flags array needs room for at */
+ /* least output_length values, or it can be a null pointer if the */
+ /* case information is not needed. A nonzero flag suggests that */
+ /* the corresponding Unicode character be forced to uppercase */
+ /* by the caller (if possible), while zero suggests that it be */
+ /* forced to lowercase (if possible). ASCII code points are */
+ /* output already in the proper case, but their flags will be set */
+ /* appropriately so that applying the flags would be harmless. */
+ /* The return value can be any of the punycode_status values */
+ /* defined above; if not punycode_success, then output_length, */
+ /* output, and case_flags might contain garbage. On success, the */
+ /* decoder will never need to write an output_length greater than */
+ /* input_length, because of how the encoding is defined. */
+
+ private static string PunycodeDecode(string ascii)
+ {
+ // 0 length strings aren't allowed
+ if (ascii.Length == 0)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(ascii));
+
+ // Throw if we're too long
+ if (ascii.Length > c_defaultNameLimit - (IsDot(ascii[ascii.Length-1]) ? 0 : 1))
+ throw new ArgumentException(SR.Format(SR.Argument_IdnBadNameSize,
+ c_defaultNameLimit - (IsDot(ascii[ascii.Length-1]) ? 0 : 1)), nameof(ascii));
+
+ // output stringbuilder
+ StringBuilder output = new StringBuilder(ascii.Length);
+
+ // Dot searching
+ int iNextDot = 0;
+ int iAfterLastDot = 0;
+ int iOutputAfterLastDot = 0;
+
+ while (iNextDot < ascii.Length)
+ {
+ // Find end of this segment
+ iNextDot = ascii.IndexOf('.', iAfterLastDot);
+ if (iNextDot < 0 || iNextDot > ascii.Length)
+ iNextDot = ascii.Length;
+
+ // Only allowed to have empty . section at end (www.microsoft.com.)
+ if (iNextDot == iAfterLastDot)
+ {
+ // Only allowed to have empty sections as trailing .
+ if (iNextDot != ascii.Length)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(ascii));
+
+ // Last dot, stop
+ break;
+ }
+
+ // In either case it can't be bigger than segment size
+ if (iNextDot - iAfterLastDot > c_labelLimit)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(ascii));
+
+ // See if this section's ASCII or ACE
+ if (ascii.Length < c_strAcePrefix.Length + iAfterLastDot ||
+ !ascii.Substring(iAfterLastDot,c_strAcePrefix.Length).Equals(c_strAcePrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ // Its ASCII, copy it
+ output.Append(ascii.Substring(iAfterLastDot, iNextDot - iAfterLastDot));
+ }
+ else
+ {
+ // Not ASCII, bump up iAfterLastDot to be after ACE Prefix
+ iAfterLastDot += c_strAcePrefix.Length;
+
+ // Get number of basic code points (where delimiter is)
+ // numBasicCodePoints < 0 if there're no basic code points
+ int iTemp = ascii.LastIndexOf(c_delimiter, iNextDot - 1);
+
+ // Trailing - not allowed
+ if (iTemp == iNextDot - 1)
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+
+ int numBasicCodePoints;
+ if (iTemp <= iAfterLastDot)
+ numBasicCodePoints = 0;
+ else
+ {
+ numBasicCodePoints = iTemp - iAfterLastDot;
+
+ // Copy all the basic code points, making sure they're all in the allowed range,
+ // and losing the casing for all of them.
+ for (int copyAscii = iAfterLastDot; copyAscii < iAfterLastDot + numBasicCodePoints; copyAscii++)
+ {
+ // Make sure we don't allow unicode in the ascii part
+ if (ascii[copyAscii] > 0x7f)
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+
+ // When appending make sure they get lower cased
+ output.Append((char)(ascii[copyAscii] >= 'A' && ascii[copyAscii] <='Z' ? ascii[copyAscii] - 'A' + 'a' : ascii[copyAscii]));
+ }
+ }
+
+ // Get ready for main loop. Start at beginning if we didn't have any
+ // basic code points, otherwise start after the -.
+ // asciiIndex will be next character to read from ascii
+ int asciiIndex = iAfterLastDot + (numBasicCodePoints > 0 ? numBasicCodePoints + 1 : 0);
+
+ // initialize our state
+ int n = c_initialN;
+ int bias = c_initialBias;
+ int i = 0;
+
+ int w, k;
+
+ // no Supplementary characters yet
+ int numSurrogatePairs = 0;
+
+ // Main loop, read rest of ascii
+ while (asciiIndex < iNextDot)
+ {
+ /* Decode a generalized variable-length integer into delta, */
+ /* which gets added to i. The overflow checking is easier */
+ /* if we increase i as we go, then subtract off its starting */
+ /* value at the end to obtain delta. */
+ int oldi = i;
+
+ for (w = 1, k = c_punycodeBase; ; k += c_punycodeBase)
+ {
+ // Check to make sure we aren't overrunning our ascii string
+ if (asciiIndex >= iNextDot)
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+
+ // decode the digit from the next char
+ int digit = DecodeDigit(ascii[asciiIndex++]);
+
+ Debug.Assert(w > 0, "[IdnMapping.punycode_decode]Expected w > 0");
+ if (digit > (c_maxint - i) / w)
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+
+ i += (int)(digit * w);
+ int t = k <= bias ? c_tmin : k >= bias + c_tmax ? c_tmax : k - bias;
+ if (digit < t)
+ break;
+ Debug.Assert(c_punycodeBase != t, "[IdnMapping.punycode_decode]Expected t != c_punycodeBase (36)");
+ if (w > c_maxint / (c_punycodeBase - t))
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+ w *= (c_punycodeBase - t);
+ }
+
+ bias = Adapt(i - oldi, (output.Length - iOutputAfterLastDot - numSurrogatePairs) + 1, oldi == 0);
+
+ /* i was supposed to wrap around from output.Length to 0, */
+ /* incrementing n each time, so we'll fix that now: */
+ Debug.Assert((output.Length - iOutputAfterLastDot - numSurrogatePairs) + 1 > 0,
+ "[IdnMapping.punycode_decode]Expected to have added > 0 characters this segment");
+ if (i / ((output.Length - iOutputAfterLastDot - numSurrogatePairs) + 1) > c_maxint - n)
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+ n += (int)(i / (output.Length - iOutputAfterLastDot - numSurrogatePairs + 1));
+ i %= (output.Length - iOutputAfterLastDot - numSurrogatePairs + 1);
+
+ // Make sure n is legal
+ if ((n < 0 || n > 0x10ffff) || (n >= 0xD800 && n <= 0xDFFF))
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+
+ // insert n at position i of the output: Really tricky if we have surrogates
+ int iUseInsertLocation;
+ String strTemp = Char.ConvertFromUtf32(n);
+
+ // If we have supplimentary characters
+ if (numSurrogatePairs > 0)
+ {
+ // Hard way, we have supplimentary characters
+ int iCount;
+ for (iCount = i, iUseInsertLocation = iOutputAfterLastDot; iCount > 0; iCount--, iUseInsertLocation++)
+ {
+ // If its a surrogate, we have to go one more
+ if (iUseInsertLocation >= output.Length)
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii));
+ if (Char.IsSurrogate(output[iUseInsertLocation]))
+ iUseInsertLocation++;
+ }
+ }
+ else
+ {
+ // No Supplementary chars yet, just add i
+ iUseInsertLocation = iOutputAfterLastDot + i;
+ }
+
+ // Insert it
+ output.Insert(iUseInsertLocation, strTemp);
+
+ // If it was a surrogate increment our counter
+ if (IsSupplementary(n))
+ numSurrogatePairs++;
+
+ // Index gets updated
+ i++;
+ }
+
+ // Do BIDI testing
+ bool bRightToLeft = false;
+
+ // Check for RTL. If right-to-left, then 1st & last chars must be RTL
+ BidiCategory eBidi = CharUnicodeInfo.GetBidiCategory(output.ToString(), iOutputAfterLastDot);
+ if (eBidi == BidiCategory.RightToLeft || eBidi == BidiCategory.RightToLeftArabic)
+ {
+ // It has to be right to left.
+ bRightToLeft = true;
+ }
+
+ // Check the rest of them to make sure RTL/LTR is consistent
+ for (int iTest = iOutputAfterLastDot; iTest < output.Length; iTest++)
+ {
+ // This might happen if we run into a pair
+ if (Char.IsLowSurrogate(output.ToString(), iTest))
+ continue;
+
+ // Check to see if its LTR
+ eBidi = CharUnicodeInfo.GetBidiCategory(output.ToString(), iTest);
+ if ((bRightToLeft && eBidi == BidiCategory.LeftToRight) ||
+ (!bRightToLeft && (eBidi == BidiCategory.RightToLeft || eBidi == BidiCategory.RightToLeftArabic)))
+ throw new ArgumentException(SR.Argument_IdnBadBidi, nameof(ascii));
+ }
+
+ // Its also a requirement that the last one be RTL if 1st is RTL
+ if (bRightToLeft && eBidi != BidiCategory.RightToLeft && eBidi != BidiCategory.RightToLeftArabic)
+ {
+ // Oops, last wasn't RTL, last should be RTL if first is RTL
+ throw new ArgumentException(SR.Argument_IdnBadBidi, nameof(ascii));
+ }
+ }
+
+ // See if this label was too long
+ if (iNextDot - iAfterLastDot > c_labelLimit)
+ throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(ascii));
+
+ // Done with this segment, add dot if necessary
+ if (iNextDot != ascii.Length)
+ output.Append('.');
+
+ iAfterLastDot = iNextDot + 1;
+ iOutputAfterLastDot = output.Length;
+ }
+
+ // Throw if we're too long
+ if (output.Length > c_defaultNameLimit - (IsDot(output[output.Length-1]) ? 0 : 1))
+ throw new ArgumentException(SR.Format(SR.Argument_IdnBadNameSize, c_defaultNameLimit - (IsDot(output[output.Length-1]) ? 0 : 1)), nameof(ascii));
+
+ // Return our output string
+ return output.ToString();
+ }
+
+ // DecodeDigit(cp) returns the numeric value of a basic code */
+ // point (for use in representing integers) in the range 0 to */
+ // c_punycodeBase-1, or <0 if cp is does not represent a value. */
+
+ private static int DecodeDigit(char cp)
+ {
+ if (cp >= '0' && cp <= '9')
+ return cp - '0' + 26;
+
+ // Two flavors for case differences
+ if (cp >= 'a' && cp <= 'z')
+ return cp - 'a';
+
+ if (cp >= 'A' && cp <= 'Z')
+ return cp - 'A';
+
+ // Expected 0-9, A-Z or a-z, everything else is illegal
+ throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(cp));
+ }
+
+ private static int Adapt(int delta, int numpoints, bool firsttime)
+ {
+ uint k;
+
+ delta = firsttime ? delta / c_damp : delta / 2;
+ Debug.Assert(numpoints != 0, "[IdnMapping.adapt]Expected non-zero numpoints.");
+ delta += delta / numpoints;
+
+ for (k = 0; delta > ((c_punycodeBase - c_tmin) * c_tmax) / 2; k += c_punycodeBase)
+ {
+ delta /= c_punycodeBase - c_tmin;
+ }
+
+ Debug.Assert(delta + c_skew != 0, "[IdnMapping.adapt]Expected non-zero delta+skew.");
+ return (int)(k + (c_punycodeBase - c_tmin + 1) * delta / (delta + c_skew));
+ }
+
+ /* EncodeBasic(bcp,flag) forces a basic code point to lowercase */
+ /* if flag is false, uppercase if flag is true, and returns */
+ /* the resulting code point. The code point is unchanged if it */
+ /* is caseless. The behavior is undefined if bcp is not a basic */
+ /* code point. */
+
+ static char EncodeBasic(char bcp)
+ {
+ if (HasUpperCaseFlag(bcp))
+ bcp += (char)('a' - 'A');
+
+ return bcp;
+ }
+
+ // Return whether a punycode code point is flagged as being upper case.
+ private static bool HasUpperCaseFlag(char punychar)
+ {
+ return (punychar >= 'A' && punychar <= 'Z');
+ }
+
+ /* EncodeDigit(d,flag) returns the basic code point whose value */
+ /* (when used for representing integers) is d, which needs to be in */
+ /* the range 0 to punycodeBase-1. The lowercase form is used unless flag is */
+ /* true, in which case the uppercase form is used. */
+
+ private static char EncodeDigit(int d)
+ {
+ Debug.Assert(d >= 0 && d < c_punycodeBase, "[IdnMapping.encode_digit]Expected 0 <= d < punycodeBase");
+ // 26-35 map to ASCII 0-9
+ if (d > 25) return (char)(d - 26 + '0');
+
+ // 0-25 map to a-z or A-Z
+ return (char)(d + 'a');
+ }
+
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/InternalGlobalizationHelper.cs b/src/System.Private.CoreLib/shared/System/Globalization/InternalGlobalizationHelper.cs
new file mode 100644
index 0000000000..60abcecf61
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/InternalGlobalizationHelper.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ internal class InternalGlobalizationHelper
+ {
+ // Copied from the TimeSpan to be used inside the globalization code and avoid internal dependency on TimeSpan class
+ internal static long TimeToTicks(int hour, int minute, int second)
+ {
+ // totalSeconds is bounded by 2^31 * 2^12 + 2^31 * 2^8 + 2^31,
+ // which is less than 2^44, meaning we won't overflow totalSeconds.
+ long totalSeconds = (long)hour * 3600 + (long)minute * 60 + (long)second;
+ if (totalSeconds > MaxSeconds || totalSeconds < MinSeconds)
+ throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong);
+ return totalSeconds * TicksPerSecond;
+ }
+
+
+ //
+ // Define needed constants so globalization code can be independant from any other types
+ //
+
+ internal const long TicksPerMillisecond = 10000;
+ internal const long TicksPerTenthSecond = TicksPerMillisecond * 100;
+ internal const long TicksPerSecond = TicksPerMillisecond * 1000; // 10,000,000
+ internal const long MaxSeconds = Int64.MaxValue / TicksPerSecond;
+ internal const long MinSeconds = Int64.MinValue / TicksPerSecond;
+ private const int DaysPerYear = 365;
+ private const int DaysPer4Years = DaysPerYear * 4 + 1; // 1461
+ private const int DaysPer100Years = DaysPer4Years * 25 - 1; // 36524
+ private const int DaysPer400Years = DaysPer100Years * 4 + 1; // 146097
+ private const int DaysTo10000 = DaysPer400Years * 25 - 366; // 3652059
+ private const long TicksPerMinute = TicksPerSecond * 60;
+ private const long TicksPerHour = TicksPerMinute * 60;
+ private const long TicksPerDay = TicksPerHour * 24;
+ internal const long MaxTicks = DaysTo10000 * TicksPerDay - 1;
+ internal const long MinTicks = 0;
+ internal const long MaxMilliSeconds = Int64.MaxValue / TicksPerMillisecond;
+ internal const long MinMilliSeconds = Int64.MinValue / TicksPerMillisecond;
+
+ internal const int StringBuilderDefaultCapacity = 16;
+
+ internal const Int64 MaxOffset = TimeSpan.TicksPerHour * 14;
+ internal const Int64 MinOffset = -MaxOffset;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Unix.cs
new file mode 100644
index 0000000000..5e66c94b2c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Unix.cs
@@ -0,0 +1,96 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ public partial class JapaneseCalendar : Calendar
+ {
+ private static EraInfo[] GetJapaneseEras()
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ return null;
+ }
+
+ string[] eraNames;
+ if (!CalendarData.EnumCalendarInfo("ja-JP", CalendarId.JAPAN, CalendarDataType.EraNames, out eraNames))
+ {
+ return null;
+ }
+
+ string[] abbrevEnglishEraNames;
+ if (!CalendarData.EnumCalendarInfo("en", CalendarId.JAPAN, CalendarDataType.AbbrevEraNames, out abbrevEnglishEraNames))
+ {
+ return null;
+ }
+
+ List<EraInfo> eras = new List<EraInfo>();
+ int lastMaxYear = GregorianCalendar.MaxYear;
+
+ int latestEra = Interop.Globalization.GetLatestJapaneseEra();
+ for (int i = latestEra; i >= 0; i--)
+ {
+ DateTime dt;
+ if (!GetJapaneseEraStartDate(i, out dt))
+ {
+ return null;
+ }
+
+ if (dt < JapaneseCalendar.calendarMinValue)
+ {
+ // only populate the Eras that are valid JapaneseCalendar date times
+ break;
+ }
+
+ eras.Add(new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1,
+ eraNames[i], GetAbbreviatedEraName(eraNames, i), abbrevEnglishEraNames[i]));
+
+ lastMaxYear = dt.Year;
+ }
+
+ // remap the Era numbers, now that we know how many there will be
+ for (int i = 0; i < eras.Count; i++)
+ {
+ eras[i].era = eras.Count - i;
+ }
+
+ return eras.ToArray();
+ }
+
+ // PAL Layer ends here
+
+ private static string GetAbbreviatedEraName(string[] eraNames, int eraIndex)
+ {
+ // This matches the behavior on Win32 - only returning the first character of the era name.
+ // See Calendar.EraAsString(Int32) - https://msdn.microsoft.com/en-us/library/windows/apps/br206751.aspx
+ return eraNames[eraIndex].Substring(0, 1);
+ }
+
+ private static bool GetJapaneseEraStartDate(int era, out DateTime dateTime)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ dateTime = default(DateTime);
+
+ int startYear;
+ int startMonth;
+ int startDay;
+ bool result = Interop.Globalization.GetJapaneseEraStartDate(
+ era,
+ out startYear,
+ out startMonth,
+ out startDay);
+
+ if (result)
+ {
+ dateTime = new DateTime(startYear, startMonth, startDay);
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs
new file mode 100644
index 0000000000..1d0180b00e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.Win32.cs
@@ -0,0 +1,210 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+using Microsoft.Win32;
+
+namespace System.Globalization
+{
+ public partial class JapaneseCalendar : Calendar
+ {
+ private const string c_japaneseErasHive = @"System\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras";
+ private const string c_japaneseErasHivePermissionList = @"HKEY_LOCAL_MACHINE\" + c_japaneseErasHive;
+
+ // We know about 4 built-in eras, however users may add additional era(s) from the
+ // registry, by adding values to HKLM\SYSTEM\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras
+ //
+ // Registry values look like:
+ // yyyy.mm.dd=era_abbrev_english_englishabbrev
+ //
+ // Where yyyy.mm.dd is the registry value name, and also the date of the era start.
+ // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long)
+ // era is the Japanese Era name
+ // abbrev is the Abbreviated Japanese Era Name
+ // english is the English name for the Era (unused)
+ // englishabbrev is the Abbreviated English name for the era.
+ // . is a delimiter, but the value of . doesn't matter.
+ // '_' marks the space between the japanese era name, japanese abbreviated era name
+ // english name, and abbreviated english names.
+ private static EraInfo[] GetJapaneseEras()
+ {
+ // Look in the registry key and see if we can find any ranges
+ int iFoundEras = 0;
+ EraInfo[] registryEraRanges = null;
+
+ try
+ {
+ // Need to access registry
+ RegistryKey key = RegistryKey.GetBaseKey(RegistryKey.HKEY_LOCAL_MACHINE).OpenSubKey(c_japaneseErasHive, false);
+
+ // Abort if we didn't find anything
+ if (key == null) return null;
+
+ // Look up the values in our reg key
+ String[] valueNames = key.GetValueNames();
+ if (valueNames != null && valueNames.Length > 0)
+ {
+ registryEraRanges = new EraInfo[valueNames.Length];
+
+ // Loop through the registry and read in all the values
+ for (int i = 0; i < valueNames.Length; i++)
+ {
+ // See if the era is a valid date
+ EraInfo era = GetEraFromValue(valueNames[i], key.GetValue(valueNames[i]).ToString());
+
+ // continue if not valid
+ if (era == null) continue;
+
+ // Remember we found one.
+ registryEraRanges[iFoundEras] = era;
+ iFoundEras++;
+ }
+ }
+ }
+ catch (System.Security.SecurityException)
+ {
+ // If we weren't allowed to read, then just ignore the error
+ return null;
+ }
+ catch (System.IO.IOException)
+ {
+ // If key is being deleted just ignore the error
+ return null;
+ }
+ catch (System.UnauthorizedAccessException)
+ {
+ // Registry access rights permissions, just ignore the error
+ return null;
+ }
+
+ //
+ // If we didn't have valid eras, then fail
+ // should have at least 4 eras
+ //
+ if (iFoundEras < 4) return null;
+
+ //
+ // Now we have eras, clean them up.
+ //
+ // Clean up array length
+ Array.Resize(ref registryEraRanges, iFoundEras);
+
+ // Sort them
+ Array.Sort(registryEraRanges, CompareEraRanges);
+
+ // Clean up era information
+ for (int i = 0; i < registryEraRanges.Length; i++)
+ {
+ // eras count backwards from length to 1 (and are 1 based indexes into string arrays)
+ registryEraRanges[i].era = registryEraRanges.Length - i;
+
+ // update max era year
+ if (i == 0)
+ {
+ // First range is 'til the end of the calendar
+ registryEraRanges[0].maxEraYear = GregorianCalendar.MaxYear - registryEraRanges[0].yearOffset;
+ }
+ else
+ {
+ // Rest are until the next era (remember most recent era is first in array)
+ registryEraRanges[i].maxEraYear = registryEraRanges[i - 1].yearOffset + 1 - registryEraRanges[i].yearOffset;
+ }
+ }
+
+ // Return our ranges
+ return registryEraRanges;
+ }
+
+ //
+ // Compare two era ranges, eg just the ticks
+ // Remember the era array is supposed to be in reverse chronological order
+ //
+ private static int CompareEraRanges(EraInfo a, EraInfo b)
+ {
+ return b.ticks.CompareTo(a.ticks);
+ }
+
+ //
+ // GetEraFromValue
+ //
+ // Parse the registry value name/data pair into an era
+ //
+ // Registry values look like:
+ // yyyy.mm.dd=era_abbrev_english_englishabbrev
+ //
+ // Where yyyy.mm.dd is the registry value name, and also the date of the era start.
+ // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long)
+ // era is the Japanese Era name
+ // abbrev is the Abbreviated Japanese Era Name
+ // english is the English name for the Era (unused)
+ // englishabbrev is the Abbreviated English name for the era.
+ // . is a delimiter, but the value of . doesn't matter.
+ // '_' marks the space between the japanese era name, japanese abbreviated era name
+ // english name, and abbreviated english names.
+ private static EraInfo GetEraFromValue(String value, String data)
+ {
+ // Need inputs
+ if (value == null || data == null) return null;
+
+ //
+ // Get Date
+ //
+ // Need exactly 10 characters in name for date
+ // yyyy.mm.dd although the . can be any character
+ if (value.Length != 10) return null;
+
+ int year;
+ int month;
+ int day;
+
+ ReadOnlySpan<char> valueSpan = value.AsSpan();
+ if (!Int32.TryParse(valueSpan.Slice(0, 4), NumberStyles.None, NumberFormatInfo.InvariantInfo, out year) ||
+ !Int32.TryParse(valueSpan.Slice(5, 2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out month) ||
+ !Int32.TryParse(valueSpan.Slice(8, 2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out day))
+ {
+ // Couldn't convert integer, fail
+ return null;
+ }
+
+ //
+ // Get Strings
+ //
+ // Needs to be a certain length e_a_E_A at least (7 chars, exactly 4 groups)
+ String[] names = data.Split('_');
+
+ // Should have exactly 4 parts
+ // 0 - Era Name
+ // 1 - Abbreviated Era Name
+ // 2 - English Era Name
+ // 3 - Abbreviated English Era Name
+ if (names.Length != 4) return null;
+
+ // Each part should have data in it
+ if (names[0].Length == 0 ||
+ names[1].Length == 0 ||
+ names[2].Length == 0 ||
+ names[3].Length == 0)
+ return null;
+
+ //
+ // Now we have an era we can build
+ // Note that the era # and max era year need cleaned up after sorting
+ // Don't use the full English Era Name (names[2])
+ //
+ return new EraInfo(0, year, month, day, year - 1, 1, 0,
+ names[0], names[1], names[3]);
+ }
+
+ // PAL Layer ends here
+
+ private static string[] s_japaneseErasEnglishNames = new String[] { "M", "T", "S", "H" };
+
+ private static string GetJapaneseEnglishEraName(int era)
+ {
+ Debug.Assert(era > 0);
+ return era <= s_japaneseErasEnglishNames.Length ? s_japaneseErasEnglishNames[era - 1] : " ";
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.WinRT.cs b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.WinRT.cs
new file mode 100644
index 0000000000..6a9df97200
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.WinRT.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+using Internal.Runtime.Augments;
+
+namespace System.Globalization
+{
+ public partial class JapaneseCalendar : Calendar
+ {
+ private static EraInfo[] GetJapaneseEras()
+ {
+ int erasCount = WinRTInterop.Callbacks.GetJapaneseEraCount();
+ if (erasCount < 4)
+ {
+ return null;
+ }
+
+ EraInfo[] eras = new EraInfo[erasCount];
+ int lastMaxYear = GregorianCalendar.MaxYear;
+
+ for (int i = erasCount; i > 0; i--)
+ {
+ DateTimeOffset dateOffset;
+
+ string eraName;
+ string abbreviatedEraName;
+
+ if (!GetJapaneseEraInfo(i, out dateOffset, out eraName, out abbreviatedEraName))
+ {
+ return null;
+ }
+
+ DateTime dt = new DateTime(dateOffset.Ticks);
+
+ eras[erasCount - i] = new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1,
+ eraName, abbreviatedEraName, GetJapaneseEnglishEraName(i)); // era #4 start year/month/day, yearOffset, minEraYear
+
+ lastMaxYear = dt.Year;
+ }
+
+ return eras;
+ }
+
+ // PAL Layer ends here
+
+ private static string[] JapaneseErasEnglishNames = new String[] { "M", "T", "S", "H" };
+
+ private static string GetJapaneseEnglishEraName(int era)
+ {
+ Debug.Assert(era > 0);
+ return era <= JapaneseErasEnglishNames.Length ? JapaneseErasEnglishNames[era - 1] : " ";
+ }
+
+ private static bool GetJapaneseEraInfo(int era, out DateTimeOffset dateOffset, out string eraName, out string abbreviatedEraName)
+ {
+ return WinRTInterop.Callbacks.GetJapaneseEraInfo(era, out dateOffset, out eraName, out abbreviatedEraName);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.cs
new file mode 100644
index 0000000000..50195d7f1c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseCalendar.cs
@@ -0,0 +1,405 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Globalization
+{
+ /*=================================JapaneseCalendar==========================
+ **
+ ** JapaneseCalendar is based on Gregorian calendar. The month and day values are the same as
+ ** Gregorian calendar. However, the year value is an offset to the Gregorian
+ ** year based on the era.
+ **
+ ** This system is adopted by Emperor Meiji in 1868. The year value is counted based on the reign of an emperor,
+ ** and the era begins on the day an emperor ascends the throne and continues until his death.
+ ** The era changes at 12:00AM.
+ **
+ ** For example, the current era is Heisei. It started on 1989/1/8 A.D. Therefore, Gregorian year 1989 is also Heisei 1st.
+ ** 1989/1/8 A.D. is also Heisei 1st 1/8.
+ **
+ ** Any date in the year during which era is changed can be reckoned in either era. For example,
+ ** 1989/1/1 can be 1/1 Heisei 1st year or 1/1 Showa 64th year.
+ **
+ ** Note:
+ ** The DateTime can be represented by the JapaneseCalendar are limited to two factors:
+ ** 1. The min value and max value of DateTime class.
+ ** 2. The available era information.
+ **
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1868/09/08 9999/12/31
+ ** Japanese Meiji 01/01 Heisei 8011/12/31
+ ============================================================================*/
+
+
+ public partial class JapaneseCalendar : Calendar
+ {
+ internal static readonly DateTime calendarMinValue = new DateTime(1868, 9, 8);
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ //
+ // Using a field initializer rather than a static constructor so that the whole class can be lazy
+ // init.
+ internal static volatile EraInfo[] japaneseEraInfo;
+
+ //
+ // Read our era info
+ //
+ // m_EraInfo must be listed in reverse chronological order. The most recent era
+ // should be the first element.
+ // That is, m_EraInfo[0] contains the most recent era.
+ //
+ // We know about 4 built-in eras, however users may add additional era(s) from the
+ // registry, by adding values to HKLM\SYSTEM\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras
+ // we don't read the registry and instead we call WinRT to get the needed informatio
+ //
+ // Registry values look like:
+ // yyyy.mm.dd=era_abbrev_english_englishabbrev
+ //
+ // Where yyyy.mm.dd is the registry value name, and also the date of the era start.
+ // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long)
+ // era is the Japanese Era name
+ // abbrev is the Abbreviated Japanese Era Name
+ // english is the English name for the Era (unused)
+ // englishabbrev is the Abbreviated English name for the era.
+ // . is a delimiter, but the value of . doesn't matter.
+ // '_' marks the space between the japanese era name, japanese abbreviated era name
+ // english name, and abbreviated english names.
+ //
+ internal static EraInfo[] GetEraInfo()
+ {
+ // See if we need to build it
+ if (japaneseEraInfo == null)
+ {
+ japaneseEraInfo = GetJapaneseEras();
+ // See if we have to use the built-in eras
+ if (japaneseEraInfo == null)
+ {
+ // We know about some built-in ranges
+ EraInfo[] defaultEraRanges = new EraInfo[4];
+ defaultEraRanges[0] = new EraInfo(4, 1989, 1, 8, 1988, 1, GregorianCalendar.MaxYear - 1988,
+ "\x5e73\x6210", "\x5e73", "H"); // era #4 start year/month/day, yearOffset, minEraYear
+ defaultEraRanges[1] = new EraInfo(3, 1926, 12, 25, 1925, 1, 1989 - 1925,
+ "\x662d\x548c", "\x662d", "S"); // era #3,start year/month/day, yearOffset, minEraYear
+ defaultEraRanges[2] = new EraInfo(2, 1912, 7, 30, 1911, 1, 1926 - 1911,
+ "\x5927\x6b63", "\x5927", "T"); // era #2,start year/month/day, yearOffset, minEraYear
+ defaultEraRanges[3] = new EraInfo(1, 1868, 1, 1, 1867, 1, 1912 - 1867,
+ "\x660e\x6cbb", "\x660e", "M"); // era #1,start year/month/day, yearOffset, minEraYear
+
+ // Remember the ranges we built
+ japaneseEraInfo = defaultEraRanges;
+ }
+ }
+
+ // return the era we found/made
+ return japaneseEraInfo;
+ }
+
+ internal static volatile Calendar s_defaultInstance;
+ internal GregorianCalendarHelper helper;
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of JapaneseCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ internal static Calendar GetDefaultInstance()
+ {
+ if (s_defaultInstance == null)
+ {
+ s_defaultInstance = new JapaneseCalendar();
+ }
+ return (s_defaultInstance);
+ }
+
+
+ public JapaneseCalendar()
+ {
+ try
+ {
+ new CultureInfo("ja-JP");
+ }
+ catch (ArgumentException e)
+ {
+ throw new TypeInitializationException(this.GetType().ToString(), e);
+ }
+ helper = new GregorianCalendarHelper(this, GetEraInfo());
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return CalendarId.JAPAN;
+ }
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ return (helper.AddMonths(time, months));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (helper.AddYears(time, years));
+ }
+
+ /*=================================GetDaysInMonth==========================
+ **Action: Returns the number of days in the month given by the year and month arguments.
+ **Returns: The number of days in the given month.
+ **Arguments:
+ ** year The year in Japanese calendar.
+ ** month The month
+ ** era The Japanese era value.
+ **Exceptions
+ ** ArgumentException If month is less than 1 or greater * than 12.
+ ============================================================================*/
+
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ return (helper.GetDaysInMonth(year, month, era));
+ }
+
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ return (helper.GetDaysInYear(year, era));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (helper.GetDayOfMonth(time));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return (helper.GetDayOfWeek(time));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (helper.GetDayOfYear(time));
+ }
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ return (helper.GetMonthsInYear(year, era));
+ }
+
+
+ public override int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ return (helper.GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+ /*=================================GetEra==========================
+ **Action: Get the era value of the specified time.
+ **Returns: The era value for the specified time.
+ **Arguments:
+ ** time the specified date time.
+ **Exceptions: ArgumentOutOfRangeException if time is out of the valid era ranges.
+ ============================================================================*/
+
+
+ public override int GetEra(DateTime time)
+ {
+ return (helper.GetEra(time));
+ }
+
+
+ public override int GetMonth(DateTime time)
+ {
+ return (helper.GetMonth(time));
+ }
+
+
+ public override int GetYear(DateTime time)
+ {
+ return (helper.GetYear(time));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ return (helper.IsLeapDay(year, month, day, era));
+ }
+
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ return (helper.IsLeapYear(year, era));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ return (helper.GetLeapMonth(year, era));
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ return (helper.IsLeapMonth(year, month, era));
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ return (helper.ToDateTime(year, month, day, hour, minute, second, millisecond, era));
+ }
+
+ // For Japanese calendar, four digit year is not used. Few emperors will live for more than one hundred years.
+ // Therefore, for any two digit number, we just return the original number.
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedPosNum);
+ }
+
+ if (year > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ helper.MaxYear));
+ }
+ return (year);
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (helper.Eras);
+ }
+ }
+
+ //
+ // Return the various era strings
+ // Note: The arrays are backwards of the eras
+ //
+ internal static String[] EraNames()
+ {
+ EraInfo[] eras = GetEraInfo();
+ String[] eraNames = new String[eras.Length];
+
+ for (int i = 0; i < eras.Length; i++)
+ {
+ // Strings are in chronological order, eras are backwards order.
+ eraNames[i] = eras[eras.Length - i - 1].eraName;
+ }
+
+ return eraNames;
+ }
+
+ internal static String[] AbbrevEraNames()
+ {
+ EraInfo[] eras = GetEraInfo();
+ String[] erasAbbrev = new String[eras.Length];
+
+ for (int i = 0; i < eras.Length; i++)
+ {
+ // Strings are in chronological order, eras are backwards order.
+ erasAbbrev[i] = eras[eras.Length - i - 1].abbrevEraName;
+ }
+
+ return erasAbbrev;
+ }
+
+ internal static String[] EnglishEraNames()
+ {
+ EraInfo[] eras = GetEraInfo();
+ String[] erasEnglish = new String[eras.Length];
+
+ for (int i = 0; i < eras.Length; i++)
+ {
+ // Strings are in chronological order, eras are backwards order.
+ erasEnglish[i] = eras[eras.Length - i - 1].englishEraName;
+ }
+
+ return erasEnglish;
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 99;
+
+ internal override bool IsValidYear(int year, int era)
+ {
+ return helper.IsValidYear(year, era);
+ }
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ helper.MaxYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/JapaneseLunisolarCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseLunisolarCalendar.cs
new file mode 100644
index 0000000000..e8a2dcd637
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/JapaneseLunisolarCalendar.cs
@@ -0,0 +1,302 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization
+{
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1960/01/28 2050/01/22
+ ** JapaneseLunisolar 1960/01/01 2049/12/29
+ */
+
+ public class JapaneseLunisolarCalendar : EastAsianLunisolarCalendar
+ {
+ //
+ // The era value for the current era.
+ //
+
+ public const int JapaneseEra = 1;
+
+ internal GregorianCalendarHelper helper;
+
+ internal const int MIN_LUNISOLAR_YEAR = 1960;
+ internal const int MAX_LUNISOLAR_YEAR = 2049;
+
+ internal const int MIN_GREGORIAN_YEAR = 1960;
+ internal const int MIN_GREGORIAN_MONTH = 1;
+ internal const int MIN_GREGORIAN_DAY = 28;
+
+ internal const int MAX_GREGORIAN_YEAR = 2050;
+ internal const int MAX_GREGORIAN_MONTH = 1;
+ internal const int MAX_GREGORIAN_DAY = 22;
+
+ internal static DateTime minDate = new DateTime(MIN_GREGORIAN_YEAR, MIN_GREGORIAN_MONTH, MIN_GREGORIAN_DAY);
+ internal static DateTime maxDate = new DateTime((new DateTime(MAX_GREGORIAN_YEAR, MAX_GREGORIAN_MONTH, MAX_GREGORIAN_DAY, 23, 59, 59, 999)).Ticks + 9999);
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // 1959 from ChineseLunisolarCalendar
+ return 354;
+ }
+ }
+
+ private static readonly int[,] s_yinfo =
+ {
+ /*Y LM Lmon Lday DaysPerMonth D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 #Days
+ 1960 */
+ { 6 , 1 , 28 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1961 */{ 0 , 2 , 15 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1962 */{ 0 , 2 , 5 , 19808 },/* 29 30 29 29 30 30 29 30 29 30 30 29 0 354
+1963 */{ 4 , 1 , 25 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1964 */{ 0 , 2 , 13 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1965 */{ 0 , 2 , 2 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 0 354
+1966 */{ 3 , 1 , 22 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+1967 */{ 0 , 2 , 9 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1968 */{ 7 , 1 , 30 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1969 */{ 0 , 2 , 17 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1970 */{ 0 , 2 , 6 , 39632 },/* 30 29 29 30 30 29 30 29 30 30 29 30 0 355
+1971 */{ 5 , 1 , 27 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1972 */{ 0 , 2 , 15 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1973 */{ 0 , 2 , 3 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+1974 */{ 4 , 1 , 23 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1975 */{ 0 , 2 , 11 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1976 */{ 8 , 1 , 31 , 54600 },/* 30 30 29 30 29 30 29 30 29 30 29 29 30 384
+1977 */{ 0 , 2 , 18 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1978 */{ 0 , 2 , 7 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 0 355
+1979 */{ 6 , 1 , 28 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1980 */{ 0 , 2 , 16 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1981 */{ 0 , 2 , 5 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1982 */{ 4 , 1 , 25 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1983 */{ 0 , 2 , 13 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1984 */{ 10 , 2 , 2 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1985 */{ 0 , 2 , 20 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1986 */{ 0 , 2 , 9 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1987 */{ 6 , 1 , 29 , 46504 },/* 30 29 30 30 29 30 29 30 30 29 30 29 30 385
+1988 */{ 0 , 2 , 18 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1989 */{ 0 , 2 , 6 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1990 */{ 5 , 1 , 27 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1991 */{ 0 , 2 , 15 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1992 */{ 0 , 2 , 4 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1993 */{ 3 , 1 , 23 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1994 */{ 0 , 2 , 10 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1995 */{ 8 , 1 , 31 , 27976 },/* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1996 */{ 0 , 2 , 19 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1997 */{ 0 , 2 , 8 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1998 */{ 5 , 1 , 28 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1999 */{ 0 , 2 , 16 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2000 */{ 0 , 2 , 5 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2001 */{ 4 , 1 , 24 , 58536 },/* 30 30 30 29 29 30 29 29 30 29 30 29 30 384
+2002 */{ 0 , 2 , 12 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2003 */{ 0 , 2 , 1 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2004 */{ 2 , 1 , 22 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2005 */{ 0 , 2 , 9 , 22208 },/* 29 30 29 30 29 30 30 29 30 30 29 29 0 354
+2006 */{ 7 , 1 , 29 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+2007 */{ 0 , 2 , 18 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+2008 */{ 0 , 2 , 7 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+2009 */{ 5 , 1 , 26 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+2010 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2011 */{ 0 , 2 , 3 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2012 */{ 3 , 1 , 23 , 47696 },/* 30 29 30 30 30 29 30 29 29 30 29 30 29 384
+2013 */{ 0 , 2 , 10 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+2014 */{ 9 , 1 , 31 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2015 */{ 0 , 2 , 19 , 19360 },/* 29 30 29 29 30 29 30 30 30 29 30 29 0 354
+2016 */{ 0 , 2 , 8 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2017 */{ 5 , 1 , 28 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+2018 */{ 0 , 2 , 16 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2019 */{ 0 , 2 , 5 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2020 */{ 4 , 1 , 25 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+2021 */{ 0 , 2 , 12 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2022 */{ 0 , 2 , 1 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2023 */{ 2 , 1 , 22 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2024 */{ 0 , 2 , 10 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2025 */{ 6 , 1 , 29 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+2026 */{ 0 , 2 , 17 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+2027 */{ 0 , 2 , 7 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 0 354
+2028 */{ 5 , 1 , 27 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+2029 */{ 0 , 2 , 13 , 55600 },/* 30 30 29 30 30 29 29 30 29 29 30 30 0 355
+2030 */{ 0 , 2 , 3 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+2031 */{ 3 , 1 , 23 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+2032 */{ 0 , 2 , 11 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2033 */{ 11 , 1 , 31 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+2034 */{ 0 , 2 , 19 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+2035 */{ 0 , 2 , 8 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2036 */{ 6 , 1 , 28 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+2037 */{ 0 , 2 , 15 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+2038 */{ 0 , 2 , 4 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2039 */{ 5 , 1 , 24 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 29 384
+2040 */{ 0 , 2 , 12 , 46752 },/* 30 29 30 30 29 30 30 29 30 29 30 29 0 355
+2041 */{ 0 , 2 , 1 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2042 */{ 2 , 1 , 22 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+2043 */{ 0 , 2 , 10 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+2044 */{ 7 , 1 , 30 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+2045 */{ 0 , 2 , 17 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+2046 */{ 0 , 2 , 6 , 45648 },/* 30 29 30 30 29 29 30 29 29 30 29 30 0 354
+2047 */{ 5 , 1 , 26 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+2048 */{ 0 , 2 , 14 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+2049 */{ 0 , 2 , 2 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+ */ };
+
+ internal override int MinCalendarYear
+ {
+ get
+ {
+ return (MIN_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override int MaxCalendarYear
+ {
+ get
+ {
+ return (MAX_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override DateTime MinDate
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ internal override DateTime MaxDate
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ internal override EraInfo[] CalEraInfo
+ {
+ get
+ {
+ return (JapaneseCalendar.GetEraInfo());
+ }
+ }
+
+ internal override int GetYearInfo(int lunarYear, int index)
+ {
+ if ((lunarYear < MIN_LUNISOLAR_YEAR) || (lunarYear > MAX_LUNISOLAR_YEAR))
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MIN_LUNISOLAR_YEAR,
+ MAX_LUNISOLAR_YEAR));
+ }
+
+ return s_yinfo[lunarYear - MIN_LUNISOLAR_YEAR, index];
+ }
+
+ internal override int GetYear(int year, DateTime time)
+ {
+ return helper.GetYear(year, time);
+ }
+
+ internal override int GetGregorianYear(int year, int era)
+ {
+ return helper.GetGregorianYear(year, era);
+ }
+
+ // Trim off the eras that are before our date range
+ private static EraInfo[] TrimEras(EraInfo[] baseEras)
+ {
+ EraInfo[] newEras = new EraInfo[baseEras.Length];
+ int newIndex = 0;
+
+ // Eras have most recent first, so start with that
+ for (int i = 0; i < baseEras.Length; i++)
+ {
+ // If this one's minimum year is bigger than our maximum year
+ // then we can't use it.
+ if (baseEras[i].yearOffset + baseEras[i].minEraYear >= MAX_LUNISOLAR_YEAR)
+ {
+ // skip this one.
+ continue;
+ }
+
+ // If this one's maximum era is less than our minimum era
+ // then we've gotten too low in the era #s, so we're done
+ if (baseEras[i].yearOffset + baseEras[i].maxEraYear < MIN_LUNISOLAR_YEAR)
+ {
+ break;
+ }
+
+ // Wasn't too large or too small, can use this one
+ newEras[newIndex] = baseEras[i];
+ newIndex++;
+ }
+
+ // If we didn't copy any then something was wrong, just return base
+ if (newIndex == 0) return baseEras;
+
+ // Resize the output array
+ Array.Resize(ref newEras, newIndex);
+ return newEras;
+ }
+
+
+ // Construct an instance of JapaneseLunisolar calendar.
+ public JapaneseLunisolarCalendar()
+ {
+ helper = new GregorianCalendarHelper(this, TrimEras(JapaneseCalendar.GetEraInfo()));
+ }
+
+
+ public override int GetEra(DateTime time)
+ {
+ return (helper.GetEra(time));
+ }
+
+ internal override CalendarId BaseCalendarID
+ {
+ get
+ {
+ return (CalendarId.JAPAN);
+ }
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.JAPANESELUNISOLAR);
+ }
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (helper.Eras);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/JulianCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/JulianCalendar.cs
new file mode 100644
index 0000000000..82e4d589d3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/JulianCalendar.cs
@@ -0,0 +1,439 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization
+{
+ //
+ // This class implements the Julian calendar. In 48 B.C. Julius Caesar ordered a calendar reform, and this calendar
+ // is called Julian calendar. It consisted of a solar year of twelve months and of 365 days with an extra day
+ // every fourth year.
+ //*
+ //* Calendar support range:
+ //* Calendar Minimum Maximum
+ //* ========== ========== ==========
+ //* Gregorian 0001/01/01 9999/12/31
+ //* Julia 0001/01/03 9999/10/19
+
+ public class JulianCalendar : Calendar
+ {
+ public static readonly int JulianEra = 1;
+
+ private const int DatePartYear = 0;
+ private const int DatePartDayOfYear = 1;
+ private const int DatePartMonth = 2;
+ private const int DatePartDay = 3;
+
+ // Number of days in a non-leap year
+ private const int JulianDaysPerYear = 365;
+ // Number of days in 4 years
+ private const int JulianDaysPer4Years = JulianDaysPerYear * 4 + 1;
+
+ private static readonly int[] s_daysToMonth365 =
+ {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+ };
+
+ private static readonly int[] s_daysToMonth366 =
+ {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
+ };
+
+ // Gregorian Calendar 9999/12/31 = Julian Calendar 9999/10/19
+ // keep it as variable field for serialization compat.
+ internal int MaxYear = 9999;
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ public JulianCalendar()
+ {
+ // There is no system setting of TwoDigitYear max, so set the value here.
+ twoDigitYearMax = 2029;
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return CalendarId.JULIAN;
+ }
+ }
+
+ internal static void CheckEraRange(int era)
+ {
+ if (era != CurrentEra && era != JulianEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ internal void CheckYearEraRange(int year, int era)
+ {
+ CheckEraRange(era);
+ if (year <= 0 || year > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxYear));
+ }
+ }
+
+ internal static void CheckMonthRange(int month)
+ {
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+ }
+
+ /*===================================CheckDayRange============================
+ **Action: Check for if the day value is valid.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ **Notes:
+ ** Before calling this method, call CheckYearEraRange()/CheckMonthRange() to make
+ ** sure year/month values are correct.
+ ============================================================================*/
+
+ internal static void CheckDayRange(int year, int month, int day)
+ {
+ if (year == 1 && month == 1)
+ {
+ // The mimimum supported Julia date is Julian 0001/01/03.
+ if (day < 3)
+ {
+ throw new ArgumentOutOfRangeException(null,
+ SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+ }
+ bool isLeapYear = (year % 4) == 0;
+ int[] days = isLeapYear ? s_daysToMonth366 : s_daysToMonth365;
+ int monthDays = days[month] - days[month - 1];
+ if (day < 1 || day > monthDays)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ monthDays));
+ }
+ }
+
+
+ // Returns a given date part of this DateTime. This method is used
+ // to compute the year, day-of-year, month, or day part.
+ internal static int GetDatePart(long ticks, int part)
+ {
+ // Gregorian 1/1/0001 is Julian 1/3/0001. Remember DateTime(0) is refered to Gregorian 1/1/0001.
+ // The following line convert Gregorian ticks to Julian ticks.
+ long julianTicks = ticks + TicksPerDay * 2;
+ // n = number of days since 1/1/0001
+ int n = (int)(julianTicks / TicksPerDay);
+ // y4 = number of whole 4-year periods within 100-year period
+ int y4 = n / JulianDaysPer4Years;
+ // n = day number within 4-year period
+ n -= y4 * JulianDaysPer4Years;
+ // y1 = number of whole years within 4-year period
+ int y1 = n / JulianDaysPerYear;
+ // Last year has an extra day, so decrement result if 4
+ if (y1 == 4) y1 = 3;
+ // If year was requested, compute and return it
+ if (part == DatePartYear)
+ {
+ return (y4 * 4 + y1 + 1);
+ }
+ // n = day number within year
+ n -= y1 * JulianDaysPerYear;
+ // If day-of-year was requested, return it
+ if (part == DatePartDayOfYear)
+ {
+ return (n + 1);
+ }
+ // Leap year calculation looks different from IsLeapYear since y1, y4,
+ // and y100 are relative to year 1, not year 0
+ bool leapYear = (y1 == 3);
+ int[] days = leapYear ? s_daysToMonth366 : s_daysToMonth365;
+ // All months have less than 32 days, so n >> 5 is a good conservative
+ // estimate for the month
+ int m = (n >> 5) + 1;
+ // m = 1-based month number
+ while (n >= days[m]) m++;
+ // If month was requested, return it
+ if (part == DatePartMonth) return (m);
+ // Return 1-based day-of-month
+ return (n - days[m - 1] + 1);
+ }
+
+ // Returns the tick count corresponding to the given year, month, and day.
+ internal static long DateToTicks(int year, int month, int day)
+ {
+ int[] days = (year % 4 == 0) ? s_daysToMonth366 : s_daysToMonth365;
+ int y = year - 1;
+ int n = y * 365 + y / 4 + days[month - 1] + day - 1;
+ // Gregorian 1/1/0001 is Julian 1/3/0001. n * TicksPerDay is the ticks in JulianCalendar.
+ // Therefore, we subtract two days in the following to convert the ticks in JulianCalendar
+ // to ticks in Gregorian calendar.
+ return ((n - 2) * TicksPerDay);
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ -120000,
+ 120000));
+ }
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int[] daysArray = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? s_daysToMonth366 : s_daysToMonth365;
+ int days = (daysArray[m] - daysArray[m - 1]);
+
+ if (d > days)
+ {
+ d = days;
+ }
+ long ticks = DateToTicks(y, m, d) + time.Ticks % TicksPerDay;
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (AddMonths(time, years * 12));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ CheckYearEraRange(year, era);
+ CheckMonthRange(month);
+ int[] days = (year % 4 == 0) ? s_daysToMonth366 : s_daysToMonth365;
+ return (days[month] - days[month - 1]);
+ }
+
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ // Year/Era range is done in IsLeapYear().
+ return (IsLeapYear(year, era) ? 366 : 365);
+ }
+
+
+ public override int GetEra(DateTime time)
+ {
+ return (JulianEra);
+ }
+
+
+ public override int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { JulianEra });
+ }
+ }
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ CheckYearEraRange(year, era);
+ return (12);
+ }
+
+
+ public override int GetYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ CheckMonthRange(month);
+ // Year/Era range check is done in IsLeapYear().
+ if (IsLeapYear(year, era))
+ {
+ CheckDayRange(year, month, day);
+ return (month == 2 && day == 29);
+ }
+ CheckDayRange(year, month, day);
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ CheckYearEraRange(year, era);
+ return (0);
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ CheckYearEraRange(year, era);
+ CheckMonthRange(month);
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ CheckYearEraRange(year, era);
+ return (year % 4 == 0);
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ CheckYearEraRange(year, era);
+ CheckMonthRange(month);
+ CheckDayRange(year, month, day);
+ if (millisecond < 0 || millisecond >= MillisPerSecond)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(millisecond),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ MillisPerSecond - 1));
+ }
+
+ if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60)
+ {
+ return new DateTime(DateToTicks(year, month, day) + (new TimeSpan(0, hour, minute, second, millisecond)).Ticks);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
+ }
+ }
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ MaxYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (year > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Bounds_Lower_Upper,
+ 1,
+ MaxYear));
+ }
+ return (base.ToFourDigitYear(year));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/KoreanCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/KoreanCalendar.cs
new file mode 100644
index 0000000000..9168b664e0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/KoreanCalendar.cs
@@ -0,0 +1,262 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Globalization
+{
+ /*=================================KoreanCalendar==========================
+ **
+ ** Korean calendar is based on the Gregorian calendar. And the year is an offset to Gregorian calendar.
+ ** That is,
+ ** Korean year = Gregorian year + 2333. So 2000/01/01 A.D. is Korean 4333/01/01
+ **
+ ** 0001/1/1 A.D. is Korean year 2334.
+ **
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 0001/01/01 9999/12/31
+ ** Korean 2334/01/01 12332/12/31
+ ============================================================================*/
+
+
+ public class KoreanCalendar : Calendar
+ {
+ //
+ // The era value for the current era.
+ //
+
+ public const int KoreanEra = 1;
+
+ // Since
+ // Gregorian Year = Era Year + yearOffset
+ // Gregorian Year 1 is Korean year 2334, so that
+ // 1 = 2334 + yearOffset
+ // So yearOffset = -2333
+ // Gregorian year 2001 is Korean year 4334.
+
+ //m_EraInfo[0] = new EraInfo(1, new DateTime(1, 1, 1).Ticks, -2333, 2334, GregorianCalendar.MaxYear + 2333);
+
+ // Initialize our era info.
+ internal static EraInfo[] koreanEraInfo = new EraInfo[] {
+ new EraInfo( 1, 1, 1, 1, -2333, 2334, GregorianCalendar.MaxYear + 2333) // era #, start year/month/day, yearOffset, minEraYear
+ };
+
+ internal GregorianCalendarHelper helper;
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ public KoreanCalendar()
+ {
+ try
+ {
+ new CultureInfo("ko-KR");
+ }
+ catch (ArgumentException e)
+ {
+ throw new TypeInitializationException(this.GetType().ToString(), e);
+ }
+ helper = new GregorianCalendarHelper(this, koreanEraInfo);
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return CalendarId.KOREA;
+ }
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ return (helper.AddMonths(time, months));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (helper.AddYears(time, years));
+ }
+
+ /*=================================GetDaysInMonth==========================
+ **Action: Returns the number of days in the month given by the year and month arguments.
+ **Returns: The number of days in the given month.
+ **Arguments:
+ ** year The year in Korean calendar.
+ ** month The month
+ ** era The Japanese era value.
+ **Exceptions
+ ** ArgumentException If month is less than 1 or greater * than 12.
+ ============================================================================*/
+
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ return (helper.GetDaysInMonth(year, month, era));
+ }
+
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ return (helper.GetDaysInYear(year, era));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (helper.GetDayOfMonth(time));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return (helper.GetDayOfWeek(time));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (helper.GetDayOfYear(time));
+ }
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ return (helper.GetMonthsInYear(year, era));
+ }
+
+
+ public override int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ return (helper.GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+
+ public override int GetEra(DateTime time)
+ {
+ return (helper.GetEra(time));
+ }
+
+ public override int GetMonth(DateTime time)
+ {
+ return (helper.GetMonth(time));
+ }
+
+
+ public override int GetYear(DateTime time)
+ {
+ return (helper.GetYear(time));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ return (helper.IsLeapDay(year, month, day, era));
+ }
+
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ return (helper.IsLeapYear(year, era));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ return (helper.GetLeapMonth(year, era));
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ return (helper.IsLeapMonth(year, month, era));
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ return (helper.ToDateTime(year, month, day, hour, minute, second, millisecond, era));
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (helper.Eras);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 4362;
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ helper.MaxYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ return (helper.ToFourDigitYear(year, this.TwoDigitYearMax));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/KoreanLunisolarCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/KoreanLunisolarCalendar.cs
new file mode 100644
index 0000000000..0f71b5f687
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/KoreanLunisolarCalendar.cs
@@ -0,0 +1,1316 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization
+{
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 918/02/19 2051/02/10
+ ** KoreanLunisolar 918/01/01 2050/13/29
+ */
+
+ public class KoreanLunisolarCalendar : EastAsianLunisolarCalendar
+ {
+ //
+ // The era value for the current era.
+ //
+
+ public const int GregorianEra = 1;
+
+ internal const int MIN_LUNISOLAR_YEAR = 918;
+ internal const int MAX_LUNISOLAR_YEAR = 2050;
+
+ internal const int MIN_GREGORIAN_YEAR = 918;
+ internal const int MIN_GREGORIAN_MONTH = 2;
+ internal const int MIN_GREGORIAN_DAY = 19;
+
+ internal const int MAX_GREGORIAN_YEAR = 2051;
+ internal const int MAX_GREGORIAN_MONTH = 2;
+ internal const int MAX_GREGORIAN_DAY = 10;
+
+ internal static DateTime minDate = new DateTime(MIN_GREGORIAN_YEAR, MIN_GREGORIAN_MONTH, MIN_GREGORIAN_DAY);
+ internal static DateTime maxDate = new DateTime((new DateTime(MAX_GREGORIAN_YEAR, MAX_GREGORIAN_MONTH, MAX_GREGORIAN_DAY, 23, 59, 59, 999)).Ticks + 9999);
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // 917 -- From http://emr.cs.iit.edu/home/reingold/calendar-book/Calendrica.html
+ // using ChineseLunisolar
+ return 384;
+ }
+ }
+
+// Data for years 1391-2050 matches that available from
+// Korea Astronomy and Space Science Institute (KASI)
+// https://astro.kasi.re.kr:444/life/pageView/5
+ private static readonly int[,] s_yinfo =
+ {
+/*Y LM Lmon Lday DaysPerMonth D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 #Days
+0918 */ { 00, 02, 19, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+0919 */ { 00, 02, 09, 0b0100010111010000 }, /* 29 30 29 29 29 30 29 30 30 30 29 30 354
+0920 */ { 06, 01, 29, 0b1010001011011000 }, /* 30 29 30 29 29 29 30 29 30 30 29 30 30 384
+0921 */ { 00, 02, 16, 0b1010001010110000 }, /* 30 29 30 29 29 29 30 29 30 29 30 30 354
+0922 */ { 00, 02, 05, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+0923 */ { 04, 01, 25, 0b1011010010101000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+0924 */ { 00, 02, 13, 0b0110110100100000 }, /* 29 30 30 29 30 30 29 30 29 29 30 29 354
+0925 */ { 12, 02, 01, 0b1010110101100000 }, /* 30 29 30 29 30 30 29 30 29 30 30 29 29 384
+0926 */ { 00, 02, 20, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+0927 */ { 00, 02, 10, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+0928 */ { 08, 01, 31, 0b0100010110111000 }, /* 29 30 29 29 29 30 29 30 30 29 30 30 30 384
+0929 */ { 00, 02, 18, 0b0100010101110000 }, /* 29 30 29 29 29 30 29 30 29 30 30 30 354
+0930 */ { 00, 02, 07, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+0931 */ { 05, 01, 27, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 29 383
+0932 */ { 00, 02, 14, 0b1110100101010000 }, /* 30 30 30 29 30 29 29 30 29 30 29 30 355
+0933 */ { 00, 02, 03, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+0934 */ { 01, 01, 23, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+0935 */ { 00, 02, 11, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+0936 */ { 11, 02, 01, 0b0101001101100000 }, /* 29 30 29 30 29 29 30 30 29 30 30 29 29 383
+0937 */ { 00, 02, 18, 0b1100101011000000 }, /* 30 30 29 29 30 29 30 29 30 30 29 29 354
+0938 */ { 00, 02, 07, 0b1110010101100000 }, /* 30 30 30 29 29 30 29 30 29 30 30 29 355
+0939 */ { 07, 01, 28, 0b1101001010101000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 30 384
+0940 */ { 00, 02, 16, 0b1101001010100000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 354
+0941 */ { 00, 02, 04, 0b1101100101010000 }, /* 30 30 29 30 30 29 29 30 29 30 29 30 355
+0942 */ { 03, 01, 25, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+0943 */ { 00, 02, 13, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+0944 */ { 12, 02, 02, 0b1010011011010000 }, /* 30 29 30 29 29 30 30 29 30 30 29 30 29 384
+0945 */ { 00, 02, 20, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+0946 */ { 00, 02, 10, 0b0100101011010000 }, /* 29 30 29 29 30 29 30 29 30 30 29 30 354
+0947 */ { 07, 01, 30, 0b1010010011011000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+0948 */ { 00, 02, 18, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+0949 */ { 00, 02, 06, 0b1011001001100000 }, /* 30 29 30 30 29 29 30 29 29 30 30 29 354
+0950 */ { 05, 01, 26, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+0951 */ { 00, 02, 14, 0b1011001101110000 }, /* 30 29 30 30 29 29 30 30 29 30 30 30 356
+0953 */ { 00, 01, 05, 0b1010101011010000 }, /* 30 29 30 29 30 29 30 29 30 30 29 30 355
+0953 */ { 01, 01, 23, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+0954 */ { 00, 02, 11, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+0955 */ { 09, 02, 01, 0b0100101010111000 }, /* 29 30 29 29 30 29 30 29 30 29 30 30 30 384
+0956 */ { 00, 02, 20, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+0957 */ { 00, 02, 08, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+0958 */ { 07, 01, 28, 0b1010101010011000 }, /* 30 29 30 29 30 29 30 29 30 29 29 30 30 384
+0959 */ { 00, 02, 16, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+0960 */ { 00, 02, 05, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+0961 */ { 03, 01, 25, 0b0100110110101000 }, /* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+0962 */ { 00, 02, 13, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+0963 */ { 12, 02, 02, 0b1001010101110000 }, /* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+0964 */ { 00, 02, 21, 0b1010001101110000 }, /* 30 29 30 29 29 29 30 30 29 30 30 30 355
+0965 */ { 00, 02, 10, 0b0101000101110000 }, /* 29 30 29 30 29 29 29 30 29 30 30 30 354
+0966 */ { 08, 01, 30, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+0967 */ { 00, 02, 17, 0b1101010010110000 }, /* 30 30 29 30 29 30 29 29 30 29 30 30 355
+0968 */ { 00, 02, 07, 0b0101101010010000 }, /* 29 30 29 30 30 29 30 29 30 29 29 30 354
+0969 */ { 05, 01, 26, 0b0110101101010000 }, /* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+0970 */ { 00, 02, 14, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+0971 */ { 00, 02, 04, 0b0010101011100000 }, /* 29 29 30 29 30 29 30 29 30 30 30 29 354
+0972 */ { 02, 01, 24, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+0973 */ { 00, 02, 11, 0b1010001011100000 }, /* 30 29 30 29 29 29 30 29 30 30 30 29 354
+0974 */ { 10, 01, 31, 0b1100100101101000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+0975 */ { 00, 02, 19, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+0976 */ { 00, 02, 08, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+0977 */ { 07, 01, 27, 0b1101101010010000 }, /* 30 30 29 30 30 29 30 29 30 29 29 30 29 384
+0978 */ { 00, 02, 15, 0b1011010110100000 }, /* 30 29 30 30 29 30 29 30 30 29 30 29 355
+0979 */ { 00, 02, 05, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+0980 */ { 03, 01, 26, 0b0010101011011000 }, /* 29 29 30 29 30 29 30 29 30 30 29 30 30 384
+0981 */ { 00, 02, 13, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+0982 */ { 12, 02, 02, 0b1001001011011000 }, /* 30 29 29 30 29 29 30 29 30 30 29 30 30 384
+0983 */ { 00, 02, 21, 0b1001001010110000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 354
+0984 */ { 00, 02, 10, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+0985 */ { 09, 01, 29, 0b1011010010101000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+0986 */ { 00, 02, 17, 0b1010110010100000 }, /* 30 29 30 29 30 30 29 29 30 29 30 29 354
+0987 */ { 00, 02, 06, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+0988 */ { 05, 01, 27, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+0989 */ { 00, 02, 14, 0b0100101110110000 }, /* 29 30 29 29 30 29 30 30 30 29 30 30 355
+0990 */ { 00, 02, 04, 0b0010010110110000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 354
+0991 */ { 02, 01, 24, 0b1001001010111000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 30 384
+0992 */ { 00, 02, 12, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+0993 */ { 10, 01, 31, 0b0110100101011000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 30 384
+0994 */ { 00, 02, 19, 0b0101100101010000 }, /* 29 30 29 30 30 29 29 30 29 30 29 30 354
+0995 */ { 00, 02, 08, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+0996 */ { 07, 01, 28, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+0997 */ { 00, 02, 15, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+0998 */ { 00, 02, 05, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+0999 */ { 03, 01, 25, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1000 */ { 00, 02, 13, 0b0010010101110000 }, /* 29 29 30 29 29 30 29 30 29 30 30 30 354
+1001 */ { 12, 02, 03, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 29 383
+1002 */ { 00, 02, 21, 0b1101001010100000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 354
+1003 */ { 00, 02, 10, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1004 */ { 09, 01, 31, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1005 */ { 00, 02, 18, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1006 */ { 00, 02, 07, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 355
+1007 */ { 05, 01, 28, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1008 */ { 00, 02, 16, 0b0100101011010000 }, /* 29 30 29 29 30 29 30 29 30 30 29 30 354
+1009 */ { 00, 02, 04, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1010 */ { 02, 01, 24, 0b1101001001101000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1011 */ { 00, 02, 12, 0b1011001001100000 }, /* 30 29 30 30 29 29 30 29 29 30 30 29 354
+1012 */ { 10, 02, 01, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1013 */ { 00, 02, 19, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1014 */ { 00, 02, 09, 0b0011010110100000 }, /* 29 29 30 30 29 30 29 30 30 29 30 29 354
+1015 */ { 06, 01, 29, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+1016 */ { 00, 02, 17, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1017 */ { 00, 02, 06, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1018 */ { 04, 01, 26, 0b1010010011011000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1019 */ { 00, 02, 14, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1020 */ { 12, 02, 03, 0b1010101001011000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 30 384
+1021 */ { 00, 02, 21, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1022 */ { 00, 02, 10, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1023 */ { 09, 01, 31, 0b0010110110101000 }, /* 29 29 30 29 30 30 29 30 30 29 30 29 30 384
+1024 */ { 00, 02, 19, 0b0010101101010000 }, /* 29 29 30 29 30 29 30 30 29 30 29 30 354
+1025 */ { 00, 02, 07, 0b1001010101110000 }, /* 30 29 29 30 29 30 29 30 29 30 30 30 355
+1026 */ { 05, 01, 28, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 29 383
+1027 */ { 00, 02, 15, 0b1100100101110000 }, /* 30 30 29 29 30 29 29 30 29 30 30 30 355
+1028 */ { 00, 02, 05, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1029 */ { 02, 01, 24, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1030 */ { 00, 02, 11, 0b1101101010010000 }, /* 30 30 29 30 30 29 30 29 30 29 29 30 355
+1031 */ { 10, 02, 01, 0b0110101101010000 }, /* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+1032 */ { 00, 02, 20, 0b0110011011010000 }, /* 29 30 30 29 29 30 30 29 30 30 29 30 355
+1033 */ { 00, 02, 09, 0b0010011011100000 }, /* 29 29 30 29 29 30 30 29 30 30 30 29 354
+1034 */ { 06, 01, 29, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1035 */ { 00, 02, 17, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1036 */ { 00, 02, 06, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1037 */ { 04, 01, 25, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1038 */ { 00, 02, 13, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1039 */ { 12, 02, 02, 0b1101011010010000 }, /* 30 30 29 30 29 30 30 29 30 29 29 30 29 384
+1040 */ { 00, 02, 21, 0b1011010110000000 }, /* 30 29 30 30 29 30 29 30 30 29 29 29 354
+1041 */ { 00, 02, 09, 0b1101011010110000 }, /* 30 30 29 30 29 30 30 29 30 29 30 30 356
+1042 */ { 09, 01, 31, 0b0010011011011000 }, /* 29 29 30 29 29 30 30 29 30 30 29 30 30 384
+1043 */ { 00, 02, 19, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1044 */ { 00, 02, 08, 0b1001001010110000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 354
+1045 */ { 05, 01, 27, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1046 */ { 00, 02, 15, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1047 */ { 00, 02, 04, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1048 */ { 01, 01, 24, 0b1011010101011000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 30 385
+1049 */ { 00, 02, 12, 0b0010110101010000 }, /* 29 29 30 29 30 30 29 30 29 30 29 30 354
+1050 */ { 11, 02, 01, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1051 */ { 00, 02, 20, 0b0100101110110000 }, /* 29 30 29 29 30 29 30 30 30 29 30 30 355
+1052 */ { 00, 02, 10, 0b0010010110110000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 354
+1053 */ { 07, 01, 29, 0b0101001010111000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1054 */ { 00, 02, 17, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1055 */ { 00, 02, 06, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1056 */ { 03, 01, 26, 0b0110101010101000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1057 */ { 00, 02, 13, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1058 */ { 12, 02, 02, 0b1010101101011000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 30 385
+1059 */ { 00, 02, 22, 0b0010011101010000 }, /* 29 29 30 29 29 30 30 30 29 30 29 30 354
+1060 */ { 00, 02, 11, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+1061 */ { 08, 01, 30, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1062 */ { 00, 02, 18, 0b1010010101100000 }, /* 30 29 30 29 29 30 29 30 29 30 30 29 354
+1063 */ { 00, 02, 07, 0b1101001001100000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 354
+1064 */ { 05, 01, 27, 0b1110100100110000 }, /* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1065 */ { 00, 02, 14, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1066 */ { 00, 02, 04, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1067 */ { 01, 01, 24, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+1068 */ { 00, 02, 12, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 355
+1069 */ { 11, 02, 01, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1070 */ { 00, 02, 20, 0b0100100111010000 }, /* 29 30 29 29 30 29 29 30 30 30 29 30 354
+1071 */ { 00, 02, 09, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1072 */ { 07, 01, 29, 0b1101001001101000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1073 */ { 00, 02, 16, 0b1010101001100000 }, /* 30 29 30 29 30 29 30 29 29 30 30 29 354
+1074 */ { 00, 02, 05, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1075 */ { 04, 01, 26, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1076 */ { 00, 02, 14, 0b0011010110100000 }, /* 29 29 30 30 29 30 29 30 30 29 30 29 354
+1077 */ { 00, 02, 02, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+1078 */ { 01, 01, 23, 0b0100101110011000 }, /* 29 30 29 29 30 29 30 30 30 29 29 30 30 384
+1079 */ { 00, 02, 11, 0b0100010110110000 }, /* 29 30 29 29 29 30 29 30 30 29 30 30 354
+1080 */ { 09, 01, 31, 0b1010010010111000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1081 */ { 00, 02, 18, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1082 */ { 00, 02, 07, 0b1010101001010000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 354
+1083 */ { 06, 01, 27, 0b1011010101001000 }, /* 30 29 30 30 29 30 29 30 29 30 29 29 30 384
+1084 */ { 00, 02, 15, 0b0110101101010000 }, /* 29 30 30 29 30 29 30 30 29 30 29 30 355
+1085 */ { 00, 02, 04, 0b0010110110100000 }, /* 29 29 30 29 30 30 29 30 30 29 30 29 354
+1086 */ { 02, 01, 24, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+1087 */ { 00, 02, 12, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1088 */ { 12, 02, 02, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 29 383
+1089 */ { 00, 02, 19, 0b1100100101110000 }, /* 30 30 29 29 30 29 29 30 29 30 30 30 355
+1090 */ { 00, 02, 09, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1091 */ { 08, 01, 29, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1092 */ { 00, 02, 16, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 355
+1093 */ { 00, 02, 05, 0b0101101101000000 }, /* 29 30 29 30 30 29 30 30 29 30 29 29 354
+1094 */ { 04, 01, 25, 0b1010101101101000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 30 385
+1095 */ { 00, 02, 14, 0b0010101011100000 }, /* 29 29 30 29 30 29 30 29 30 30 30 29 354
+1096 */ { 00, 02, 03, 0b1110011000010000 }, /* 30 30 30 29 29 30 30 29 29 29 29 30 354
+1097 */ { 02, 01, 22, 0b1100100101110000 }, /* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+1098 */ { 00, 02, 10, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1099 */ { 09, 01, 30, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1100 */ { 00, 02, 18, 0b0101010010100000 }, /* 29 30 29 30 29 30 29 29 30 29 30 29 353
+1101 */ { 00, 02, 07, 0b1101011001010000 }, /* 30 30 29 30 29 30 30 29 29 30 29 30 355
+1102 */ { 06, 01, 28, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1103 */ { 00, 02, 16, 0b0101010111010000 }, /* 29 30 29 30 29 30 29 30 30 30 29 30 355
+1104 */ { 00, 02, 06, 0b0010011011010000 }, /* 29 29 30 29 29 30 30 29 30 30 29 30 354
+1105 */ { 02, 01, 25, 0b1001001011101000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 30 384
+1106 */ { 00, 02, 13, 0b1001001010110000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 354
+1107 */ { 10, 02, 02, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1108 */ { 00, 02, 21, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1109 */ { 00, 02, 09, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1110 */ { 08, 01, 29, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1111 */ { 00, 02, 17, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1112 */ { 00, 02, 07, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+1113 */ { 04, 01, 27, 0b0010010110111000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1114 */ { 00, 02, 15, 0b0100010101110000 }, /* 29 30 29 29 29 30 29 30 29 30 30 30 354
+1115 */ { 00, 02, 04, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1116 */ { 01, 01, 24, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1117 */ { 00, 02, 11, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1118 */ { 09, 01, 31, 0b0111001010101000 }, /* 29 30 30 30 29 29 30 29 30 29 30 29 30 384
+1119 */ { 00, 02, 19, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1120 */ { 00, 02, 08, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1121 */ { 05, 01, 28, 0b0100101101101000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1122 */ { 00, 02, 16, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+1123 */ { 00, 02, 05, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1124 */ { 03, 01, 26, 0b0101001001110000 }, /* 29 30 29 30 29 29 30 29 29 30 30 30 29 383
+1125 */ { 00, 02, 12, 0b1101001001100000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 354
+1126 */ { 11, 02, 01, 0b1110100100110000 }, /* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1127 */ { 00, 02, 20, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1128 */ { 00, 02, 10, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1129 */ { 08, 01, 29, 0b1001101110010000 }, /* 30 29 29 30 30 29 30 30 30 29 29 30 29 384
+1130 */ { 00, 02, 17, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 355
+1131 */ { 00, 02, 07, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1132 */ { 04, 01, 27, 0b1010010011101000 }, /* 30 29 30 29 29 30 29 29 30 30 30 29 30 384
+1133 */ { 00, 02, 14, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1134 */ { 00, 02, 03, 0b1101001001010000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 354
+1135 */ { 02, 01, 23, 0b1101100100101000 }, /* 30 30 29 30 30 29 29 30 29 29 30 29 30 384
+1136 */ { 00, 02, 11, 0b1011010101000000 }, /* 30 29 30 30 29 30 29 30 29 30 29 29 354
+1137 */ { 10, 01, 30, 0b1101011010101000 }, /* 30 30 29 30 29 30 30 29 30 29 30 29 30 385
+1138 */ { 00, 02, 19, 0b0010110110100000 }, /* 29 29 30 29 30 30 29 30 30 29 30 29 354
+1139 */ { 00, 02, 08, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+1140 */ { 06, 01, 29, 0b0100101011011000 }, /* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+1141 */ { 00, 02, 16, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1142 */ { 00, 02, 05, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1143 */ { 04, 01, 25, 0b1011001001011000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1144 */ { 00, 02, 13, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 354
+1145 */ { 11, 02, 01, 0b1011010100101000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1146 */ { 00, 02, 20, 0b0110101101000000 }, /* 29 30 30 29 30 29 30 30 29 30 29 29 354
+1147 */ { 00, 02, 09, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+1148 */ { 08, 01, 30, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+1149 */ { 00, 02, 17, 0b1001100101110000 }, /* 30 29 29 30 30 29 29 30 29 30 30 30 355
+1150 */ { 00, 02, 07, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1151 */ { 04, 01, 27, 0b0110010010111000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 30 384
+1152 */ { 00, 02, 15, 0b0101010010110000 }, /* 29 30 29 30 29 30 29 29 30 29 30 30 354
+1153 */ { 12, 02, 03, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1154 */ { 00, 02, 21, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 355
+1155 */ { 00, 02, 11, 0b0101101011000000 }, /* 29 30 29 30 30 29 30 29 30 30 29 29 354
+1156 */ { 10, 01, 31, 0b1010101101101000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 30 385
+1157 */ { 00, 02, 19, 0b0010011011100000 }, /* 29 29 30 29 29 30 30 29 30 30 30 29 354
+1158 */ { 00, 02, 08, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1159 */ { 06, 01, 28, 0b1100100101110000 }, /* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+1160 */ { 00, 02, 16, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1161 */ { 00, 02, 04, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1162 */ { 02, 01, 24, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1163 */ { 00, 02, 12, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1164 */ { 11, 02, 02, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1165 */ { 00, 02, 20, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+1166 */ { 00, 02, 10, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1167 */ { 07, 01, 30, 0b1001001011101000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 30 384
+1168 */ { 00, 02, 18, 0b1001001001110000 }, /* 30 29 29 30 29 29 30 29 29 30 30 30 354
+1169 */ { 00, 02, 06, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1170 */ { 05, 01, 26, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1171 */ { 00, 02, 14, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1172 */ { 00, 02, 03, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1173 */ { 01, 01, 23, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1174 */ { 00, 02, 11, 0b0100110110110000 }, /* 29 30 29 29 30 30 29 30 30 29 30 30 355
+1175 */ { 09, 02, 01, 0b0010010110110000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 29 383
+1176 */ { 00, 02, 19, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1177 */ { 00, 02, 08, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1178 */ { 06, 01, 28, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1179 */ { 00, 02, 16, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1180 */ { 00, 02, 05, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1181 */ { 03, 01, 24, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1182 */ { 00, 02, 12, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1183 */ { 11, 02, 02, 0b0100101101101000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1184 */ { 00, 02, 21, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1185 */ { 00, 02, 09, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1186 */ { 07, 01, 30, 0b0101001001110000 }, /* 29 30 29 30 29 29 30 29 29 30 30 30 29 383
+1187 */ { 00, 02, 17, 0b1101000010010000 }, /* 30 30 29 30 29 29 29 29 30 29 29 30 353
+1188 */ { 00, 01, 08, 0b0111010010011000 }, /* 29 30 30 30 29 30 29 29 30 29 29 30 354
+1189 */ { 05, 01, 26, 0b0110101010101000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1190 */ { 00, 02, 14, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1191 */ { 00, 02, 03, 0b1001101101010000 }, /* 30 29 29 30 30 29 30 30 29 30 29 30 355
+1192 */ { 02, 01, 24, 0b0100101101101000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1193 */ { 00, 02, 11, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1194 */ { 10, 01, 31, 0b1010010011101000 }, /* 30 29 30 29 29 30 29 29 30 30 30 29 30 384
+1195 */ { 00, 02, 19, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1196 */ { 00, 02, 08, 0b1101001001100000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 354
+1197 */ { 06, 01, 27, 0b1101010100101000 }, /* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1198 */ { 00, 02, 15, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1199 */ { 00, 02, 04, 0b1101011010100000 }, /* 30 30 29 30 29 30 30 29 30 29 30 29 355
+1200 */ { 02, 01, 25, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1201 */ { 00, 02, 12, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+1202 */ { 12, 02, 02, 0b0100100111011000 }, /* 29 30 29 29 30 29 29 30 30 30 29 30 30 384
+1203 */ { 00, 02, 21, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1204 */ { 00, 02, 10, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1205 */ { 08, 01, 29, 0b1010101001011000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 30 384
+1206 */ { 00, 02, 17, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 354
+1207 */ { 00, 02, 06, 0b1011010101000000 }, /* 30 29 30 30 29 30 29 30 29 30 29 29 354
+1208 */ { 04, 01, 26, 0b1011010110100000 }, /* 30 29 30 30 29 30 29 30 30 29 30 29 29 384
+1209 */ { 00, 02, 13, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+1210 */ { 00, 02, 03, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1211 */ { 02, 01, 24, 0b0100100110111000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1212 */ { 00, 02, 12, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1213 */ { 09, 01, 31, 0b0110010010111000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 30 384
+1214 */ { 00, 02, 19, 0b0101010010110000 }, /* 29 30 29 30 29 30 29 29 30 29 30 30 354
+1215 */ { 00, 02, 08, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 354
+1216 */ { 07, 01, 28, 0b0110110100101000 }, /* 29 30 30 29 30 30 29 30 29 29 30 29 30 384
+1217 */ { 00, 02, 15, 0b0101101011000000 }, /* 29 30 29 30 30 29 30 29 30 30 29 29 354
+1218 */ { 00, 02, 04, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+1219 */ { 03, 01, 25, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1220 */ { 00, 02, 13, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1221 */ { 12, 02, 01, 0b1100100101110000 }, /* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+1222 */ { 00, 02, 20, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1223 */ { 00, 02, 09, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1224 */ { 08, 01, 29, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1225 */ { 00, 02, 16, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1226 */ { 00, 02, 06, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1227 */ { 05, 01, 26, 0b1010101011011000 }, /* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+1228 */ { 00, 02, 15, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1229 */ { 00, 02, 03, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1230 */ { 02, 01, 23, 0b1100100101011000 }, /* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+1231 */ { 00, 02, 11, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1232 */ { 09, 01, 31, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1233 */ { 00, 02, 18, 0b1011001010100000 }, /* 30 29 30 30 29 29 30 29 30 29 30 29 354
+1234 */ { 00, 02, 07, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1235 */ { 07, 01, 28, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1236 */ { 00, 02, 16, 0b0100110110100000 }, /* 29 30 29 29 30 30 29 30 30 29 30 29 354
+1237 */ { 00, 02, 04, 0b1010010110110000 }, /* 30 29 30 29 29 30 29 30 30 29 30 30 355
+1238 */ { 04, 01, 25, 0b0101001010111000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1239 */ { 00, 02, 13, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1240 */ { 12, 02, 02, 0b1010100100111000 }, /* 30 29 30 29 30 29 29 30 29 29 30 30 30 384
+1241 */ { 00, 02, 20, 0b0110100100110000 }, /* 29 30 30 29 30 29 29 30 29 29 30 30 354
+1242 */ { 00, 02, 09, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1243 */ { 08, 01, 29, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1244 */ { 00, 02, 17, 0b1010111001010000 }, /* 30 29 30 29 30 30 30 29 29 30 29 30 355
+1245 */ { 00, 02, 06, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+1246 */ { 04, 01, 26, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1247 */ { 00, 02, 14, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1248 */ { 00, 02, 04, 0b0101001001110000 }, /* 29 30 29 30 29 29 30 29 29 30 30 30 354
+1249 */ { 02, 01, 23, 0b0110100100110000 }, /* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+1250 */ { 00, 02, 10, 0b1110010100110000 }, /* 30 30 30 29 29 30 29 30 29 29 30 30 355
+1251 */ { 10, 01, 31, 0b0110110010011000 }, /* 29 30 30 29 30 30 29 29 30 29 29 30 30 384
+1252 */ { 00, 02, 19, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1253 */ { 00, 02, 07, 0b0101101011010000 }, /* 29 30 29 30 30 29 30 29 30 30 29 30 355
+1254 */ { 06, 01, 28, 0b0100101101101000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1255 */ { 00, 02, 16, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1256 */ { 00, 02, 05, 0b1010010011100000 }, /* 30 29 30 29 29 30 29 29 30 30 30 29 354
+1257 */ { 04, 01, 24, 0b1101001001101000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1258 */ { 00, 02, 12, 0b1101001001100000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 354
+1259 */ { 11, 02, 01, 0b1101010100101000 }, /* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1260 */ { 00, 02, 20, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1261 */ { 00, 02, 08, 0b1011011010100000 }, /* 30 29 30 30 29 30 30 29 30 29 30 29 355
+1262 */ { 09, 01, 29, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1263 */ { 00, 02, 17, 0b0101010101110000 }, /* 29 30 29 30 29 30 29 30 29 30 30 30 355
+1264 */ { 00, 02, 07, 0b0100100111010000 }, /* 29 30 29 29 30 29 29 30 30 30 29 30 354
+1265 */ { 05, 01, 26, 0b1010010011011000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1266 */ { 00, 02, 14, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1267 */ { 00, 02, 03, 0b1010101001010000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 354
+1268 */ { 01, 01, 23, 0b1011010100101000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1269 */ { 00, 02, 10, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1270 */ { 11, 01, 30, 0b1011010111000000 }, /* 30 29 30 30 29 30 29 30 30 30 29 29 29 384
+1271 */ { 00, 02, 18, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+1272 */ { 00, 02, 08, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1273 */ { 06, 01, 28, 0b0100100110111000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1274 */ { 00, 02, 16, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1275 */ { 00, 02, 05, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1276 */ { 03, 01, 25, 0b0110101001011000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 30 384
+1277 */ { 00, 02, 12, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 354
+1278 */ { 11, 02, 01, 0b0110101100101000 }, /* 29 30 30 29 30 29 30 30 29 29 30 29 30 384
+1279 */ { 00, 02, 20, 0b0101101011000000 }, /* 29 30 29 30 30 29 30 29 30 30 29 29 354
+1280 */ { 00, 02, 09, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+1281 */ { 08, 01, 29, 0b0010101011101000 }, /* 29 29 30 29 30 29 30 29 30 30 30 29 30 384
+1282 */ { 00, 02, 17, 0b0100100111100000 }, /* 29 30 29 29 30 29 29 30 30 30 30 29 354
+1283 */ { 00, 02, 06, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1284 */ { 05, 01, 26, 0b1101001001011000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+1285 */ { 00, 02, 13, 0b1011001001010000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 354
+1286 */ { 00, 02, 02, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1287 */ { 02, 01, 22, 0b1111001010010000 }, /* 30 30 30 30 29 29 30 29 30 29 29 30 29 384
+1288 */ { 00, 02, 10, 0b1011010110100000 }, /* 30 29 30 30 29 30 29 30 30 29 30 29 355
+1289 */ { 10, 01, 30, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+1290 */ { 00, 02, 18, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1291 */ { 00, 02, 08, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1292 */ { 06, 01, 28, 0b1010010010111000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1293 */ { 00, 02, 15, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1294 */ { 00, 02, 04, 0b1010101001010000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 354
+1295 */ { 04, 01, 24, 0b1011010100101000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1296 */ { 00, 02, 12, 0b0110110101000000 }, /* 29 30 30 29 30 30 29 30 29 30 29 29 354
+1297 */ { 12, 01, 31, 0b1010110101100000 }, /* 30 29 30 29 30 30 29 30 29 30 30 29 29 384
+1298 */ { 00, 02, 19, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+1299 */ { 00, 02, 09, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1300 */ { 08, 01, 30, 0b0000100101111000 }, /* 29 29 29 29 30 29 29 30 29 30 30 30 30 383
+1301 */ { 00, 02, 18, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1302 */ { 00, 02, 07, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1303 */ { 05, 01, 27, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1304 */ { 00, 02, 14, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 355
+1305 */ { 00, 02, 03, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1306 */ { 01, 01, 23, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 29 384
+1307 */ { 00, 02, 11, 0b1010011011100000 }, /* 30 29 30 29 29 30 30 29 30 30 30 29 355
+1308 */ { 11, 02, 01, 0b1001001011101000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 30 384
+1309 */ { 00, 02, 19, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1310 */ { 00, 02, 08, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1311 */ { 07, 01, 28, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1312 */ { 00, 02, 16, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1313 */ { 00, 02, 04, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1314 */ { 03, 01, 25, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1315 */ { 00, 02, 13, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1316 */ { 00, 02, 02, 0b1010011011010000 }, /* 30 29 30 29 29 30 30 29 30 30 29 30 355
+1317 */ { 01, 01, 22, 0b1001001011101000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 30 384
+1318 */ { 00, 02, 10, 0b1001001010110000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 354
+1319 */ { 08, 01, 30, 0b1010010101011000 }, /* 30 29 30 29 29 30 29 30 29 30 29 30 30 384
+1320 */ { 00, 02, 18, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1321 */ { 00, 02, 06, 0b1011001010100000 }, /* 30 29 30 30 29 29 30 29 30 29 30 29 354
+1322 */ { 05, 01, 26, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1323 */ { 00, 02, 14, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1324 */ { 00, 02, 04, 0b0100110110100000 }, /* 29 30 29 29 30 30 29 30 30 29 30 29 354
+1325 */ { 01, 01, 23, 0b1010010111010000 }, /* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+1326 */ { 00, 02, 11, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1327 */ { 09, 02, 01, 0b0101001010111000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1328 */ { 00, 02, 20, 0b0101001001110000 }, /* 29 30 29 30 29 29 30 29 29 30 30 30 354
+1329 */ { 00, 02, 08, 0b0110100100110000 }, /* 29 30 30 29 30 29 29 30 29 29 30 30 354
+1330 */ { 07, 01, 28, 0b0110101010011000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 30 384
+1331 */ { 00, 02, 16, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1332 */ { 00, 02, 05, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1333 */ { 03, 01, 25, 0b0100101110101000 }, /* 29 30 29 29 30 29 30 30 30 29 30 29 30 384
+1334 */ { 00, 02, 13, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+1335 */ { 12, 02, 02, 0b1010011001110000 }, /* 30 29 30 29 29 30 30 29 29 30 30 30 29 384
+1336 */ { 00, 02, 21, 0b1010001011100000 }, /* 30 29 30 29 29 29 30 29 30 30 30 29 354
+1337 */ { 00, 02, 09, 0b1101000101100000 }, /* 30 30 29 30 29 29 29 30 29 30 30 29 354
+1338 */ { 08, 01, 29, 0b1110100100110000 }, /* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1339 */ { 00, 02, 17, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1340 */ { 00, 02, 06, 0b1101101010100000 }, /* 30 30 29 30 30 29 30 29 30 29 30 29 355
+1341 */ { 05, 01, 26, 0b0101101101010000 }, /* 29 30 29 30 30 29 30 30 29 30 29 30 29 384
+1342 */ { 00, 02, 14, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+1343 */ { 00, 02, 04, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1344 */ { 02, 01, 24, 0b1010001011101000 }, /* 30 29 30 29 29 29 30 29 30 30 30 29 30 384
+1345 */ { 00, 02, 11, 0b1010001011010000 }, /* 30 29 30 29 29 29 30 29 30 30 29 30 354
+1346 */ { 10, 01, 31, 0b1101000101011000 }, /* 30 30 29 30 29 29 29 30 29 30 29 30 30 384
+1347 */ { 00, 02, 19, 0b1010101001010000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 354
+1348 */ { 00, 02, 08, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1349 */ { 07, 01, 27, 0b1101011010100000 }, /* 30 30 29 30 29 30 30 29 30 29 30 29 29 384
+1350 */ { 00, 02, 15, 0b1010110110100000 }, /* 30 29 30 29 30 30 29 30 30 29 30 29 355
+1351 */ { 00, 02, 05, 0b0101010111010000 }, /* 29 30 29 30 29 30 29 30 30 30 29 30 355
+1352 */ { 03, 01, 26, 0b0100100111011000 }, /* 29 30 29 29 30 29 29 30 30 30 29 30 30 384
+1353 */ { 00, 02, 13, 0b0100010110110000 }, /* 29 30 29 29 29 30 29 30 30 29 30 30 354
+1354 */ { 00, 02, 02, 0b1010001010110000 }, /* 30 29 30 29 29 29 30 29 30 29 30 30 354
+1355 */ { 01, 01, 22, 0b1101000101011000 }, /* 30 30 29 30 29 29 29 30 29 30 29 30 30 384
+1356 */ { 00, 02, 10, 0b1010101001010000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 354
+1357 */ { 09, 01, 29, 0b1011010100101000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1358 */ { 00, 02, 17, 0b0110101100100000 }, /* 29 30 30 29 30 29 30 30 29 29 30 29 354
+1359 */ { 00, 02, 06, 0b1010110101100000 }, /* 30 29 30 29 30 30 29 30 29 30 30 29 355
+1360 */ { 05, 01, 27, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1361 */ { 00, 02, 14, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1362 */ { 00, 02, 04, 0b0100010101110000 }, /* 29 30 29 29 29 30 29 30 29 30 30 30 354
+1363 */ { 03, 01, 24, 0b1010001010111000 }, /* 30 29 30 29 29 29 30 29 30 29 30 30 30 384
+1364 */ { 00, 02, 12, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1365 */ { 10, 01, 31, 0b1010101001010000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 29 383
+1366 */ { 00, 02, 18, 0b1101100101010000 }, /* 30 30 29 30 30 29 29 30 29 30 29 30 355
+1367 */ { 00, 02, 08, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1368 */ { 07, 01, 28, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 29 384
+1369 */ { 00, 02, 15, 0b1010011011100000 }, /* 30 29 30 29 29 30 30 29 30 30 30 29 355
+1370 */ { 00, 02, 05, 0b0101001011100000 }, /* 29 30 29 30 29 29 30 29 30 30 30 29 354
+1371 */ { 03, 01, 25, 0b1100010101110000 }, /* 30 30 29 29 29 30 29 30 29 30 30 30 29 384
+1372 */ { 00, 02, 13, 0b1010010101100000 }, /* 30 29 30 29 29 30 29 30 29 30 30 29 354
+1373 */ { 11, 02, 01, 0b1101001010101000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 30 384
+1374 */ { 00, 02, 20, 0b1101001010100000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 354
+1375 */ { 00, 02, 09, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1376 */ { 09, 01, 30, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1377 */ { 00, 02, 17, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1378 */ { 00, 02, 06, 0b1010011011010000 }, /* 30 29 30 29 29 30 30 29 30 30 29 30 355
+1379 */ { 05, 01, 27, 0b0101001011101000 }, /* 29 30 29 30 29 29 30 29 30 30 30 29 30 384
+1380 */ { 00, 02, 15, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1381 */ { 00, 02, 03, 0b1010100011010000 }, /* 30 29 30 29 30 29 29 29 30 30 29 30 354
+1382 */ { 02, 01, 23, 0b1101001010101000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 30 384
+1383 */ { 00, 02, 11, 0b1011001010100000 }, /* 30 29 30 30 29 29 30 29 30 29 30 29 354
+1384 */ { 10, 01, 31, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1385 */ { 00, 02, 18, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1386 */ { 00, 02, 08, 0b0100110110100000 }, /* 29 30 29 29 30 30 29 30 30 29 30 29 354
+1387 */ { 06, 01, 28, 0b1010010111010000 }, /* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+1388 */ { 00, 02, 16, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1389 */ { 00, 02, 05, 0b0101000110110000 }, /* 29 30 29 30 29 29 29 30 30 29 30 30 354
+1390 */ { 04, 01, 25, 0b1010100010111000 }, /* 30 29 30 29 30 29 29 29 30 29 30 30 30 384
+1391 */ { 00, 02, 13, 0b0110010100110000 }, /* 29 30 30 29 29 30 29 30 29 29 30 30 354
+1392 */ { 12, 02, 02, 0b0110101010011000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 30 384
+1393 */ { 00, 02, 20, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1394 */ { 00, 02, 09, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1395 */ { 09, 01, 30, 0b0010101110101000 }, /* 29 29 30 29 30 29 30 30 30 29 30 29 30 384
+1396 */ { 00, 02, 18, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1397 */ { 00, 02, 06, 0b1100001101110000 }, /* 30 30 29 29 29 29 30 30 29 30 30 30 355
+1398 */ { 05, 01, 27, 0b0101000101110000 }, /* 29 30 29 30 29 29 29 30 29 30 30 30 29 383
+1399 */ { 00, 02, 14, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1400 */ { 00, 02, 03, 0b1110010010110000 }, /* 30 30 30 29 29 30 29 29 30 29 30 30 355
+1401 */ { 03, 01, 24, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 29 383
+1402 */ { 00, 02, 11, 0b1101101010100000 }, /* 30 30 29 30 30 29 30 29 30 29 30 29 355
+1403 */ { 11, 02, 01, 0b0101101101010000 }, /* 29 30 29 30 30 29 30 30 29 30 29 30 29 384
+1404 */ { 00, 02, 20, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+1405 */ { 00, 02, 09, 0b0010101011100000 }, /* 29 29 30 29 30 29 30 29 30 30 30 29 354
+1406 */ { 07, 01, 29, 0b1010001011101000 }, /* 30 29 30 29 29 29 30 29 30 30 30 29 30 384
+1407 */ { 00, 02, 17, 0b1010001011010000 }, /* 30 29 30 29 29 29 30 29 30 30 29 30 354
+1408 */ { 00, 02, 06, 0b1101000101010000 }, /* 30 30 29 30 29 29 29 30 29 30 29 30 354
+1409 */ { 04, 01, 25, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1410 */ { 00, 02, 13, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1411 */ { 12, 02, 02, 0b1011011010010000 }, /* 30 29 30 30 29 30 30 29 30 29 29 30 29 384
+1412 */ { 00, 02, 21, 0b1010110110100000 }, /* 30 29 30 29 30 30 29 30 30 29 30 29 355
+1413 */ { 00, 02, 10, 0b0101010111010000 }, /* 29 30 29 30 29 30 29 30 30 30 29 30 355
+1414 */ { 09, 01, 31, 0b0010010111011000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 30 384
+1415 */ { 00, 02, 19, 0b0100010110110000 }, /* 29 30 29 29 29 30 29 30 30 29 30 30 354
+1416 */ { 00, 02, 08, 0b1010001010110000 }, /* 30 29 30 29 29 29 30 29 30 29 30 30 354
+1417 */ { 05, 01, 27, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1418 */ { 00, 02, 15, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1419 */ { 00, 02, 04, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1420 */ { 01, 01, 24, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1421 */ { 00, 02, 11, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+1422 */ { 12, 02, 01, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1423 */ { 00, 02, 20, 0b0100101101110000 }, /* 29 30 29 29 30 29 30 30 29 30 30 30 355
+1424 */ { 00, 02, 10, 0b0100010101110000 }, /* 29 30 29 29 29 30 29 30 29 30 30 30 354
+1425 */ { 07, 01, 29, 0b0101001010111000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1426 */ { 00, 02, 17, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1427 */ { 00, 02, 06, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1428 */ { 04, 01, 26, 0b0110110010101000 }, /* 29 30 30 29 30 30 29 29 30 29 30 29 30 384
+1429 */ { 00, 02, 13, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1430 */ { 12, 02, 02, 0b1001101101010000 }, /* 30 29 29 30 30 29 30 30 29 30 29 30 29 384
+1431 */ { 00, 02, 21, 0b1010011011100000 }, /* 30 29 30 29 29 30 30 29 30 30 30 29 355
+1432 */ { 00, 02, 11, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1433 */ { 08, 01, 30, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1434 */ { 00, 02, 18, 0b1010010101100000 }, /* 30 29 30 29 29 30 29 30 29 30 30 29 354
+1435 */ { 00, 02, 07, 0b1101001010100000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 354
+1436 */ { 06, 01, 27, 0b1110100101010000 }, /* 30 30 30 29 30 29 29 30 29 30 29 30 29 384
+1437 */ { 00, 02, 14, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1438 */ { 00, 02, 04, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1439 */ { 02, 01, 24, 0b1010101011010000 }, /* 30 29 30 29 30 29 30 29 30 30 29 30 29 384
+1440 */ { 00, 02, 12, 0b1001010111100000 }, /* 30 29 29 30 29 30 29 30 30 30 30 29 355
+1441 */ { 11, 02, 01, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1442 */ { 00, 02, 20, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1443 */ { 00, 02, 09, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1444 */ { 07, 01, 29, 0b1101001001110000 }, /* 30 30 29 30 29 29 30 29 29 30 30 30 29 384
+1445 */ { 00, 02, 16, 0b1011001010100000 }, /* 30 29 30 30 29 29 30 29 30 29 30 29 354
+1446 */ { 00, 02, 05, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1447 */ { 04, 01, 26, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1448 */ { 00, 02, 14, 0b0010110110100000 }, /* 29 29 30 29 30 30 29 30 30 29 30 29 354
+1449 */ { 00, 02, 02, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1450 */ { 01, 01, 23, 0b0100101010111000 }, /* 29 30 29 29 30 29 30 29 30 29 30 30 30 384
+1451 */ { 00, 02, 11, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1452 */ { 09, 01, 31, 0b1010010010111000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1453 */ { 00, 02, 18, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1454 */ { 00, 02, 07, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 354
+1455 */ { 06, 01, 27, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1456 */ { 00, 02, 15, 0b0110101101010000 }, /* 29 30 30 29 30 29 30 30 29 30 29 30 355
+1457 */ { 00, 02, 04, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1458 */ { 02, 01, 24, 0b1001010101110000 }, /* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+1459 */ { 00, 02, 12, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1460 */ { 11, 02, 02, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 29 383
+1461 */ { 00, 02, 19, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1462 */ { 00, 02, 08, 0b1110010010110000 }, /* 30 30 30 29 29 30 29 29 30 29 30 30 355
+1463 */ { 07, 01, 29, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 29 383
+1464 */ { 00, 02, 16, 0b1101101010100000 }, /* 30 30 29 30 30 29 30 29 30 29 30 29 355
+1465 */ { 00, 02, 05, 0b0101101011010000 }, /* 29 30 29 30 30 29 30 29 30 30 29 30 355
+1466 */ { 03, 01, 26, 0b0010101101101000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 30 384
+1467 */ { 00, 02, 14, 0b0010011011100000 }, /* 29 29 30 29 29 30 30 29 30 30 30 29 354
+1468 */ { 00, 02, 03, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1469 */ { 02, 01, 22, 0b1100100101101000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1470 */ { 00, 02, 10, 0b1100100101010000 }, /* 30 30 29 29 30 29 29 30 29 30 29 30 354
+1471 */ { 09, 01, 30, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1472 */ { 00, 02, 18, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1473 */ { 00, 02, 06, 0b1011011010010000 }, /* 30 29 30 30 29 30 30 29 30 29 29 30 355
+1474 */ { 06, 01, 27, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1475 */ { 00, 02, 15, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+1476 */ { 00, 02, 05, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1477 */ { 02, 01, 24, 0b1001001011011000 }, /* 30 29 29 30 29 29 30 29 30 30 29 30 30 384
+1478 */ { 00, 02, 12, 0b1001001010110000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 354
+1479 */ { 10, 02, 01, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1480 */ { 00, 02, 20, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1481 */ { 00, 02, 08, 0b0111010010100000 }, /* 29 30 30 30 29 30 29 29 30 29 30 29 354
+1482 */ { 08, 01, 28, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1483 */ { 00, 02, 16, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 355
+1484 */ { 00, 02, 06, 0b0101001110110000 }, /* 29 30 29 30 29 29 30 30 30 29 30 30 355
+1485 */ { 04, 01, 26, 0b0010010110111000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1486 */ { 00, 02, 14, 0b0010010101110000 }, /* 29 29 30 29 29 30 29 30 29 30 30 30 354
+1487 */ { 00, 02, 03, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1488 */ { 01, 01, 23, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1489 */ { 00, 02, 10, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1490 */ { 09, 01, 30, 0b0110101010101000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1491 */ { 00, 02, 18, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1492 */ { 00, 02, 07, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1493 */ { 05, 01, 27, 0b0100101101101000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1494 */ { 00, 02, 15, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1495 */ { 00, 02, 04, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1496 */ { 03, 01, 25, 0b0101001001110000 }, /* 29 30 29 30 29 29 30 29 29 30 30 30 29 383
+1497 */ { 00, 02, 11, 0b1101001010100000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 354
+1498 */ { 11, 01, 31, 0b1101100101010000 }, /* 30 30 29 30 30 29 29 30 29 30 29 30 29 384
+1499 */ { 00, 02, 19, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1500 */ { 00, 02, 09, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1501 */ { 07, 01, 29, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1502 */ { 00, 02, 17, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+1503 */ { 00, 02, 07, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1504 */ { 04, 01, 27, 0b1010010011011000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1505 */ { 00, 02, 14, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1506 */ { 00, 02, 03, 0b1101001001010000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 354
+1507 */ { 01, 01, 23, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 29 384
+1508 */ { 00, 02, 11, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1509 */ { 09, 01, 31, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1510 */ { 00, 02, 19, 0b0010110110100000 }, /* 29 29 30 29 30 30 29 30 30 29 30 29 354
+1511 */ { 00, 02, 08, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1512 */ { 05, 01, 29, 0b0100100110111000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1513 */ { 00, 02, 16, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1514 */ { 00, 02, 05, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1515 */ { 04, 01, 25, 0b1011001001011000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1516 */ { 00, 02, 13, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 354
+1517 */ { 12, 02, 01, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1518 */ { 00, 02, 20, 0b0101101101010000 }, /* 29 30 29 30 30 29 30 30 29 30 29 30 355
+1519 */ { 00, 02, 10, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1520 */ { 08, 01, 30, 0b1001010101110000 }, /* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+1521 */ { 00, 02, 17, 0b1001001011110000 }, /* 30 29 29 30 29 29 30 29 30 30 30 30 355
+1522 */ { 00, 02, 07, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1523 */ { 04, 01, 27, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1524 */ { 00, 02, 14, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1525 */ { 12, 02, 02, 0b1110101001010000 }, /* 30 30 30 29 30 29 30 29 29 30 29 30 29 384
+1526 */ { 00, 02, 21, 0b1101011010010000 }, /* 30 30 29 30 29 30 30 29 30 29 29 30 355
+1527 */ { 00, 02, 11, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+1528 */ { 10, 02, 01, 0b0010101101101000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 30 384
+1529 */ { 00, 02, 19, 0b0010011011100000 }, /* 29 29 30 29 29 30 30 29 30 30 30 29 354
+1530 */ { 00, 02, 08, 0b0101001011100000 }, /* 29 30 29 30 29 29 30 29 30 30 30 29 354
+1531 */ { 06, 01, 28, 0b1100100101101000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1532 */ { 00, 02, 16, 0b1100100101010000 }, /* 30 30 29 29 30 29 29 30 29 30 29 30 354
+1533 */ { 00, 02, 04, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1534 */ { 02, 01, 24, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1535 */ { 00, 02, 12, 0b1011010110010000 }, /* 30 29 30 30 29 30 29 30 30 29 29 30 355
+1536 */ { 12, 02, 02, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1537 */ { 00, 02, 20, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+1538 */ { 00, 02, 10, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1539 */ { 07, 01, 30, 0b1001001011011000 }, /* 30 29 29 30 29 29 30 29 30 30 29 30 30 384
+1540 */ { 00, 02, 18, 0b1001001010110000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 354
+1541 */ { 00, 02, 06, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1542 */ { 05, 01, 26, 0b1011010010101000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+1543 */ { 00, 02, 14, 0b0110110010100000 }, /* 29 30 30 29 30 30 29 29 30 29 30 29 354
+1544 */ { 00, 02, 03, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1545 */ { 01, 01, 23, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1546 */ { 00, 02, 11, 0b0100101110110000 }, /* 29 30 29 29 30 29 30 30 30 29 30 30 355
+1547 */ { 09, 02, 01, 0b0010010110111000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1548 */ { 00, 02, 20, 0b0010010101110000 }, /* 29 29 30 29 29 30 29 30 29 30 30 30 354
+1549 */ { 00, 02, 08, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1550 */ { 06, 01, 28, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1551 */ { 00, 02, 15, 0b1110100101010000 }, /* 30 30 30 29 30 29 29 30 29 30 29 30 355
+1552 */ { 00, 02, 05, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1553 */ { 03, 01, 24, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1554 */ { 00, 02, 12, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1555 */ { 11, 02, 02, 0b0100101101101000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1556 */ { 00, 02, 21, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1557 */ { 00, 02, 09, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1558 */ { 07, 01, 30, 0b0101001001101000 }, /* 29 30 29 30 29 29 30 29 29 30 30 29 30 383
+1559 */ { 00, 02, 17, 0b1101001001100000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 354
+1560 */ { 00, 02, 06, 0b1101100101010000 }, /* 30 30 29 30 30 29 29 30 29 30 29 30 355
+1561 */ { 05, 01, 26, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1562 */ { 00, 02, 14, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1563 */ { 00, 02, 03, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 355
+1564 */ { 02, 01, 24, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1565 */ { 00, 02, 11, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1566 */ { 10, 01, 31, 0b1010010011011000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1567 */ { 00, 02, 19, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1568 */ { 00, 02, 08, 0b1101001001010000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 354
+1569 */ { 06, 01, 27, 0b1101010101001000 }, /* 30 30 29 30 29 30 29 30 29 30 29 29 30 384
+1570 */ { 00, 02, 15, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1571 */ { 00, 02, 05, 0b0011010110100000 }, /* 29 29 30 30 29 30 29 30 30 29 30 29 354
+1572 */ { 02, 01, 25, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+1573 */ { 00, 02, 12, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1574 */ { 12, 02, 02, 0b0100100110111000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1575 */ { 00, 02, 21, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1576 */ { 00, 02, 10, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1577 */ { 08, 01, 29, 0b1011001001011000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1578 */ { 00, 02, 17, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 354
+1579 */ { 00, 02, 06, 0b0110110101000000 }, /* 29 30 30 29 30 30 29 30 29 30 29 29 354
+1580 */ { 04, 01, 26, 0b1010110110101000 }, /* 30 29 30 29 30 30 29 30 30 29 30 29 30 385
+1581 */ { 00, 02, 14, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1582 */ { 00, 02, 03, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1583 */ { 02, 01, 24, 0b0100100101111000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1584 */ { 00, 02, 12, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1585 */ { 09, 01, 31, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1586 */ { 00, 02, 18, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1587 */ { 00, 02, 07, 0b1110101001010000 }, /* 30 30 30 29 30 29 30 29 29 30 29 30 355
+1588 */ { 06, 01, 28, 0b0110101101001000 }, /* 29 30 30 29 30 29 30 30 29 30 29 29 30 384
+1589 */ { 00, 02, 15, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+1590 */ { 00, 02, 05, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1591 */ { 03, 01, 25, 0b1001001011110000 }, /* 30 29 29 30 29 29 30 29 30 30 30 30 29 384
+1592 */ { 00, 02, 13, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1593 */ { 11, 02, 01, 0b1100100101101000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1594 */ { 00, 02, 20, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1595 */ { 00, 02, 09, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1596 */ { 08, 01, 29, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1597 */ { 00, 02, 16, 0b1011010011010000 }, /* 30 29 30 30 29 30 29 29 30 30 29 30 355
+1598 */ { 00, 02, 06, 0b0101011010110000 }, /* 29 30 29 30 29 30 30 29 30 29 30 30 355
+1599 */ { 04, 01, 27, 0b0010011011011000 }, /* 29 29 30 29 29 30 30 29 30 30 29 30 30 384
+1600 */ { 00, 02, 15, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1601 */ { 00, 02, 03, 0b1001001011010000 }, /* 30 29 29 30 29 29 30 29 30 30 29 30 354
+1602 */ { 02, 01, 23, 0b1100100110011000 }, /* 30 30 29 29 30 29 29 30 30 29 29 30 30 384
+1603 */ { 00, 02, 11, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1604 */ { 09, 01, 31, 0b1011010010101000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+1605 */ { 00, 02, 18, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1606 */ { 00, 02, 07, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1607 */ { 06, 01, 28, 0b0101010110101000 }, /* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+1608 */ { 00, 02, 16, 0b0100101110110000 }, /* 29 30 29 29 30 29 30 30 30 29 30 30 355
+1609 */ { 00, 02, 05, 0b0010010110110000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 354
+1610 */ { 03, 01, 25, 0b0101001010111000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1611 */ { 00, 02, 13, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1612 */ { 11, 02, 02, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1613 */ { 00, 02, 19, 0b1110100101010000 }, /* 30 30 30 29 30 29 29 30 29 30 29 30 355
+1614 */ { 00, 02, 09, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1615 */ { 08, 01, 29, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1616 */ { 00, 02, 17, 0b1001101101010000 }, /* 30 29 29 30 30 29 30 30 29 30 29 30 355
+1617 */ { 00, 02, 06, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+1618 */ { 04, 01, 26, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1619 */ { 00, 02, 14, 0b1010010011110000 }, /* 30 29 30 29 29 30 29 29 30 30 30 30 355
+1620 */ { 00, 02, 04, 0b0101001001100000 }, /* 29 30 29 30 29 29 30 29 29 30 30 29 353
+1621 */ { 02, 01, 22, 0b1110100100110000 }, /* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1622 */ { 00, 02, 10, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1623 */ { 10, 01, 31, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1624 */ { 00, 02, 19, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1625 */ { 00, 02, 07, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 355
+1626 */ { 06, 01, 28, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1627 */ { 00, 02, 16, 0b0100100111100000 }, /* 29 30 29 29 30 29 29 30 30 30 30 29 354
+1628 */ { 00, 02, 05, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1629 */ { 04, 01, 24, 0b1101001001101000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1630 */ { 00, 02, 12, 0b1101001001010000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 354
+1631 */ { 11, 02, 01, 0b1101010100101000 }, /* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1632 */ { 00, 02, 20, 0b1011010101000000 }, /* 30 29 30 30 29 30 29 30 29 30 29 29 354
+1633 */ { 00, 02, 08, 0b1011010110100000 }, /* 30 29 30 30 29 30 29 30 30 29 30 29 355
+1634 */ { 08, 01, 29, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+1635 */ { 00, 02, 17, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1636 */ { 00, 02, 07, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1637 */ { 04, 01, 26, 0b1010010010111000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1638 */ { 00, 02, 14, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1639 */ { 00, 02, 03, 0b1010101001010000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 354
+1640 */ { 01, 01, 23, 0b1011010100101000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1641 */ { 00, 02, 10, 0b0110110101000000 }, /* 29 30 30 29 30 30 29 30 29 30 29 29 354
+1642 */ { 11, 01, 30, 0b1010110110101000 }, /* 30 29 30 29 30 30 29 30 30 29 30 29 30 385
+1643 */ { 00, 02, 19, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1644 */ { 00, 02, 08, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1645 */ { 06, 01, 28, 0b0100100101111000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1646 */ { 00, 02, 16, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1647 */ { 00, 02, 05, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1648 */ { 03, 01, 25, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1649 */ { 00, 02, 11, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 355
+1650 */ { 11, 02, 01, 0b0110101101001000 }, /* 29 30 30 29 30 29 30 30 29 30 29 29 30 384
+1651 */ { 00, 02, 20, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+1652 */ { 00, 02, 10, 0b0010101110100000 }, /* 29 29 30 29 30 29 30 30 30 29 30 29 354
+1653 */ { 07, 01, 29, 0b1001001011110000 }, /* 30 29 29 30 29 29 30 29 30 30 30 30 29 384
+1654 */ { 00, 02, 17, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1655 */ { 00, 02, 06, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1656 */ { 05, 01, 26, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1657 */ { 00, 02, 13, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1658 */ { 00, 02, 02, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 355
+1659 */ { 03, 01, 23, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1660 */ { 00, 02, 11, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1661 */ { 07, 01, 30, 0b1010011011010000 }, /* 30 29 30 29 29 30 30 29 30 30 29 30 29 384
+1662 */ { 00, 02, 18, 0b1010010111010000 }, /* 30 29 30 29 29 30 29 30 30 30 29 30 355
+1663 */ { 00, 02, 08, 0b1001001011010000 }, /* 30 29 29 30 29 29 30 29 30 30 29 30 354
+1664 */ { 06, 01, 28, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1665 */ { 00, 02, 15, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1666 */ { 00, 02, 04, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1667 */ { 04, 01, 24, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1668 */ { 00, 02, 12, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1669 */ { 00, 02, 01, 0b0101010110100000 }, /* 29 30 29 30 29 30 29 30 30 29 30 29 354
+1670 */ { 02, 01, 21, 0b1010010111010000 }, /* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+1671 */ { 00, 02, 09, 0b1010010110110000 }, /* 30 29 30 29 29 30 29 30 30 29 30 30 355
+1672 */ { 07, 01, 30, 0b0101001010111000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1673 */ { 00, 02, 17, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1674 */ { 00, 02, 06, 0b0110100100110000 }, /* 29 30 30 29 30 29 29 30 29 29 30 30 354
+1675 */ { 05, 01, 26, 0b0111010010101000 }, /* 29 30 30 30 29 30 29 29 30 29 30 29 30 384
+1676 */ { 00, 02, 14, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1677 */ { 00, 02, 02, 0b1010110110010000 }, /* 30 29 30 29 30 30 29 30 30 29 29 30 355
+1678 */ { 03, 01, 23, 0b0100110110101000 }, /* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+1679 */ { 00, 02, 11, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+1680 */ { 08, 01, 31, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1681 */ { 00, 02, 18, 0b1010010011100000 }, /* 30 29 30 29 29 30 29 29 30 30 30 29 354
+1682 */ { 00, 02, 07, 0b1101001001100000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 354
+1683 */ { 06, 01, 27, 0b1110100100110000 }, /* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1684 */ { 00, 02, 15, 0b1101010100100000 }, /* 30 30 29 30 29 30 29 30 29 29 30 29 354
+1685 */ { 00, 02, 03, 0b1101101010100000 }, /* 30 30 29 30 30 29 30 29 30 29 30 29 355
+1686 */ { 04, 01, 24, 0b0110101101010000 }, /* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+1687 */ { 00, 02, 12, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+1688 */ { 00, 02, 02, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1689 */ { 03, 01, 21, 0b1010010011101000 }, /* 30 29 30 29 29 30 29 29 30 30 30 29 30 384
+1690 */ { 00, 02, 09, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1691 */ { 07, 01, 29, 0b1101001001011000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+1692 */ { 00, 02, 17, 0b1011001001010000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 354
+1693 */ { 00, 02, 05, 0b1101010100100000 }, /* 30 30 29 30 29 30 29 30 29 29 30 29 354
+1694 */ { 05, 01, 25, 0b1101011010100000 }, /* 30 30 29 30 29 30 30 29 30 29 30 29 29 384
+1695 */ { 00, 02, 13, 0b1011010110100000 }, /* 30 29 30 30 29 30 29 30 30 29 30 29 355
+1696 */ { 00, 02, 03, 0b0101010111010000 }, /* 29 30 29 30 29 30 29 30 30 30 29 30 355
+1697 */ { 03, 01, 23, 0b0100101011011000 }, /* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+1698 */ { 00, 02, 11, 0b0100100111010000 }, /* 29 30 29 29 30 29 29 30 30 30 29 30 354
+1699 */ { 07, 01, 31, 0b1010010010111000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1700 */ { 00, 02, 19, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1701 */ { 00, 02, 08, 0b1010101001010000 }, /* 30 29 30 29 30 29 30 29 29 30 29 30 354
+1702 */ { 06, 01, 28, 0b1011010100101000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1703 */ { 00, 02, 16, 0b0110110100100000 }, /* 29 30 30 29 30 30 29 30 29 29 30 29 354
+1704 */ { 00, 02, 05, 0b1010110110100000 }, /* 30 29 30 29 30 30 29 30 30 29 30 29 355
+1705 */ { 04, 01, 25, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1706 */ { 00, 02, 13, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1707 */ { 00, 02, 03, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1708 */ { 03, 01, 23, 0b0110010010111000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 30 384
+1709 */ { 00, 02, 10, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1710 */ { 07, 01, 30, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1711 */ { 00, 02, 17, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 355
+1712 */ { 00, 02, 07, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1713 */ { 05, 01, 26, 0b1010101101100000 }, /* 30 29 30 29 30 29 30 30 29 30 30 29 29 384
+1714 */ { 00, 02, 14, 0b1010101011100000 }, /* 30 29 30 29 30 29 30 29 30 30 30 29 355
+1715 */ { 00, 02, 04, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1716 */ { 03, 01, 24, 0b1100100101110000 }, /* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+1717 */ { 00, 02, 11, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+1718 */ { 08, 01, 31, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1719 */ { 00, 02, 19, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1720 */ { 00, 02, 08, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1721 */ { 06, 01, 28, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1722 */ { 00, 02, 16, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1723 */ { 00, 02, 05, 0b1010011011010000 }, /* 30 29 30 29 29 30 30 29 30 30 29 30 355
+1724 */ { 04, 01, 26, 0b0101001011101000 }, /* 29 30 29 30 29 29 30 29 30 30 30 29 30 384
+1725 */ { 00, 02, 13, 0b0101001011010000 }, /* 29 30 29 30 29 29 30 29 30 30 29 30 354
+1726 */ { 00, 02, 02, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1727 */ { 03, 01, 22, 0b1110010010101000 }, /* 30 30 30 29 29 30 29 29 30 29 30 29 30 384
+1728 */ { 00, 02, 10, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1729 */ { 07, 01, 29, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1730 */ { 00, 02, 17, 0b1001110101010000 }, /* 30 29 29 30 30 30 29 30 29 30 29 30 355
+1731 */ { 00, 02, 07, 0b0101010110100000 }, /* 29 30 29 30 29 30 29 30 30 29 30 29 354
+1732 */ { 05, 01, 27, 0b1010010111010000 }, /* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+1733 */ { 00, 02, 14, 0b1010010110110000 }, /* 30 29 30 29 29 30 29 30 30 29 30 30 355
+1734 */ { 00, 02, 04, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1735 */ { 04, 01, 24, 0b1010100010111000 }, /* 30 29 30 29 30 29 29 29 30 29 30 30 30 384
+1736 */ { 00, 02, 12, 0b0110100100110000 }, /* 29 30 30 29 30 29 29 30 29 29 30 30 354
+1737 */ { 09, 01, 31, 0b0110101010011000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 30 384
+1738 */ { 00, 02, 19, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1739 */ { 00, 02, 08, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1740 */ { 06, 01, 29, 0b0100110110101000 }, /* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+1741 */ { 00, 02, 16, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+1742 */ { 00, 02, 05, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1743 */ { 04, 01, 26, 0b0101000101110000 }, /* 29 30 29 30 29 29 29 30 29 30 30 30 29 383
+1744 */ { 00, 02, 13, 0b1101000101100000 }, /* 30 30 29 30 29 29 29 30 29 30 30 29 354
+1745 */ { 00, 02, 01, 0b1110100100110000 }, /* 30 30 30 29 30 29 29 30 29 29 30 30 355
+1746 */ { 03, 01, 22, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 29 383
+1747 */ { 00, 02, 09, 0b1101101010100000 }, /* 30 30 29 30 30 29 30 29 30 29 30 29 355
+1748 */ { 07, 01, 30, 0b0101101101010000 }, /* 29 30 29 30 30 29 30 30 29 30 29 30 29 384
+1749 */ { 00, 02, 17, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+1750 */ { 00, 02, 07, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1751 */ { 05, 01, 27, 0b1010010011011000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1752 */ { 00, 02, 15, 0b1010001011010000 }, /* 30 29 30 29 29 29 30 29 30 30 29 30 354
+1753 */ { 00, 02, 03, 0b1101000101010000 }, /* 30 30 29 30 29 29 29 30 29 30 29 30 354
+1754 */ { 04, 01, 23, 0b1101010100101000 }, /* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1755 */ { 00, 02, 11, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1756 */ { 09, 01, 31, 0b1101011010010000 }, /* 30 30 29 30 29 30 30 29 30 29 29 30 29 384
+1757 */ { 00, 02, 18, 0b1010110110100000 }, /* 30 29 30 29 30 30 29 30 30 29 30 29 355
+1758 */ { 00, 02, 08, 0b0101010111010000 }, /* 29 30 29 30 29 30 29 30 30 30 29 30 355
+1759 */ { 06, 01, 29, 0b0010101011011000 }, /* 29 29 30 29 30 29 30 29 30 30 29 30 30 384
+1760 */ { 00, 02, 17, 0b0100010110110000 }, /* 29 30 29 29 29 30 29 30 30 29 30 30 354
+1761 */ { 00, 02, 05, 0b1010001010110000 }, /* 30 29 30 29 29 29 30 29 30 29 30 30 354
+1762 */ { 05, 01, 25, 0b1011000101011000 }, /* 30 29 30 30 29 29 29 30 29 30 29 30 30 384
+1763 */ { 00, 02, 13, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1764 */ { 00, 02, 02, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1765 */ { 02, 01, 21, 0b1011010110010000 }, /* 30 29 30 30 29 30 29 30 30 29 29 30 29 384
+1766 */ { 00, 02, 09, 0b1010110101100000 }, /* 30 29 30 29 30 30 29 30 29 30 30 29 355
+1767 */ { 07, 01, 30, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1768 */ { 00, 02, 18, 0b0101001101110000 }, /* 29 30 29 30 29 29 30 30 29 30 30 30 355
+1769 */ { 00, 02, 07, 0b0100010101110000 }, /* 29 30 29 29 29 30 29 30 29 30 30 30 354
+1770 */ { 05, 01, 27, 0b0110001010111000 }, /* 29 30 30 29 29 29 30 29 30 29 30 30 30 384
+1771 */ { 00, 02, 15, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1772 */ { 00, 02, 04, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1773 */ { 03, 01, 23, 0b0110110010101000 }, /* 29 30 30 29 30 30 29 29 30 29 30 29 30 384
+1774 */ { 00, 02, 11, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1775 */ { 10, 01, 31, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+1776 */ { 00, 02, 19, 0b1010011011010000 }, /* 30 29 30 29 29 30 30 29 30 30 29 30 355
+1777 */ { 00, 02, 08, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1778 */ { 06, 01, 28, 0b1001010101110000 }, /* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+1779 */ { 00, 02, 16, 0b1010010101100000 }, /* 30 29 30 29 29 30 29 30 29 30 30 29 354
+1780 */ { 00, 02, 05, 0b1101001010100000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 354
+1781 */ { 05, 01, 24, 0b1110100101010000 }, /* 30 30 30 29 30 29 29 30 29 30 29 30 29 384
+1782 */ { 00, 02, 12, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1783 */ { 00, 02, 02, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1784 */ { 03, 01, 22, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+1785 */ { 00, 02, 09, 0b1010011011010000 }, /* 30 29 30 29 29 30 30 29 30 30 29 30 355
+1786 */ { 07, 01, 30, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1787 */ { 00, 02, 18, 0b0100101010110000 }, /* 29 30 29 29 30 29 30 29 30 29 30 30 354
+1788 */ { 00, 02, 07, 0b1010100011010000 }, /* 30 29 30 29 30 29 29 29 30 30 29 30 354
+1789 */ { 05, 01, 26, 0b1101001010101000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 30 384
+1790 */ { 00, 02, 14, 0b1011001010100000 }, /* 30 29 30 30 29 29 30 29 30 29 30 29 354
+1791 */ { 00, 02, 03, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1792 */ { 04, 01, 24, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1793 */ { 00, 02, 11, 0b0100110110100000 }, /* 29 30 29 29 30 30 29 30 30 29 30 29 354
+1794 */ { 00, 01, 31, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+1795 */ { 02, 01, 21, 0b0100101010111000 }, /* 29 30 29 29 30 29 30 29 30 29 30 30 30 384
+1796 */ { 00, 02, 09, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1797 */ { 06, 01, 28, 0b1010100010111000 }, /* 30 29 30 29 30 29 29 29 30 29 30 30 30 384
+1798 */ { 00, 02, 16, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1799 */ { 00, 02, 05, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 354
+1800 */ { 04, 01, 25, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1801 */ { 00, 02, 13, 0b0110101101010000 }, /* 29 30 30 29 30 29 30 30 29 30 29 30 355
+1802 */ { 00, 02, 03, 0b0010101110100000 }, /* 29 29 30 29 30 29 30 30 30 29 30 29 354
+1803 */ { 02, 01, 23, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+1804 */ { 00, 02, 11, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1805 */ { 06, 01, 31, 0b0101000101110000 }, /* 29 30 29 30 29 29 29 30 29 30 30 30 29 383
+1806 */ { 00, 02, 18, 0b1101000101100000 }, /* 30 30 29 30 29 29 29 30 29 30 30 29 354
+1807 */ { 00, 02, 07, 0b1110010010110000 }, /* 30 30 30 29 29 30 29 29 30 29 30 30 355
+1808 */ { 05, 01, 28, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 29 383
+1809 */ { 00, 02, 14, 0b1101101010010000 }, /* 30 30 29 30 30 29 30 29 30 29 29 30 355
+1810 */ { 00, 02, 04, 0b0101101101010000 }, /* 29 30 29 30 30 29 30 30 29 30 29 30 355
+1811 */ { 03, 01, 25, 0b0010101101101000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 30 384
+1812 */ { 00, 02, 13, 0b0010101011100000 }, /* 29 29 30 29 30 29 30 29 30 30 30 29 354
+1813 */ { 00, 02, 01, 0b1010001011100000 }, /* 30 29 30 29 29 29 30 29 30 30 30 29 354
+1814 */ { 02, 01, 21, 0b1101000101101000 }, /* 30 30 29 30 29 29 29 30 29 30 30 29 30 384
+1815 */ { 00, 02, 09, 0b1100100101010000 }, /* 30 30 29 29 30 29 29 30 29 30 29 30 354
+1816 */ { 06, 01, 29, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1817 */ { 00, 02, 16, 0b1011010100100000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 354
+1818 */ { 00, 02, 05, 0b1011011010010000 }, /* 30 29 30 30 29 30 30 29 30 29 29 30 355
+1819 */ { 04, 01, 26, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1820 */ { 00, 02, 14, 0b0101010111010000 }, /* 29 30 29 30 29 30 29 30 30 30 29 30 355
+1821 */ { 00, 02, 03, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1822 */ { 03, 01, 23, 0b1010001011011000 }, /* 30 29 30 29 29 29 30 29 30 30 29 30 30 384
+1823 */ { 00, 02, 11, 0b1010001010110000 }, /* 30 29 30 29 29 29 30 29 30 29 30 30 354
+1824 */ { 07, 01, 31, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1825 */ { 00, 02, 18, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1826 */ { 00, 02, 07, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1827 */ { 05, 01, 27, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1828 */ { 00, 02, 15, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+1829 */ { 00, 02, 04, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+1830 */ { 04, 01, 25, 0b0010010110111000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1831 */ { 00, 02, 13, 0b0010010101110000 }, /* 29 29 30 29 29 30 29 30 29 30 30 30 354
+1832 */ { 09, 02, 02, 0b0101001010111000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1833 */ { 00, 02, 20, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1834 */ { 00, 02, 09, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1835 */ { 06, 01, 29, 0b0110110010101000 }, /* 29 30 30 29 30 30 29 29 30 29 30 29 30 384
+1836 */ { 00, 02, 17, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1837 */ { 00, 02, 05, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1838 */ { 04, 01, 26, 0b0101001101101000 }, /* 29 30 29 30 29 29 30 30 29 30 30 29 30 384
+1839 */ { 00, 02, 14, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1840 */ { 00, 02, 03, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1841 */ { 03, 01, 23, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 29 383
+1842 */ { 00, 02, 10, 0b1101001010100000 }, /* 30 30 29 30 29 29 30 29 30 29 30 29 354
+1843 */ { 07, 01, 30, 0b1110100101010000 }, /* 30 30 30 29 30 29 29 30 29 30 29 30 29 384
+1844 */ { 00, 02, 18, 0b1101010101010000 }, /* 30 30 29 30 29 30 29 30 29 30 29 30 355
+1845 */ { 00, 02, 07, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+1846 */ { 05, 01, 27, 0b1010101011010000 }, /* 30 29 30 29 30 29 30 29 30 30 29 30 29 384
+1847 */ { 00, 02, 15, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+1848 */ { 00, 02, 05, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1849 */ { 04, 01, 24, 0b1010010101011000 }, /* 30 29 30 29 29 30 29 30 29 30 29 30 30 384
+1850 */ { 00, 02, 12, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1851 */ { 08, 02, 01, 0b1101001001011000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+1852 */ { 00, 02, 20, 0b1011001010010000 }, /* 30 29 30 30 29 29 30 29 30 29 29 30 354
+1853 */ { 00, 02, 08, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1854 */ { 07, 01, 29, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1855 */ { 00, 02, 17, 0b0010110110100000 }, /* 29 29 30 29 30 30 29 30 30 29 30 29 354
+1856 */ { 00, 02, 06, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+1857 */ { 05, 01, 26, 0b0100101010111000 }, /* 29 30 29 29 30 29 30 29 30 29 30 30 30 384
+1858 */ { 00, 02, 14, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1859 */ { 00, 02, 03, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1860 */ { 03, 01, 23, 0b1011001001011000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1861 */ { 00, 02, 10, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 354
+1862 */ { 08, 01, 30, 0b1010110101001000 }, /* 30 29 30 29 30 30 29 30 29 30 29 29 30 384
+1863 */ { 00, 02, 18, 0b0110101101010000 }, /* 29 30 30 29 30 29 30 30 29 30 29 30 355
+1864 */ { 00, 02, 08, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1865 */ { 05, 01, 27, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+1866 */ { 00, 02, 15, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 355
+1867 */ { 00, 02, 05, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1868 */ { 04, 01, 25, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1869 */ { 00, 02, 11, 0b1110010010100000 }, /* 30 30 30 29 29 30 29 29 30 29 30 29 354
+1870 */ { 10, 01, 31, 0b1110101001010000 }, /* 30 30 30 29 30 29 30 29 29 30 29 30 29 384
+1871 */ { 00, 02, 19, 0b1101101010010000 }, /* 30 30 29 30 30 29 30 29 30 29 29 30 355
+1872 */ { 00, 02, 09, 0b0101101011010000 }, /* 29 30 29 30 30 29 30 29 30 30 29 30 355
+1873 */ { 06, 01, 29, 0b0010101101101000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 30 384
+1874 */ { 00, 02, 17, 0b0010101011100000 }, /* 29 29 30 29 30 29 30 29 30 30 30 29 354
+1875 */ { 00, 02, 06, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1876 */ { 05, 01, 26, 0b1100100101101000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1877 */ { 00, 02, 13, 0b1100100101010000 }, /* 30 30 29 29 30 29 29 30 29 30 29 30 354
+1878 */ { 00, 02, 02, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1879 */ { 03, 01, 22, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1880 */ { 00, 02, 10, 0b1011011010010000 }, /* 30 29 30 30 29 30 30 29 30 29 29 30 355
+1881 */ { 07, 01, 30, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1882 */ { 00, 02, 18, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+1883 */ { 00, 02, 08, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1884 */ { 05, 01, 28, 0b1001001011011000 }, /* 30 29 29 30 29 29 30 29 30 30 29 30 30 384
+1885 */ { 00, 02, 15, 0b1001001010110000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 354
+1886 */ { 00, 02, 04, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1887 */ { 04, 01, 24, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1888 */ { 00, 02, 12, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1889 */ { 00, 01, 31, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1890 */ { 02, 01, 21, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1891 */ { 00, 02, 09, 0b0101010110110000 }, /* 29 30 29 30 29 30 29 30 30 29 30 30 355
+1892 */ { 06, 01, 30, 0b0010010110111000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1893 */ { 00, 02, 17, 0b0010010101110000 }, /* 29 29 30 29 29 30 29 30 29 30 30 30 354
+1894 */ { 00, 02, 06, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1895 */ { 05, 01, 26, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1896 */ { 00, 02, 13, 0b1110100101010000 }, /* 30 30 30 29 30 29 29 30 29 30 29 30 355
+1897 */ { 00, 02, 02, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1898 */ { 03, 01, 22, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1899 */ { 00, 02, 10, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1900 */ { 08, 01, 31, 0b0100101101101000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1901 */ { 00, 02, 19, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1902 */ { 00, 02, 08, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1903 */ { 05, 01, 29, 0b0101001001101000 }, /* 29 30 29 30 29 29 30 29 29 30 30 29 30 383
+1904 */ { 00, 02, 16, 0b1101001001100000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 354
+1905 */ { 00, 02, 04, 0b1101100101010000 }, /* 30 30 29 30 30 29 29 30 29 30 29 30 355
+1906 */ { 04, 01, 25, 0b0110101010101000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1907 */ { 00, 02, 13, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1908 */ { 00, 02, 02, 0b1001101011010000 }, /* 30 29 29 30 30 29 30 29 30 30 29 30 355
+1909 */ { 02, 01, 22, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1910 */ { 00, 02, 10, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1911 */ { 06, 01, 30, 0b1010010011011000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1912 */ { 00, 02, 18, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+1913 */ { 00, 02, 06, 0b1101001001010000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 354
+1914 */ { 05, 01, 26, 0b1101100101001000 }, /* 30 30 29 30 30 29 29 30 29 30 29 29 30 384
+1915 */ { 00, 02, 14, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1916 */ { 00, 02, 04, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1917 */ { 02, 01, 23, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1918 */ { 00, 02, 11, 0b1001010111010000 }, /* 30 29 29 30 29 30 29 30 30 30 29 30 355
+1919 */ { 07, 02, 01, 0b0100101011011000 }, /* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+1920 */ { 00, 02, 20, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1921 */ { 00, 02, 08, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1922 */ { 05, 01, 28, 0b1011001001011000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1923 */ { 00, 02, 16, 0b0110101010010000 }, /* 29 30 30 29 30 29 30 29 30 29 29 30 354
+1924 */ { 00, 02, 05, 0b1010110101000000 }, /* 30 29 30 29 30 30 29 30 29 30 29 29 354
+1925 */ { 04, 01, 24, 0b1011010110101000 }, /* 30 29 30 30 29 30 29 30 30 29 30 29 30 385
+1926 */ { 00, 02, 13, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1927 */ { 00, 02, 02, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1928 */ { 02, 01, 23, 0b0100100110111000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1929 */ { 00, 02, 10, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1930 */ { 06, 01, 30, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1931 */ { 00, 02, 17, 0b1110010010100000 }, /* 30 30 30 29 29 30 29 29 30 29 30 29 354
+1932 */ { 00, 02, 06, 0b1110101001010000 }, /* 30 30 30 29 30 29 30 29 29 30 29 30 355
+1933 */ { 05, 01, 26, 0b0110110101001000 }, /* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1934 */ { 00, 02, 14, 0b0101101101010000 }, /* 29 30 29 30 30 29 30 30 29 30 29 30 355
+1935 */ { 00, 02, 04, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1936 */ { 03, 01, 24, 0b1001010101110000 }, /* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+1937 */ { 00, 02, 11, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+1938 */ { 07, 01, 31, 0b1100100101101000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1939 */ { 00, 02, 19, 0b1100100101010000 }, /* 30 30 29 29 30 29 29 30 29 30 29 30 354
+1940 */ { 00, 02, 08, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+1941 */ { 06, 01, 27, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1942 */ { 00, 02, 15, 0b1011011010010000 }, /* 30 29 30 30 29 30 30 29 30 29 29 30 355
+1943 */ { 00, 02, 05, 0b0101011011010000 }, /* 29 30 29 30 29 30 30 29 30 30 29 30 355
+1944 */ { 04, 01, 26, 0b0010101011011000 }, /* 29 29 30 29 30 29 30 29 30 30 29 30 30 384
+1945 */ { 00, 02, 13, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+1946 */ { 00, 02, 02, 0b1001001011010000 }, /* 30 29 29 30 29 29 30 29 30 30 29 30 354
+1947 */ { 02, 01, 22, 0b1100100101011000 }, /* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+1948 */ { 00, 02, 10, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+1949 */ { 07, 01, 29, 0b1101010010101000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1950 */ { 00, 02, 17, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+1951 */ { 00, 02, 06, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+1952 */ { 05, 01, 27, 0b0101011010101000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1953 */ { 00, 02, 14, 0b0100110110110000 }, /* 29 30 29 29 30 30 29 30 30 29 30 30 355
+1954 */ { 00, 02, 04, 0b0010010110110000 }, /* 29 29 30 29 29 30 29 30 30 29 30 30 354
+1955 */ { 03, 01, 24, 0b1001001010111000 }, /* 30 29 29 30 29 29 30 29 30 29 30 30 30 384
+1956 */ { 00, 02, 12, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+1957 */ { 08, 01, 31, 0b1010100101011000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1958 */ { 00, 02, 19, 0b0110100101010000 }, /* 29 30 30 29 30 29 29 30 29 30 29 30 354
+1959 */ { 00, 02, 08, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+1960 */ { 06, 01, 28, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1961 */ { 00, 02, 15, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 355
+1962 */ { 00, 02, 05, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+1963 */ { 04, 01, 25, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1964 */ { 00, 02, 13, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+1965 */ { 00, 02, 02, 0b0101001001110000 }, /* 29 30 29 30 29 29 30 29 29 30 30 30 354
+1966 */ { 03, 01, 22, 0b0110100100110000 }, /* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+1967 */ { 00, 02, 09, 0b1101100101010000 }, /* 30 30 29 30 30 29 29 30 29 30 29 30 355
+1968 */ { 07, 01, 30, 0b0110101010101000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1969 */ { 00, 02, 17, 0b0101011010100000 }, /* 29 30 29 30 29 30 30 29 30 29 30 29 354
+1970 */ { 00, 02, 06, 0b1001101011010000 }, /* 30 29 29 30 30 29 30 29 30 30 29 30 355
+1971 */ { 05, 01, 27, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1972 */ { 00, 02, 15, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+1973 */ { 00, 02, 03, 0b1010010011100000 }, /* 30 29 30 29 29 30 29 29 30 30 30 29 354
+1974 */ { 04, 01, 23, 0b1101001001101000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1975 */ { 00, 02, 11, 0b1101001001010000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 354
+1976 */ { 08, 01, 31, 0b1101010101001000 }, /* 30 30 29 30 29 30 29 30 29 30 29 29 30 384
+1977 */ { 00, 02, 18, 0b1011010101000000 }, /* 30 29 30 30 29 30 29 30 29 30 29 29 354
+1978 */ { 00, 02, 07, 0b1101011010100000 }, /* 30 30 29 30 29 30 30 29 30 29 30 29 355
+1979 */ { 06, 01, 28, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1980 */ { 00, 02, 16, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1981 */ { 00, 02, 05, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+1982 */ { 04, 01, 25, 0b1010010011011000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1983 */ { 00, 02, 13, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+1984 */ { 10, 02, 02, 0b1011001001011000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1985 */ { 00, 02, 20, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 354
+1986 */ { 00, 02, 09, 0b0110110101000000 }, /* 29 30 30 29 30 30 29 30 29 30 29 29 354
+1987 */ { 06, 01, 29, 0b1011010110101000 }, /* 30 29 30 30 29 30 29 30 30 29 30 29 30 385
+1988 */ { 00, 02, 18, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1989 */ { 00, 02, 06, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 355
+1990 */ { 05, 01, 27, 0b0100100110111000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1991 */ { 00, 02, 15, 0b0100100101110000 }, /* 29 30 29 29 30 29 29 30 29 30 30 30 354
+1992 */ { 00, 02, 04, 0b0110010010110000 }, /* 29 30 30 29 29 30 29 29 30 29 30 30 354
+1993 */ { 03, 01, 23, 0b0110101001010000 }, /* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1994 */ { 00, 02, 10, 0b1110101001010000 }, /* 30 30 30 29 30 29 30 29 29 30 29 30 355
+1995 */ { 08, 01, 31, 0b0110110101001000 }, /* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1996 */ { 00, 02, 19, 0b0101101011010000 }, /* 29 30 29 30 30 29 30 29 30 30 29 30 355
+1997 */ { 00, 02, 08, 0b0010101101100000 }, /* 29 29 30 29 30 29 30 30 29 30 30 29 354
+1998 */ { 05, 01, 28, 0b1001001101110000 }, /* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1999 */ { 00, 02, 16, 0b1001001011100000 }, /* 30 29 29 30 29 29 30 29 30 30 30 29 354
+2000 */ { 00, 02, 05, 0b1100100101100000 }, /* 30 30 29 29 30 29 29 30 29 30 30 29 354
+2001 */ { 04, 01, 24, 0b1110010010101000 }, /* 30 30 30 29 29 30 29 29 30 29 30 29 30 384
+2002 */ { 00, 02, 12, 0b1101010010100000 }, /* 30 30 29 30 29 30 29 29 30 29 30 29 354
+2003 */ { 00, 02, 01, 0b1101101001010000 }, /* 30 30 29 30 30 29 30 29 29 30 29 30 355
+2004 */ { 02, 01, 22, 0b0101101010101000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2005 */ { 00, 02, 09, 0b0101011011000000 }, /* 29 30 29 30 29 30 30 29 30 30 29 29 354
+2006 */ { 07, 01, 29, 0b1010101011011000 }, /* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+2007 */ { 00, 02, 18, 0b0010010111010000 }, /* 29 29 30 29 29 30 29 30 30 30 29 30 354
+2008 */ { 00, 02, 07, 0b1001001011010000 }, /* 30 29 29 30 29 29 30 29 30 30 29 30 354
+2009 */ { 05, 01, 26, 0b1100100101011000 }, /* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+2010 */ { 00, 02, 14, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+2011 */ { 00, 02, 03, 0b1011010010100000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 354
+2012 */ { 03, 01, 23, 0b1011101001010000 }, /* 30 29 30 30 30 29 30 29 29 30 29 30 29 384
+2013 */ { 00, 02, 10, 0b1011010101010000 }, /* 30 29 30 30 29 30 29 30 29 30 29 30 355
+2014 */ { 09, 01, 31, 0b0101010110101000 }, /* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2015 */ { 00, 02, 19, 0b0100101110100000 }, /* 29 30 29 29 30 29 30 30 30 29 30 29 354
+2016 */ { 00, 02, 08, 0b1010010110110000 }, /* 30 29 30 29 29 30 29 30 30 29 30 30 355
+2017 */ { 05, 01, 28, 0b0101001010111000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+2018 */ { 00, 02, 16, 0b0101001010110000 }, /* 29 30 29 30 29 29 30 29 30 29 30 30 354
+2019 */ { 00, 02, 05, 0b1010100101010000 }, /* 30 29 30 29 30 29 29 30 29 30 29 30 354
+2020 */ { 04, 01, 25, 0b1011010010101000 }, /* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+2021 */ { 00, 02, 12, 0b0110101010100000 }, /* 29 30 30 29 30 29 30 29 30 29 30 29 354
+2022 */ { 00, 02, 01, 0b1010110101010000 }, /* 30 29 30 29 30 30 29 30 29 30 29 30 355
+2023 */ { 02, 01, 22, 0b0101010110101000 }, /* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2024 */ { 00, 02, 10, 0b0100101101100000 }, /* 29 30 29 29 30 29 30 30 29 30 30 29 354
+2025 */ { 06, 01, 29, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+2026 */ { 00, 02, 17, 0b1010010101110000 }, /* 30 29 30 29 29 30 29 30 29 30 30 30 355
+2027 */ { 00, 02, 07, 0b0101001001110000 }, /* 29 30 29 30 29 29 30 29 29 30 30 30 354
+2028 */ { 05, 01, 27, 0b0110100100110000 }, /* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+2029 */ { 00, 02, 13, 0b1101100100110000 }, /* 30 30 29 30 30 29 29 30 29 29 30 30 355
+2030 */ { 00, 02, 03, 0b0101101010100000 }, /* 29 30 29 30 30 29 30 29 30 29 30 29 354
+2031 */ { 03, 01, 23, 0b1010101101010000 }, /* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+2032 */ { 00, 02, 11, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 355
+2033 */ { 11, 01, 31, 0b0100101011101000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+2034 */ { 00, 02, 19, 0b0100101011100000 }, /* 29 30 29 29 30 29 30 29 30 30 30 29 354
+2035 */ { 00, 02, 08, 0b1010010011010000 }, /* 30 29 30 29 29 30 29 29 30 30 29 30 354
+2036 */ { 06, 01, 28, 0b1101001001101000 }, /* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+2037 */ { 00, 02, 15, 0b1101001001010000 }, /* 30 30 29 30 29 29 30 29 29 30 29 30 354
+2038 */ { 00, 02, 04, 0b1101010100100000 }, /* 30 30 29 30 29 30 29 30 29 29 30 29 354
+2039 */ { 05, 01, 24, 0b1101101010100000 }, /* 30 30 29 30 30 29 30 29 30 29 30 29 29 384
+2040 */ { 00, 02, 12, 0b1011011010100000 }, /* 30 29 30 30 29 30 30 29 30 29 30 29 355
+2041 */ { 00, 02, 01, 0b1001011011010000 }, /* 30 29 29 30 29 30 30 29 30 30 29 30 355
+2042 */ { 02, 01, 22, 0b0100101011011000 }, /* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+2043 */ { 00, 02, 10, 0b0100100110110000 }, /* 29 30 29 29 30 29 29 30 30 29 30 30 354
+2044 */ { 07, 01, 30, 0b1010010010111000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+2045 */ { 00, 02, 17, 0b1010010010110000 }, /* 30 29 30 29 29 30 29 29 30 29 30 30 354
+2046 */ { 00, 02, 06, 0b1011001001010000 }, /* 30 29 30 30 29 29 30 29 29 30 29 30 354
+2047 */ { 05, 01, 26, 0b1011010100101000 }, /* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+2048 */ { 00, 02, 14, 0b0110110101000000 }, /* 29 30 30 29 30 30 29 30 29 30 29 29 354
+2049 */ { 00, 02, 02, 0b1010110110100000 }, /* 30 29 30 29 30 30 29 30 30 29 30 29 355
+2050 */ { 03, 01, 23, 0b1001010110110000 }, /* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+ */ };
+
+ internal override int MinCalendarYear
+ {
+ get
+ {
+ return (MIN_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override int MaxCalendarYear
+ {
+ get
+ {
+ return (MAX_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override DateTime MinDate
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ internal override DateTime MaxDate
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ internal override EraInfo[] CalEraInfo
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ internal override int GetYearInfo(int lunarYear, int index)
+ {
+ if ((lunarYear < MIN_LUNISOLAR_YEAR) || (lunarYear > MAX_LUNISOLAR_YEAR))
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MIN_LUNISOLAR_YEAR,
+ MAX_LUNISOLAR_YEAR));
+ }
+ return s_yinfo[lunarYear - MIN_LUNISOLAR_YEAR, index];
+ }
+
+ internal override int GetYear(int year, DateTime time)
+ {
+ return year;
+ }
+
+ internal override int GetGregorianYear(int year, int era)
+ {
+ if (era != CurrentEra && era != GregorianEra)
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+
+ if (year < MIN_LUNISOLAR_YEAR || year > MAX_LUNISOLAR_YEAR)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR));
+ }
+
+ return year;
+ }
+
+ public KoreanLunisolarCalendar()
+ {
+ }
+
+ public override int GetEra(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return (GregorianEra);
+ }
+
+ internal override CalendarId BaseCalendarID
+ {
+ get
+ {
+ return (CalendarId.KOREA);
+ }
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.KOREANLUNISOLAR);
+ }
+ }
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { GregorianEra });
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/LocaleData.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/LocaleData.Unix.cs
new file mode 100644
index 0000000000..ccf1078ac6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/LocaleData.Unix.cs
@@ -0,0 +1,4571 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+// This file contains the handling of Windows OS specific culture features.
+
+namespace System.Globalization
+{
+ internal enum LocaleDataParts
+ {
+ Lcid = 0,
+ AnsiCodePage = 1,
+ OemCodePage = 2,
+ MacCodePage = 3,
+ EbcdicCodePage = 4,
+ GeoId = 5,
+ DigitSubstitution = 6,
+ SpecificLocaleIndex = 7,
+ ConsoleLocaleIndex = 8
+ }
+
+ internal partial class LocaleData
+ {
+ // this is done rather than using a large readonly array of strings to avoid
+ // generating a large amount of code in the static constructor.
+ // Using indices from s_localeNamesIndices, we binary search this string when mapping
+ // an culture name to Lcid. Note that these names are all lowercase and are
+ // sorted alphabetically (ordinal).
+ private const string c_localeNames =
+ // culture name Lcid
+ "aa" + // 01000 - 0
+ "aa-dj" + // 01000 - 2
+ "aa-er" + // 01000 - 7
+ "aa-et" + // 01000 - 12
+ "af" + // 00036 - 17
+ "af-na" + // 01000 - 19
+ "af-za" + // 00436 - 24
+ "agq" + // 01000 - 29
+ "agq-cm" + // 01000 - 32
+ "ak" + // 01000 - 38
+ "ak-gh" + // 01000 - 40
+ "am" + // 0005e - 45
+ "am-et" + // 0045e - 47
+ "ar" + // 00001 - 52
+ "ar-001" + // 01000 - 54
+ "ar-ae" + // 03801 - 60
+ "ar-bh" + // 03c01 - 65
+ "ar-dj" + // 01000 - 70
+ "ar-dz" + // 01401 - 75
+ "ar-eg" + // 00c01 - 80
+ "ar-er" + // 01000 - 85
+ "ar-il" + // 01000 - 90
+ "ar-iq" + // 00801 - 95
+ "ar-jo" + // 02c01 - 100
+ "ar-km" + // 01000 - 105
+ "ar-kw" + // 03401 - 110
+ "ar-lb" + // 03001 - 115
+ "ar-ly" + // 01001 - 120
+ "ar-ma" + // 01801 - 125
+ "ar-mr" + // 01000 - 130
+ "ar-om" + // 02001 - 135
+ "ar-ps" + // 01000 - 140
+ "ar-qa" + // 04001 - 145
+ "ar-sa" + // 00401 - 150
+ "ar-sd" + // 01000 - 155
+ "ar-so" + // 01000 - 160
+ "ar-ss" + // 01000 - 165
+ "ar-sy" + // 02801 - 170
+ "ar-td" + // 01000 - 175
+ "ar-tn" + // 01c01 - 180
+ "ar-ye" + // 02401 - 185
+ "arn" + // 0007a - 190
+ "arn-cl" + // 0047a - 193
+ "as" + // 0004d - 199
+ "as-in" + // 0044d - 201
+ "asa" + // 01000 - 206
+ "asa-tz" + // 01000 - 209
+ "ast" + // 01000 - 215
+ "ast-es" + // 01000 - 218
+ "az" + // 0002c - 224
+ "az-cyrl" + // 0742c - 226
+ "az-cyrl-az" + // 0082c - 233
+ "az-latn" + // 0782c - 243
+ "az-latn-az" + // 0042c - 250
+ "ba" + // 0006d - 260
+ "ba-ru" + // 0046d - 262
+ "bas" + // 01000 - 267
+ "bas-cm" + // 01000 - 270
+ "be" + // 00023 - 276
+ "be-by" + // 00423 - 278
+ "bem" + // 01000 - 283
+ "bem-zm" + // 01000 - 286
+ "bez" + // 01000 - 292
+ "bez-tz" + // 01000 - 295
+ "bg" + // 00002 - 301
+ "bg-bg" + // 00402 - 303
+ "bin" + // 00066 - 308
+ "bin-ng" + // 00466 - 311
+ "bm" + // 01000 - 317
+ "bm-latn" + // 01000 - 319
+ "bm-latn-ml" + // 01000 - 326
+ "bn" + // 00045 - 336
+ "bn-bd" + // 00845 - 338
+ "bn-in" + // 00445 - 343
+ "bo" + // 00051 - 348
+ "bo-cn" + // 00451 - 350
+ "bo-in" + // 01000 - 355
+ "br" + // 0007e - 360
+ "br-fr" + // 0047e - 362
+ "brx" + // 01000 - 367
+ "brx-in" + // 01000 - 370
+ "bs" + // 0781a - 376
+ "bs-cyrl" + // 0641a - 378
+ "bs-cyrl-ba" + // 0201a - 385
+ "bs-latn" + // 0681a - 395
+ "bs-latn-ba" + // 0141a - 402
+ "byn" + // 01000 - 412
+ "byn-er" + // 01000 - 415
+ "ca" + // 00003 - 421
+ "ca-ad" + // 01000 - 423
+ "ca-es" + // 00403 - 428
+ "ca-es-valencia" + // 00803 - 433
+ "ca-fr" + // 01000 - 447
+ "ca-it" + // 01000 - 452
+ "ce" + // 01000 - 457
+ "ce-ru" + // 01000 - 459
+ "cgg" + // 01000 - 464
+ "cgg-ug" + // 01000 - 467
+ "chr" + // 0005c - 473
+ "chr-cher" + // 07c5c - 476
+ "chr-cher-us" + // 0045c - 484
+ "co" + // 00083 - 495
+ "co-fr" + // 00483 - 497
+ "cs" + // 00005 - 502
+ "cs-cz" + // 00405 - 504
+ "cu" + // 01000 - 509
+ "cu-ru" + // 01000 - 511
+ "cy" + // 00052 - 516
+ "cy-gb" + // 00452 - 518
+ "da" + // 00006 - 523
+ "da-dk" + // 00406 - 525
+ "da-gl" + // 01000 - 530
+ "dav" + // 01000 - 535
+ "dav-ke" + // 01000 - 538
+ "de" + // 00007 - 544
+ "de-at" + // 00c07 - 546
+ "de-be" + // 01000 - 551
+ "de-ch" + // 00807 - 556
+ "de-de" + // 00407 - 561
+ "de-de_phoneb" + // 10407 - 566
+ "de-it" + // 01000 - 578
+ "de-li" + // 01407 - 583
+ "de-lu" + // 01007 - 588
+ "dje" + // 01000 - 593
+ "dje-ne" + // 01000 - 596
+ "dsb" + // 07c2e - 602
+ "dsb-de" + // 0082e - 605
+ "dua" + // 01000 - 611
+ "dua-cm" + // 01000 - 614
+ "dv" + // 00065 - 620
+ "dv-mv" + // 00465 - 622
+ "dyo" + // 01000 - 627
+ "dyo-sn" + // 01000 - 630
+ "dz" + // 01000 - 636
+ "dz-bt" + // 00c51 - 638
+ "ebu" + // 01000 - 643
+ "ebu-ke" + // 01000 - 646
+ "ee" + // 01000 - 652
+ "ee-gh" + // 01000 - 654
+ "ee-tg" + // 01000 - 659
+ "el" + // 00008 - 664
+ "el-cy" + // 01000 - 666
+ "el-gr" + // 00408 - 671
+ "en" + // 00009 - 676
+ "en-001" + // 01000 - 678
+ "en-029" + // 02409 - 684
+ "en-150" + // 01000 - 690
+ "en-ag" + // 01000 - 696
+ "en-ai" + // 01000 - 701
+ "en-as" + // 01000 - 706
+ "en-at" + // 01000 - 711
+ "en-au" + // 00c09 - 716
+ "en-bb" + // 01000 - 721
+ "en-be" + // 01000 - 726
+ "en-bi" + // 01000 - 731
+ "en-bm" + // 01000 - 736
+ "en-bs" + // 01000 - 741
+ "en-bw" + // 01000 - 746
+ "en-bz" + // 02809 - 751
+ "en-ca" + // 01009 - 756
+ "en-cc" + // 01000 - 761
+ "en-ch" + // 01000 - 766
+ "en-ck" + // 01000 - 771
+ "en-cm" + // 01000 - 776
+ "en-cx" + // 01000 - 781
+ "en-cy" + // 01000 - 786
+ "en-de" + // 01000 - 791
+ "en-dk" + // 01000 - 796
+ "en-dm" + // 01000 - 801
+ "en-er" + // 01000 - 806
+ "en-fi" + // 01000 - 811
+ "en-fj" + // 01000 - 816
+ "en-fk" + // 01000 - 821
+ "en-fm" + // 01000 - 826
+ "en-gb" + // 00809 - 831
+ "en-gd" + // 01000 - 836
+ "en-gg" + // 01000 - 841
+ "en-gh" + // 01000 - 846
+ "en-gi" + // 01000 - 851
+ "en-gm" + // 01000 - 856
+ "en-gu" + // 01000 - 861
+ "en-gy" + // 01000 - 866
+ "en-hk" + // 03c09 - 871
+ "en-id" + // 03809 - 876
+ "en-ie" + // 01809 - 881
+ "en-il" + // 01000 - 886
+ "en-im" + // 01000 - 891
+ "en-in" + // 04009 - 896
+ "en-io" + // 01000 - 901
+ "en-je" + // 01000 - 906
+ "en-jm" + // 02009 - 911
+ "en-ke" + // 01000 - 916
+ "en-ki" + // 01000 - 921
+ "en-kn" + // 01000 - 926
+ "en-ky" + // 01000 - 931
+ "en-lc" + // 01000 - 936
+ "en-lr" + // 01000 - 941
+ "en-ls" + // 01000 - 946
+ "en-mg" + // 01000 - 951
+ "en-mh" + // 01000 - 956
+ "en-mo" + // 01000 - 961
+ "en-mp" + // 01000 - 966
+ "en-ms" + // 01000 - 971
+ "en-mt" + // 01000 - 976
+ "en-mu" + // 01000 - 981
+ "en-mw" + // 01000 - 986
+ "en-my" + // 04409 - 991
+ "en-na" + // 01000 - 996
+ "en-nf" + // 01000 - 1001
+ "en-ng" + // 01000 - 1006
+ "en-nl" + // 01000 - 1011
+ "en-nr" + // 01000 - 1016
+ "en-nu" + // 01000 - 1021
+ "en-nz" + // 01409 - 1026
+ "en-pg" + // 01000 - 1031
+ "en-ph" + // 03409 - 1036
+ "en-pk" + // 01000 - 1041
+ "en-pn" + // 01000 - 1046
+ "en-pr" + // 01000 - 1051
+ "en-pw" + // 01000 - 1056
+ "en-rw" + // 01000 - 1061
+ "en-sb" + // 01000 - 1066
+ "en-sc" + // 01000 - 1071
+ "en-sd" + // 01000 - 1076
+ "en-se" + // 01000 - 1081
+ "en-sg" + // 04809 - 1086
+ "en-sh" + // 01000 - 1091
+ "en-si" + // 01000 - 1096
+ "en-sl" + // 01000 - 1101
+ "en-ss" + // 01000 - 1106
+ "en-sx" + // 01000 - 1111
+ "en-sz" + // 01000 - 1116
+ "en-tc" + // 01000 - 1121
+ "en-tk" + // 01000 - 1126
+ "en-to" + // 01000 - 1131
+ "en-tt" + // 02c09 - 1136
+ "en-tv" + // 01000 - 1141
+ "en-tz" + // 01000 - 1146
+ "en-ug" + // 01000 - 1151
+ "en-um" + // 01000 - 1156
+ "en-us" + // 00409 - 1161
+ "en-vc" + // 01000 - 1166
+ "en-vg" + // 01000 - 1171
+ "en-vi" + // 01000 - 1176
+ "en-vu" + // 01000 - 1181
+ "en-ws" + // 01000 - 1186
+ "en-za" + // 01c09 - 1191
+ "en-zm" + // 01000 - 1196
+ "en-zw" + // 03009 - 1201
+ "eo" + // 01000 - 1206
+ "eo-001" + // 01000 - 1208
+ "es" + // 0000a - 1214
+ "es-419" + // 0580a - 1216
+ "es-ar" + // 02c0a - 1222
+ "es-bo" + // 0400a - 1227
+ "es-br" + // 01000 - 1232
+ "es-cl" + // 0340a - 1237
+ "es-co" + // 0240a - 1242
+ "es-cr" + // 0140a - 1247
+ "es-cu" + // 05c0a - 1252
+ "es-do" + // 01c0a - 1257
+ "es-ec" + // 0300a - 1262
+ "es-es" + // 00c0a - 1267
+ "es-es_tradnl" + // 0040a - 1272
+ "es-gq" + // 01000 - 1284
+ "es-gt" + // 0100a - 1289
+ "es-hn" + // 0480a - 1294
+ "es-mx" + // 0080a - 1299
+ "es-ni" + // 04c0a - 1304
+ "es-pa" + // 0180a - 1309
+ "es-pe" + // 0280a - 1314
+ "es-ph" + // 01000 - 1319
+ "es-pr" + // 0500a - 1324
+ "es-py" + // 03c0a - 1329
+ "es-sv" + // 0440a - 1334
+ "es-us" + // 0540a - 1339
+ "es-uy" + // 0380a - 1344
+ "es-ve" + // 0200a - 1349
+ "et" + // 00025 - 1354
+ "et-ee" + // 00425 - 1356
+ "eu" + // 0002d - 1361
+ "eu-es" + // 0042d - 1363
+ "ewo" + // 01000 - 1368
+ "ewo-cm" + // 01000 - 1371
+ "fa" + // 00029 - 1377
+ "fa-ir" + // 00429 - 1379
+ "ff" + // 00067 - 1384
+ "ff-cm" + // 01000 - 1386
+ "ff-gn" + // 01000 - 1391
+ "ff-latn" + // 07c67 - 1396
+ "ff-latn-sn" + // 00867 - 1403
+ "ff-mr" + // 01000 - 1413
+ "ff-ng" + // 00467 - 1418
+ "fi" + // 0000b - 1423
+ "fi-fi" + // 0040b - 1425
+ "fil" + // 00064 - 1430
+ "fil-ph" + // 00464 - 1433
+ "fo" + // 00038 - 1439
+ "fo-dk" + // 01000 - 1441
+ "fo-fo" + // 00438 - 1446
+ "fr" + // 0000c - 1451
+ "fr-029" + // 01c0c - 1453
+ "fr-be" + // 0080c - 1459
+ "fr-bf" + // 01000 - 1464
+ "fr-bi" + // 01000 - 1469
+ "fr-bj" + // 01000 - 1474
+ "fr-bl" + // 01000 - 1479
+ "fr-ca" + // 00c0c - 1484
+ "fr-cd" + // 0240c - 1489
+ "fr-cf" + // 01000 - 1494
+ "fr-cg" + // 01000 - 1499
+ "fr-ch" + // 0100c - 1504
+ "fr-ci" + // 0300c - 1509
+ "fr-cm" + // 02c0c - 1514
+ "fr-dj" + // 01000 - 1519
+ "fr-dz" + // 01000 - 1524
+ "fr-fr" + // 0040c - 1529
+ "fr-ga" + // 01000 - 1534
+ "fr-gf" + // 01000 - 1539
+ "fr-gn" + // 01000 - 1544
+ "fr-gp" + // 01000 - 1549
+ "fr-gq" + // 01000 - 1554
+ "fr-ht" + // 03c0c - 1559
+ "fr-km" + // 01000 - 1564
+ "fr-lu" + // 0140c - 1569
+ "fr-ma" + // 0380c - 1574
+ "fr-mc" + // 0180c - 1579
+ "fr-mf" + // 01000 - 1584
+ "fr-mg" + // 01000 - 1589
+ "fr-ml" + // 0340c - 1594
+ "fr-mq" + // 01000 - 1599
+ "fr-mr" + // 01000 - 1604
+ "fr-mu" + // 01000 - 1609
+ "fr-nc" + // 01000 - 1614
+ "fr-ne" + // 01000 - 1619
+ "fr-pf" + // 01000 - 1624
+ "fr-pm" + // 01000 - 1629
+ "fr-re" + // 0200c - 1634
+ "fr-rw" + // 01000 - 1639
+ "fr-sc" + // 01000 - 1644
+ "fr-sn" + // 0280c - 1649
+ "fr-sy" + // 01000 - 1654
+ "fr-td" + // 01000 - 1659
+ "fr-tg" + // 01000 - 1664
+ "fr-tn" + // 01000 - 1669
+ "fr-vu" + // 01000 - 1674
+ "fr-wf" + // 01000 - 1679
+ "fr-yt" + // 01000 - 1684
+ "fur" + // 01000 - 1689
+ "fur-it" + // 01000 - 1692
+ "fy" + // 00062 - 1698
+ "fy-nl" + // 00462 - 1700
+ "ga" + // 0003c - 1705
+ "ga-ie" + // 0083c - 1707
+ "gd" + // 00091 - 1712
+ "gd-gb" + // 00491 - 1714
+ "gl" + // 00056 - 1719
+ "gl-es" + // 00456 - 1721
+ "gn" + // 00074 - 1726
+ "gn-py" + // 00474 - 1728
+ "gsw" + // 00084 - 1733
+ "gsw-ch" + // 01000 - 1736
+ "gsw-fr" + // 00484 - 1742
+ "gsw-li" + // 01000 - 1748
+ "gu" + // 00047 - 1754
+ "gu-in" + // 00447 - 1756
+ "guz" + // 01000 - 1761
+ "guz-ke" + // 01000 - 1764
+ "gv" + // 01000 - 1770
+ "gv-im" + // 01000 - 1772
+ "ha" + // 00068 - 1777
+ "ha-latn" + // 07c68 - 1779
+ "ha-latn-gh" + // 01000 - 1786
+ "ha-latn-ne" + // 01000 - 1796
+ "ha-latn-ng" + // 00468 - 1806
+ "haw" + // 00075 - 1816
+ "haw-us" + // 00475 - 1819
+ "he" + // 0000d - 1825
+ "he-il" + // 0040d - 1827
+ "hi" + // 00039 - 1832
+ "hi-in" + // 00439 - 1834
+ "hr" + // 0001a - 1839
+ "hr-ba" + // 0101a - 1841
+ "hr-hr" + // 0041a - 1846
+ "hsb" + // 0002e - 1851
+ "hsb-de" + // 0042e - 1854
+ "hu" + // 0000e - 1860
+ "hu-hu" + // 0040e - 1862
+ "hu-hu_technl" + // 1040e - 1867
+ "hy" + // 0002b - 1879
+ "hy-am" + // 0042b - 1881
+ "ia" + // 01000 - 1886
+ "ia-001" + // 01000 - 1888
+ "ia-fr" + // 01000 - 1894
+ "ibb" + // 00069 - 1899
+ "ibb-ng" + // 00469 - 1902
+ "id" + // 00021 - 1908
+ "id-id" + // 00421 - 1910
+ "ig" + // 00070 - 1915
+ "ig-ng" + // 00470 - 1917
+ "ii" + // 00078 - 1922
+ "ii-cn" + // 00478 - 1924
+ "is" + // 0000f - 1929
+ "is-is" + // 0040f - 1931
+ "it" + // 00010 - 1936
+ "it-ch" + // 00810 - 1938
+ "it-it" + // 00410 - 1943
+ "it-sm" + // 01000 - 1948
+ "iu" + // 0005d - 1953
+ "iu-cans" + // 0785d - 1955
+ "iu-cans-ca" + // 0045d - 1962
+ "iu-latn" + // 07c5d - 1972
+ "iu-latn-ca" + // 0085d - 1979
+ "ja" + // 00011 - 1989
+ "ja-jp" + // 00411 - 1991
+ "ja-jp_radstr" + // 40411 - 1996
+ "jgo" + // 01000 - 2008
+ "jgo-cm" + // 01000 - 2011
+ "jmc" + // 01000 - 2017
+ "jmc-tz" + // 01000 - 2020
+ "jv" + // 01000 - 2026
+ "jv-java" + // 01000 - 2028
+ "jv-java-id" + // 01000 - 2035
+ "jv-latn" + // 01000 - 2045
+ "jv-latn-id" + // 01000 - 2052
+ "ka" + // 00037 - 2062
+ "ka-ge" + // 00437 - 2064
+ "ka-ge_modern" + // 10437 - 2069
+ "kab" + // 01000 - 2081
+ "kab-dz" + // 01000 - 2084
+ "kam" + // 01000 - 2090
+ "kam-ke" + // 01000 - 2093
+ "kde" + // 01000 - 2099
+ "kde-tz" + // 01000 - 2102
+ "kea" + // 01000 - 2108
+ "kea-cv" + // 01000 - 2111
+ "khq" + // 01000 - 2117
+ "khq-ml" + // 01000 - 2120
+ "ki" + // 01000 - 2126
+ "ki-ke" + // 01000 - 2128
+ "kk" + // 0003f - 2133
+ "kk-kz" + // 0043f - 2135
+ "kkj" + // 01000 - 2140
+ "kkj-cm" + // 01000 - 2143
+ "kl" + // 0006f - 2149
+ "kl-gl" + // 0046f - 2151
+ "kln" + // 01000 - 2156
+ "kln-ke" + // 01000 - 2159
+ "km" + // 00053 - 2165
+ "km-kh" + // 00453 - 2167
+ "kn" + // 0004b - 2172
+ "kn-in" + // 0044b - 2174
+ "ko" + // 00012 - 2179
+ "ko-kp" + // 01000 - 2181
+ "ko-kr" + // 00412 - 2186
+ "kok" + // 00057 - 2191
+ "kok-in" + // 00457 - 2194
+ "kr" + // 00071 - 2200
+ "kr-ng" + // 00471 - 2202
+ "ks" + // 00060 - 2207
+ "ks-arab" + // 00460 - 2209
+ "ks-arab-in" + // 01000 - 2216
+ "ks-deva" + // 01000 - 2226
+ "ks-deva-in" + // 00860 - 2233
+ "ksb" + // 01000 - 2243
+ "ksb-tz" + // 01000 - 2246
+ "ksf" + // 01000 - 2252
+ "ksf-cm" + // 01000 - 2255
+ "ksh" + // 01000 - 2261
+ "ksh-de" + // 01000 - 2264
+ "ku" + // 00092 - 2270
+ "ku-arab" + // 07c92 - 2272
+ "ku-arab-iq" + // 00492 - 2279
+ "ku-arab-ir" + // 01000 - 2289
+ "kw" + // 01000 - 2299
+ "kw-gb" + // 01000 - 2301
+ "ky" + // 00040 - 2306
+ "ky-kg" + // 00440 - 2308
+ "la" + // 00076 - 2313
+ "la-001" + // 00476 - 2315
+ "lag" + // 01000 - 2321
+ "lag-tz" + // 01000 - 2324
+ "lb" + // 0006e - 2330
+ "lb-lu" + // 0046e - 2332
+ "lg" + // 01000 - 2337
+ "lg-ug" + // 01000 - 2339
+ "lkt" + // 01000 - 2344
+ "lkt-us" + // 01000 - 2347
+ "ln" + // 01000 - 2353
+ "ln-ao" + // 01000 - 2355
+ "ln-cd" + // 01000 - 2360
+ "ln-cf" + // 01000 - 2365
+ "ln-cg" + // 01000 - 2370
+ "lo" + // 00054 - 2375
+ "lo-la" + // 00454 - 2377
+ "lrc" + // 01000 - 2382
+ "lrc-iq" + // 01000 - 2385
+ "lrc-ir" + // 01000 - 2391
+ "lt" + // 00027 - 2397
+ "lt-lt" + // 00427 - 2399
+ "lu" + // 01000 - 2404
+ "lu-cd" + // 01000 - 2406
+ "luo" + // 01000 - 2411
+ "luo-ke" + // 01000 - 2414
+ "luy" + // 01000 - 2420
+ "luy-ke" + // 01000 - 2423
+ "lv" + // 00026 - 2429
+ "lv-lv" + // 00426 - 2431
+ "mas" + // 01000 - 2436
+ "mas-ke" + // 01000 - 2439
+ "mas-tz" + // 01000 - 2445
+ "mer" + // 01000 - 2451
+ "mer-ke" + // 01000 - 2454
+ "mfe" + // 01000 - 2460
+ "mfe-mu" + // 01000 - 2463
+ "mg" + // 01000 - 2469
+ "mg-mg" + // 01000 - 2471
+ "mgh" + // 01000 - 2476
+ "mgh-mz" + // 01000 - 2479
+ "mgo" + // 01000 - 2485
+ "mgo-cm" + // 01000 - 2488
+ "mi" + // 00081 - 2494
+ "mi-nz" + // 00481 - 2496
+ "mk" + // 0002f - 2501
+ "mk-mk" + // 0042f - 2503
+ "ml" + // 0004c - 2508
+ "ml-in" + // 0044c - 2510
+ "mn" + // 00050 - 2515
+ "mn-cyrl" + // 07850 - 2517
+ "mn-mn" + // 00450 - 2524
+ "mn-mong" + // 07c50 - 2529
+ "mn-mong-cn" + // 00850 - 2536
+ "mn-mong-mn" + // 00c50 - 2546
+ "mni" + // 00058 - 2556
+ "mni-in" + // 00458 - 2559
+ "moh" + // 0007c - 2565
+ "moh-ca" + // 0047c - 2568
+ "mr" + // 0004e - 2574
+ "mr-in" + // 0044e - 2576
+ "ms" + // 0003e - 2581
+ "ms-bn" + // 0083e - 2583
+ "ms-my" + // 0043e - 2588
+ "ms-sg" + // 01000 - 2593
+ "mt" + // 0003a - 2598
+ "mt-mt" + // 0043a - 2600
+ "mua" + // 01000 - 2605
+ "mua-cm" + // 01000 - 2608
+ "my" + // 00055 - 2614
+ "my-mm" + // 00455 - 2616
+ "mzn" + // 01000 - 2621
+ "mzn-ir" + // 01000 - 2624
+ "naq" + // 01000 - 2630
+ "naq-na" + // 01000 - 2633
+ "nb" + // 07c14 - 2639
+ "nb-no" + // 00414 - 2641
+ "nb-sj" + // 01000 - 2646
+ "nd" + // 01000 - 2651
+ "nd-zw" + // 01000 - 2653
+ "nds" + // 01000 - 2658
+ "nds-de" + // 01000 - 2661
+ "nds-nl" + // 01000 - 2667
+ "ne" + // 00061 - 2673
+ "ne-in" + // 00861 - 2675
+ "ne-np" + // 00461 - 2680
+ "nl" + // 00013 - 2685
+ "nl-aw" + // 01000 - 2687
+ "nl-be" + // 00813 - 2692
+ "nl-bq" + // 01000 - 2697
+ "nl-cw" + // 01000 - 2702
+ "nl-nl" + // 00413 - 2707
+ "nl-sr" + // 01000 - 2712
+ "nl-sx" + // 01000 - 2717
+ "nmg" + // 01000 - 2722
+ "nmg-cm" + // 01000 - 2725
+ "nn" + // 07814 - 2731
+ "nn-no" + // 00814 - 2733
+ "nnh" + // 01000 - 2738
+ "nnh-cm" + // 01000 - 2741
+ "no" + // 00014 - 2747
+ "nqo" + // 01000 - 2749
+ "nqo-gn" + // 01000 - 2752
+ "nr" + // 01000 - 2758
+ "nr-za" + // 01000 - 2760
+ "nso" + // 0006c - 2765
+ "nso-za" + // 0046c - 2768
+ "nus" + // 01000 - 2774
+ "nus-ss" + // 01000 - 2777
+ "nyn" + // 01000 - 2783
+ "nyn-ug" + // 01000 - 2786
+ "oc" + // 00082 - 2792
+ "oc-fr" + // 00482 - 2794
+ "om" + // 00072 - 2799
+ "om-et" + // 00472 - 2801
+ "om-ke" + // 01000 - 2806
+ "or" + // 00048 - 2811
+ "or-in" + // 00448 - 2813
+ "os" + // 01000 - 2818
+ "os-ge" + // 01000 - 2820
+ "os-ru" + // 01000 - 2825
+ "pa" + // 00046 - 2830
+ "pa-arab" + // 07c46 - 2832
+ "pa-arab-pk" + // 00846 - 2839
+ "pa-in" + // 00446 - 2849
+ "pap" + // 00079 - 2854
+ "pap-029" + // 00479 - 2857
+ "pl" + // 00015 - 2864
+ "pl-pl" + // 00415 - 2866
+ "prg" + // 01000 - 2871
+ "prg-001" + // 01000 - 2874
+ "prs" + // 0008c - 2881
+ "prs-af" + // 0048c - 2884
+ "ps" + // 00063 - 2890
+ "ps-af" + // 00463 - 2892
+ "pt" + // 00016 - 2897
+ "pt-ao" + // 01000 - 2899
+ "pt-br" + // 00416 - 2904
+ "pt-ch" + // 01000 - 2909
+ "pt-cv" + // 01000 - 2914
+ "pt-gq" + // 01000 - 2919
+ "pt-gw" + // 01000 - 2924
+ "pt-lu" + // 01000 - 2929
+ "pt-mo" + // 01000 - 2934
+ "pt-mz" + // 01000 - 2939
+ "pt-pt" + // 00816 - 2944
+ "pt-st" + // 01000 - 2949
+ "pt-tl" + // 01000 - 2954
+ "qps-latn-x-sh" + // 00901 - 2959
+ "qps-ploc" + // 00501 - 2972
+ "qps-ploca" + // 005fe - 2980
+ "qps-plocm" + // 009ff - 2989
+ "quc" + // 00086 - 2998
+ "quc-latn" + // 07c86 - 3001
+ "quc-latn-gt" + // 00486 - 3009
+ "quz" + // 0006b - 3020
+ "quz-bo" + // 0046b - 3023
+ "quz-ec" + // 0086b - 3029
+ "quz-pe" + // 00c6b - 3035
+ "rm" + // 00017 - 3041
+ "rm-ch" + // 00417 - 3043
+ "rn" + // 01000 - 3048
+ "rn-bi" + // 01000 - 3050
+ "ro" + // 00018 - 3055
+ "ro-md" + // 00818 - 3057
+ "ro-ro" + // 00418 - 3062
+ "rof" + // 01000 - 3067
+ "rof-tz" + // 01000 - 3070
+ "ru" + // 00019 - 3076
+ "ru-by" + // 01000 - 3078
+ "ru-kg" + // 01000 - 3083
+ "ru-kz" + // 01000 - 3088
+ "ru-md" + // 00819 - 3093
+ "ru-ru" + // 00419 - 3098
+ "ru-ua" + // 01000 - 3103
+ "rw" + // 00087 - 3108
+ "rw-rw" + // 00487 - 3110
+ "rwk" + // 01000 - 3115
+ "rwk-tz" + // 01000 - 3118
+ "sa" + // 0004f - 3124
+ "sa-in" + // 0044f - 3126
+ "sah" + // 00085 - 3131
+ "sah-ru" + // 00485 - 3134
+ "saq" + // 01000 - 3140
+ "saq-ke" + // 01000 - 3143
+ "sbp" + // 01000 - 3149
+ "sbp-tz" + // 01000 - 3152
+ "sd" + // 00059 - 3158
+ "sd-arab" + // 07c59 - 3160
+ "sd-arab-pk" + // 00859 - 3167
+ "sd-deva" + // 01000 - 3177
+ "sd-deva-in" + // 00459 - 3184
+ "se" + // 0003b - 3194
+ "se-fi" + // 00c3b - 3196
+ "se-no" + // 0043b - 3201
+ "se-se" + // 0083b - 3206
+ "seh" + // 01000 - 3211
+ "seh-mz" + // 01000 - 3214
+ "ses" + // 01000 - 3220
+ "ses-ml" + // 01000 - 3223
+ "sg" + // 01000 - 3229
+ "sg-cf" + // 01000 - 3231
+ "shi" + // 01000 - 3236
+ "shi-latn" + // 01000 - 3239
+ "shi-latn-ma" + // 01000 - 3247
+ "shi-tfng" + // 01000 - 3258
+ "shi-tfng-ma" + // 01000 - 3266
+ "si" + // 0005b - 3277
+ "si-lk" + // 0045b - 3279
+ "sk" + // 0001b - 3284
+ "sk-sk" + // 0041b - 3286
+ "sl" + // 00024 - 3291
+ "sl-si" + // 00424 - 3293
+ "sma" + // 0783b - 3298
+ "sma-no" + // 0183b - 3301
+ "sma-se" + // 01c3b - 3307
+ "smj" + // 07c3b - 3313
+ "smj-no" + // 0103b - 3316
+ "smj-se" + // 0143b - 3322
+ "smn" + // 0703b - 3328
+ "smn-fi" + // 0243b - 3331
+ "sms" + // 0743b - 3337
+ "sms-fi" + // 0203b - 3340
+ "sn" + // 01000 - 3346
+ "sn-latn" + // 01000 - 3348
+ "sn-latn-zw" + // 01000 - 3355
+ "so" + // 00077 - 3365
+ "so-dj" + // 01000 - 3367
+ "so-et" + // 01000 - 3372
+ "so-ke" + // 01000 - 3377
+ "so-so" + // 00477 - 3382
+ "sq" + // 0001c - 3387
+ "sq-al" + // 0041c - 3389
+ "sq-mk" + // 01000 - 3394
+ "sq-xk" + // 01000 - 3399
+ "sr" + // 07c1a - 3404
+ "sr-cyrl" + // 06c1a - 3406
+ "sr-cyrl-ba" + // 01c1a - 3413
+ "sr-cyrl-cs" + // 00c1a - 3423
+ "sr-cyrl-me" + // 0301a - 3433
+ "sr-cyrl-rs" + // 0281a - 3443
+ "sr-cyrl-xk" + // 01000 - 3453
+ "sr-latn" + // 0701a - 3463
+ "sr-latn-ba" + // 0181a - 3470
+ "sr-latn-cs" + // 0081a - 3480
+ "sr-latn-me" + // 02c1a - 3490
+ "sr-latn-rs" + // 0241a - 3500
+ "sr-latn-xk" + // 01000 - 3510
+ "ss" + // 01000 - 3520
+ "ss-sz" + // 01000 - 3522
+ "ss-za" + // 01000 - 3527
+ "ssy" + // 01000 - 3532
+ "ssy-er" + // 01000 - 3535
+ "st" + // 00030 - 3541
+ "st-ls" + // 01000 - 3543
+ "st-za" + // 00430 - 3548
+ "sv" + // 0001d - 3553
+ "sv-ax" + // 01000 - 3555
+ "sv-fi" + // 0081d - 3560
+ "sv-se" + // 0041d - 3565
+ "sw" + // 00041 - 3570
+ "sw-cd" + // 01000 - 3572
+ "sw-ke" + // 00441 - 3577
+ "sw-tz" + // 01000 - 3582
+ "sw-ug" + // 01000 - 3587
+ "swc" + // 01000 - 3592
+ "swc-cd" + // 01000 - 3595
+ "syr" + // 0005a - 3601
+ "syr-sy" + // 0045a - 3604
+ "ta" + // 00049 - 3610
+ "ta-in" + // 00449 - 3612
+ "ta-lk" + // 00849 - 3617
+ "ta-my" + // 01000 - 3622
+ "ta-sg" + // 01000 - 3627
+ "te" + // 0004a - 3632
+ "te-in" + // 0044a - 3634
+ "teo" + // 01000 - 3639
+ "teo-ke" + // 01000 - 3642
+ "teo-ug" + // 01000 - 3648
+ "tg" + // 00028 - 3654
+ "tg-cyrl" + // 07c28 - 3656
+ "tg-cyrl-tj" + // 00428 - 3663
+ "th" + // 0001e - 3673
+ "th-th" + // 0041e - 3675
+ "ti" + // 00073 - 3680
+ "ti-er" + // 00873 - 3682
+ "ti-et" + // 00473 - 3687
+ "tig" + // 01000 - 3692
+ "tig-er" + // 01000 - 3695
+ "tk" + // 00042 - 3701
+ "tk-tm" + // 00442 - 3703
+ "tn" + // 00032 - 3708
+ "tn-bw" + // 00832 - 3710
+ "tn-za" + // 00432 - 3715
+ "to" + // 01000 - 3720
+ "to-to" + // 01000 - 3722
+ "tr" + // 0001f - 3727
+ "tr-cy" + // 01000 - 3729
+ "tr-tr" + // 0041f - 3734
+ "ts" + // 00031 - 3739
+ "ts-za" + // 00431 - 3741
+ "tt" + // 00044 - 3746
+ "tt-ru" + // 00444 - 3748
+ "twq" + // 01000 - 3753
+ "twq-ne" + // 01000 - 3756
+ "tzm" + // 0005f - 3762
+ "tzm-arab" + // 01000 - 3765
+ "tzm-arab-ma" + // 0045f - 3773
+ "tzm-latn" + // 07c5f - 3784
+ "tzm-latn-dz" + // 0085f - 3792
+ "tzm-latn-ma" + // 01000 - 3803
+ "tzm-tfng" + // 0785f - 3814
+ "tzm-tfng-ma" + // 0105f - 3822
+ "ug" + // 00080 - 3833
+ "ug-cn" + // 00480 - 3835
+ "uk" + // 00022 - 3840
+ "uk-ua" + // 00422 - 3842
+ "ur" + // 00020 - 3847
+ "ur-in" + // 00820 - 3849
+ "ur-pk" + // 00420 - 3854
+ "uz" + // 00043 - 3859
+ "uz-arab" + // 01000 - 3861
+ "uz-arab-af" + // 01000 - 3868
+ "uz-cyrl" + // 07843 - 3878
+ "uz-cyrl-uz" + // 00843 - 3885
+ "uz-latn" + // 07c43 - 3895
+ "uz-latn-uz" + // 00443 - 3902
+ "vai" + // 01000 - 3912
+ "vai-latn" + // 01000 - 3915
+ "vai-latn-lr" + // 01000 - 3923
+ "vai-vaii" + // 01000 - 3934
+ "vai-vaii-lr" + // 01000 - 3942
+ "ve" + // 00033 - 3953
+ "ve-za" + // 00433 - 3955
+ "vi" + // 0002a - 3960
+ "vi-vn" + // 0042a - 3962
+ "vo" + // 01000 - 3967
+ "vo-001" + // 01000 - 3969
+ "vun" + // 01000 - 3975
+ "vun-tz" + // 01000 - 3978
+ "wae" + // 01000 - 3984
+ "wae-ch" + // 01000 - 3987
+ "wal" + // 01000 - 3993
+ "wal-et" + // 01000 - 3996
+ "wo" + // 00088 - 4002
+ "wo-sn" + // 00488 - 4004
+ "x-iv_mathan" + // 1007f - 4009
+ "xh" + // 00034 - 4020
+ "xh-za" + // 00434 - 4022
+ "xog" + // 01000 - 4027
+ "xog-ug" + // 01000 - 4030
+ "yav" + // 01000 - 4036
+ "yav-cm" + // 01000 - 4039
+ "yi" + // 0003d - 4045
+ "yi-001" + // 0043d - 4047
+ "yo" + // 0006a - 4053
+ "yo-bj" + // 01000 - 4055
+ "yo-ng" + // 0046a - 4060
+ "yue" + // 01000 - 4065
+ "yue-hk" + // 01000 - 4068
+ "zgh" + // 01000 - 4074
+ "zgh-tfng" + // 01000 - 4077
+ "zgh-tfng-ma" + // 01000 - 4085
+ "zh" + // 07804 - 4096
+ "zh-chs" + // 00004 - 4098
+ "zh-cht" + // 07c04 - 4104
+ "zh-cn" + // 00804 - 4110
+ "zh-cn_phoneb" + // 50804 - 4115
+ "zh-cn_stroke" + // 20804 - 4127
+ "zh-hans" + // 00004 - 4139
+ "zh-hans-hk" + // 01000 - 4146
+ "zh-hans-mo" + // 01000 - 4156
+ "zh-hant" + // 07c04 - 4166
+ "zh-hk" + // 00c04 - 4173
+ "zh-hk_radstr" + // 40c04 - 4178
+ "zh-mo" + // 01404 - 4190
+ "zh-mo_radstr" + // 41404 - 4195
+ "zh-mo_stroke" + // 21404 - 4207
+ "zh-sg" + // 01004 - 4219
+ "zh-sg_phoneb" + // 51004 - 4224
+ "zh-sg_stroke" + // 21004 - 4236
+ "zh-tw" + // 00404 - 4248
+ "zh-tw_pronun" + // 30404 - 4253
+ "zh-tw_radstr" + // 40404 - 4265
+ "zu" + // 00035 - 4277
+ "zu-za"; // 00435 - 4279
+
+ // c_threeLetterWindowsLanguageName is string containing 3-letter Windows language names
+ // every 3-characters entry is matching locale name entry in c_localeNames
+
+ private const string c_threeLetterWindowsLanguageName =
+ "ZZZ" + // aa
+ "ZZZ" + // aa-dj
+ "ZZZ" + // aa-er
+ "ZZZ" + // aa-et
+ "AFK" + // af
+ "ZZZ" + // af-na
+ "AFK" + // af-za
+ "ZZZ" + // agq
+ "ZZZ" + // agq-cm
+ "ZZZ" + // ak
+ "ZZZ" + // ak-gh
+ "AMH" + // am
+ "AMH" + // am-et
+ "ARA" + // ar
+ "ZZZ" + // ar-001
+ "ARU" + // ar-ae
+ "ARH" + // ar-bh
+ "ZZZ" + // ar-dj
+ "ARG" + // ar-dz
+ "ARE" + // ar-eg
+ "ZZZ" + // ar-er
+ "ZZZ" + // ar-il
+ "ARI" + // ar-iq
+ "ARJ" + // ar-jo
+ "ZZZ" + // ar-km
+ "ARK" + // ar-kw
+ "ARB" + // ar-lb
+ "ARL" + // ar-ly
+ "ARM" + // ar-ma
+ "ZZZ" + // ar-mr
+ "ARO" + // ar-om
+ "ZZZ" + // ar-ps
+ "ARQ" + // ar-qa
+ "ARA" + // ar-sa
+ "ZZZ" + // ar-sd
+ "ZZZ" + // ar-so
+ "ZZZ" + // ar-ss
+ "ARS" + // ar-sy
+ "ZZZ" + // ar-td
+ "ART" + // ar-tn
+ "ARY" + // ar-ye
+ "MPD" + // arn
+ "MPD" + // arn-cl
+ "ASM" + // as
+ "ASM" + // as-in
+ "ZZZ" + // asa
+ "ZZZ" + // asa-tz
+ "ZZZ" + // ast
+ "ZZZ" + // ast-es
+ "AZE" + // az
+ "AZC" + // az-cyrl
+ "AZC" + // az-cyrl-az
+ "AZE" + // az-latn
+ "AZE" + // az-latn-az
+ "BAS" + // ba
+ "BAS" + // ba-ru
+ "ZZZ" + // bas
+ "ZZZ" + // bas-cm
+ "BEL" + // be
+ "BEL" + // be-by
+ "ZZZ" + // bem
+ "ZZZ" + // bem-zm
+ "ZZZ" + // bez
+ "ZZZ" + // bez-tz
+ "BGR" + // bg
+ "BGR" + // bg-bg
+ "ZZZ" + // bin
+ "ZZZ" + // bin-ng
+ "ZZZ" + // bm
+ "ZZZ" + // bm-latn
+ "ZZZ" + // bm-latn-ml
+ "BNB" + // bn
+ "BNB" + // bn-bd
+ "BNG" + // bn-in
+ "BOB" + // bo
+ "BOB" + // bo-cn
+ "ZZZ" + // bo-in
+ "BRE" + // br
+ "BRE" + // br-fr
+ "ZZZ" + // brx
+ "ZZZ" + // brx-in
+ "BSB" + // bs
+ "BSC" + // bs-cyrl
+ "BSC" + // bs-cyrl-ba
+ "BSB" + // bs-latn
+ "BSB" + // bs-latn-ba
+ "ZZZ" + // byn
+ "ZZZ" + // byn-er
+ "CAT" + // ca
+ "ZZZ" + // ca-ad
+ "CAT" + // ca-es
+ "VAL" + // ca-es-valencia
+ "ZZZ" + // ca-fr
+ "ZZZ" + // ca-it
+ "ZZZ" + // ce
+ "ZZZ" + // ce-ru
+ "ZZZ" + // cgg
+ "ZZZ" + // cgg-ug
+ "CRE" + // chr
+ "CRE" + // chr-cher
+ "CRE" + // chr-cher-us
+ "COS" + // co
+ "COS" + // co-fr
+ "CSY" + // cs
+ "CSY" + // cs-cz
+ "ZZZ" + // cu
+ "ZZZ" + // cu-ru
+ "CYM" + // cy
+ "CYM" + // cy-gb
+ "DAN" + // da
+ "DAN" + // da-dk
+ "ZZZ" + // da-gl
+ "ZZZ" + // dav
+ "ZZZ" + // dav-ke
+ "DEU" + // de
+ "DEA" + // de-at
+ "ZZZ" + // de-be
+ "DES" + // de-ch
+ "DEU" + // de-de
+ "DEU" + // de-de_phoneb
+ "ZZZ" + // de-it
+ "DEC" + // de-li
+ "DEL" + // de-lu
+ "ZZZ" + // dje
+ "ZZZ" + // dje-ne
+ "DSB" + // dsb
+ "DSB" + // dsb-de
+ "ZZZ" + // dua
+ "ZZZ" + // dua-cm
+ "DIV" + // dv
+ "DIV" + // dv-mv
+ "ZZZ" + // dyo
+ "ZZZ" + // dyo-sn
+ "ZZZ" + // dz
+ "ZZZ" + // dz-bt
+ "ZZZ" + // ebu
+ "ZZZ" + // ebu-ke
+ "ZZZ" + // ee
+ "ZZZ" + // ee-gh
+ "ZZZ" + // ee-tg
+ "ELL" + // el
+ "ZZZ" + // el-cy
+ "ELL" + // el-gr
+ "ENU" + // en
+ "ZZZ" + // en-001
+ "ENB" + // en-029
+ "ZZZ" + // en-150
+ "ZZZ" + // en-ag
+ "ZZZ" + // en-ai
+ "ZZZ" + // en-as
+ "ZZZ" + // en-at
+ "ENA" + // en-au
+ "ZZZ" + // en-bb
+ "ZZZ" + // en-be
+ "ZZZ" + // en-bi
+ "ZZZ" + // en-bm
+ "ZZZ" + // en-bs
+ "ZZZ" + // en-bw
+ "ENL" + // en-bz
+ "ENC" + // en-ca
+ "ZZZ" + // en-cc
+ "ZZZ" + // en-ch
+ "ZZZ" + // en-ck
+ "ZZZ" + // en-cm
+ "ZZZ" + // en-cx
+ "ZZZ" + // en-cy
+ "ZZZ" + // en-de
+ "ZZZ" + // en-dk
+ "ZZZ" + // en-dm
+ "ZZZ" + // en-er
+ "ZZZ" + // en-fi
+ "ZZZ" + // en-fj
+ "ZZZ" + // en-fk
+ "ZZZ" + // en-fm
+ "ENG" + // en-gb
+ "ZZZ" + // en-gd
+ "ZZZ" + // en-gg
+ "ZZZ" + // en-gh
+ "ZZZ" + // en-gi
+ "ZZZ" + // en-gm
+ "ZZZ" + // en-gu
+ "ZZZ" + // en-gy
+ "ENH" + // en-hk
+ "ZZZ" + // en-id
+ "ENI" + // en-ie
+ "ZZZ" + // en-il
+ "ZZZ" + // en-im
+ "ENN" + // en-in
+ "ZZZ" + // en-io
+ "ZZZ" + // en-je
+ "ENJ" + // en-jm
+ "ZZZ" + // en-ke
+ "ZZZ" + // en-ki
+ "ZZZ" + // en-kn
+ "ZZZ" + // en-ky
+ "ZZZ" + // en-lc
+ "ZZZ" + // en-lr
+ "ZZZ" + // en-ls
+ "ZZZ" + // en-mg
+ "ZZZ" + // en-mh
+ "ZZZ" + // en-mo
+ "ZZZ" + // en-mp
+ "ZZZ" + // en-ms
+ "ZZZ" + // en-mt
+ "ZZZ" + // en-mu
+ "ZZZ" + // en-mw
+ "ENM" + // en-my
+ "ZZZ" + // en-na
+ "ZZZ" + // en-nf
+ "ZZZ" + // en-ng
+ "ZZZ" + // en-nl
+ "ZZZ" + // en-nr
+ "ZZZ" + // en-nu
+ "ENZ" + // en-nz
+ "ZZZ" + // en-pg
+ "ENP" + // en-ph
+ "ZZZ" + // en-pk
+ "ZZZ" + // en-pn
+ "ZZZ" + // en-pr
+ "ZZZ" + // en-pw
+ "ZZZ" + // en-rw
+ "ZZZ" + // en-sb
+ "ZZZ" + // en-sc
+ "ZZZ" + // en-sd
+ "ZZZ" + // en-se
+ "ENE" + // en-sg
+ "ZZZ" + // en-sh
+ "ZZZ" + // en-si
+ "ZZZ" + // en-sl
+ "ZZZ" + // en-ss
+ "ZZZ" + // en-sx
+ "ZZZ" + // en-sz
+ "ZZZ" + // en-tc
+ "ZZZ" + // en-tk
+ "ZZZ" + // en-to
+ "ENT" + // en-tt
+ "ZZZ" + // en-tv
+ "ZZZ" + // en-tz
+ "ZZZ" + // en-ug
+ "ZZZ" + // en-um
+ "ENU" + // en-us
+ "ZZZ" + // en-vc
+ "ZZZ" + // en-vg
+ "ZZZ" + // en-vi
+ "ZZZ" + // en-vu
+ "ZZZ" + // en-ws
+ "ENS" + // en-za
+ "ZZZ" + // en-zm
+ "ENW" + // en-zw
+ "ZZZ" + // eo
+ "ZZZ" + // eo-001
+ "ESN" + // es
+ "ESJ" + // es-419
+ "ESS" + // es-ar
+ "ESB" + // es-bo
+ "ZZZ" + // es-br
+ "ESL" + // es-cl
+ "ESO" + // es-co
+ "ESC" + // es-cr
+ "ESK" + // es-cu
+ "ESD" + // es-do
+ "ESF" + // es-ec
+ "ESN" + // es-es
+ "ESP" + // es-es_tradnl
+ "ZZZ" + // es-gq
+ "ESG" + // es-gt
+ "ESH" + // es-hn
+ "ESM" + // es-mx
+ "ESI" + // es-ni
+ "ESA" + // es-pa
+ "ESR" + // es-pe
+ "ZZZ" + // es-ph
+ "ESU" + // es-pr
+ "ESZ" + // es-py
+ "ESE" + // es-sv
+ "EST" + // es-us
+ "ESY" + // es-uy
+ "ESV" + // es-ve
+ "ETI" + // et
+ "ETI" + // et-ee
+ "EUQ" + // eu
+ "EUQ" + // eu-es
+ "ZZZ" + // ewo
+ "ZZZ" + // ewo-cm
+ "FAR" + // fa
+ "FAR" + // fa-ir
+ "FUL" + // ff
+ "ZZZ" + // ff-cm
+ "ZZZ" + // ff-gn
+ "FUL" + // ff-latn
+ "FUL" + // ff-latn-sn
+ "ZZZ" + // ff-mr
+ "ZZZ" + // ff-ng
+ "FIN" + // fi
+ "FIN" + // fi-fi
+ "FPO" + // fil
+ "FPO" + // fil-ph
+ "FOS" + // fo
+ "ZZZ" + // fo-dk
+ "FOS" + // fo-fo
+ "FRA" + // fr
+ "ZZZ" + // fr-029
+ "FRB" + // fr-be
+ "ZZZ" + // fr-bf
+ "ZZZ" + // fr-bi
+ "ZZZ" + // fr-bj
+ "ZZZ" + // fr-bl
+ "FRC" + // fr-ca
+ "FRD" + // fr-cd
+ "ZZZ" + // fr-cf
+ "ZZZ" + // fr-cg
+ "FRS" + // fr-ch
+ "FRI" + // fr-ci
+ "FRE" + // fr-cm
+ "ZZZ" + // fr-dj
+ "ZZZ" + // fr-dz
+ "FRA" + // fr-fr
+ "ZZZ" + // fr-ga
+ "ZZZ" + // fr-gf
+ "ZZZ" + // fr-gn
+ "ZZZ" + // fr-gp
+ "ZZZ" + // fr-gq
+ "FRH" + // fr-ht
+ "ZZZ" + // fr-km
+ "FRL" + // fr-lu
+ "FRO" + // fr-ma
+ "FRM" + // fr-mc
+ "ZZZ" + // fr-mf
+ "ZZZ" + // fr-mg
+ "FRF" + // fr-ml
+ "ZZZ" + // fr-mq
+ "ZZZ" + // fr-mr
+ "ZZZ" + // fr-mu
+ "ZZZ" + // fr-nc
+ "ZZZ" + // fr-ne
+ "ZZZ" + // fr-pf
+ "ZZZ" + // fr-pm
+ "FRR" + // fr-re
+ "ZZZ" + // fr-rw
+ "ZZZ" + // fr-sc
+ "FRN" + // fr-sn
+ "ZZZ" + // fr-sy
+ "ZZZ" + // fr-td
+ "ZZZ" + // fr-tg
+ "ZZZ" + // fr-tn
+ "ZZZ" + // fr-vu
+ "ZZZ" + // fr-wf
+ "ZZZ" + // fr-yt
+ "ZZZ" + // fur
+ "ZZZ" + // fur-it
+ "FYN" + // fy
+ "FYN" + // fy-nl
+ "IRE" + // ga
+ "IRE" + // ga-ie
+ "GLA" + // gd
+ "GLA" + // gd-gb
+ "GLC" + // gl
+ "GLC" + // gl-es
+ "GRN" + // gn
+ "GRN" + // gn-py
+ "ZZZ" + // gsw
+ "ZZZ" + // gsw-ch
+ "GSW" + // gsw-fr
+ "ZZZ" + // gsw-li
+ "GUJ" + // gu
+ "GUJ" + // gu-in
+ "ZZZ" + // guz
+ "ZZZ" + // guz-ke
+ "ZZZ" + // gv
+ "ZZZ" + // gv-im
+ "HAU" + // ha
+ "HAU" + // ha-latn
+ "ZZZ" + // ha-latn-gh
+ "ZZZ" + // ha-latn-ne
+ "HAU" + // ha-latn-ng
+ "HAW" + // haw
+ "HAW" + // haw-us
+ "HEB" + // he
+ "HEB" + // he-il
+ "HIN" + // hi
+ "HIN" + // hi-in
+ "HRV" + // hr
+ "HRB" + // hr-ba
+ "HRV" + // hr-hr
+ "HSB" + // hsb
+ "HSB" + // hsb-de
+ "HUN" + // hu
+ "HUN" + // hu-hu
+ "HUN" + // hu-hu_technl
+ "HYE" + // hy
+ "HYE" + // hy-am
+ "ZZZ" + // ia
+ "ZZZ" + // ia-001
+ "ZZZ" + // ia-fr
+ "ZZZ" + // ibb
+ "ZZZ" + // ibb-ng
+ "IND" + // id
+ "IND" + // id-id
+ "IBO" + // ig
+ "IBO" + // ig-ng
+ "III" + // ii
+ "III" + // ii-cn
+ "ISL" + // is
+ "ISL" + // is-is
+ "ITA" + // it
+ "ITS" + // it-ch
+ "ITA" + // it-it
+ "ZZZ" + // it-sm
+ "IUK" + // iu
+ "IUS" + // iu-cans
+ "IUS" + // iu-cans-ca
+ "IUK" + // iu-latn
+ "IUK" + // iu-latn-ca
+ "JPN" + // ja
+ "JPN" + // ja-jp
+ "JPN" + // ja-jp_radstr
+ "ZZZ" + // jgo
+ "ZZZ" + // jgo-cm
+ "ZZZ" + // jmc
+ "ZZZ" + // jmc-tz
+ "JAV" + // jv
+ "ZZZ" + // jv-java
+ "ZZZ" + // jv-java-id
+ "JAV" + // jv-latn
+ "JAV" + // jv-latn-id
+ "KAT" + // ka
+ "KAT" + // ka-ge
+ "KAT" + // ka-ge_modern
+ "ZZZ" + // kab
+ "ZZZ" + // kab-dz
+ "ZZZ" + // kam
+ "ZZZ" + // kam-ke
+ "ZZZ" + // kde
+ "ZZZ" + // kde-tz
+ "ZZZ" + // kea
+ "ZZZ" + // kea-cv
+ "ZZZ" + // khq
+ "ZZZ" + // khq-ml
+ "ZZZ" + // ki
+ "ZZZ" + // ki-ke
+ "KKZ" + // kk
+ "KKZ" + // kk-kz
+ "ZZZ" + // kkj
+ "ZZZ" + // kkj-cm
+ "KAL" + // kl
+ "KAL" + // kl-gl
+ "ZZZ" + // kln
+ "ZZZ" + // kln-ke
+ "KHM" + // km
+ "KHM" + // km-kh
+ "KDI" + // kn
+ "KDI" + // kn-in
+ "KOR" + // ko
+ "ZZZ" + // ko-kp
+ "KOR" + // ko-kr
+ "KNK" + // kok
+ "KNK" + // kok-in
+ "ZZZ" + // kr
+ "ZZZ" + // kr-ng
+ "ZZZ" + // ks
+ "ZZZ" + // ks-arab
+ "ZZZ" + // ks-arab-in
+ "ZZZ" + // ks-deva
+ "ZZZ" + // ks-deva-in
+ "ZZZ" + // ksb
+ "ZZZ" + // ksb-tz
+ "ZZZ" + // ksf
+ "ZZZ" + // ksf-cm
+ "ZZZ" + // ksh
+ "ZZZ" + // ksh-de
+ "KUR" + // ku
+ "KUR" + // ku-arab
+ "KUR" + // ku-arab-iq
+ "ZZZ" + // ku-arab-ir
+ "ZZZ" + // kw
+ "ZZZ" + // kw-gb
+ "KYR" + // ky
+ "KYR" + // ky-kg
+ "ZZZ" + // la
+ "ZZZ" + // la-001
+ "ZZZ" + // lag
+ "ZZZ" + // lag-tz
+ "LBX" + // lb
+ "LBX" + // lb-lu
+ "ZZZ" + // lg
+ "ZZZ" + // lg-ug
+ "ZZZ" + // lkt
+ "ZZZ" + // lkt-us
+ "ZZZ" + // ln
+ "ZZZ" + // ln-ao
+ "ZZZ" + // ln-cd
+ "ZZZ" + // ln-cf
+ "ZZZ" + // ln-cg
+ "LAO" + // lo
+ "LAO" + // lo-la
+ "ZZZ" + // lrc
+ "ZZZ" + // lrc-iq
+ "ZZZ" + // lrc-ir
+ "LTH" + // lt
+ "LTH" + // lt-lt
+ "ZZZ" + // lu
+ "ZZZ" + // lu-cd
+ "ZZZ" + // luo
+ "ZZZ" + // luo-ke
+ "ZZZ" + // luy
+ "ZZZ" + // luy-ke
+ "LVI" + // lv
+ "LVI" + // lv-lv
+ "ZZZ" + // mas
+ "ZZZ" + // mas-ke
+ "ZZZ" + // mas-tz
+ "ZZZ" + // mer
+ "ZZZ" + // mer-ke
+ "ZZZ" + // mfe
+ "ZZZ" + // mfe-mu
+ "MLG" + // mg
+ "MLG" + // mg-mg
+ "ZZZ" + // mgh
+ "ZZZ" + // mgh-mz
+ "ZZZ" + // mgo
+ "ZZZ" + // mgo-cm
+ "MRI" + // mi
+ "MRI" + // mi-nz
+ "MKI" + // mk
+ "MKI" + // mk-mk
+ "MYM" + // ml
+ "MYM" + // ml-in
+ "MNN" + // mn
+ "MNN" + // mn-cyrl
+ "MNN" + // mn-mn
+ "MNG" + // mn-mong
+ "MNG" + // mn-mong-cn
+ "MNM" + // mn-mong-mn
+ "ZZZ" + // mni
+ "ZZZ" + // mni-in
+ "MWK" + // moh
+ "MWK" + // moh-ca
+ "MAR" + // mr
+ "MAR" + // mr-in
+ "MSL" + // ms
+ "MSB" + // ms-bn
+ "MSL" + // ms-my
+ "ZZZ" + // ms-sg
+ "MLT" + // mt
+ "MLT" + // mt-mt
+ "ZZZ" + // mua
+ "ZZZ" + // mua-cm
+ "MYA" + // my
+ "MYA" + // my-mm
+ "ZZZ" + // mzn
+ "ZZZ" + // mzn-ir
+ "ZZZ" + // naq
+ "ZZZ" + // naq-na
+ "NOR" + // nb
+ "NOR" + // nb-no
+ "ZZZ" + // nb-sj
+ "ZZZ" + // nd
+ "ZZZ" + // nd-zw
+ "ZZZ" + // nds
+ "ZZZ" + // nds-de
+ "ZZZ" + // nds-nl
+ "NEP" + // ne
+ "NEI" + // ne-in
+ "NEP" + // ne-np
+ "NLD" + // nl
+ "ZZZ" + // nl-aw
+ "NLB" + // nl-be
+ "ZZZ" + // nl-bq
+ "ZZZ" + // nl-cw
+ "NLD" + // nl-nl
+ "ZZZ" + // nl-sr
+ "ZZZ" + // nl-sx
+ "ZZZ" + // nmg
+ "ZZZ" + // nmg-cm
+ "NON" + // nn
+ "NON" + // nn-no
+ "ZZZ" + // nnh
+ "ZZZ" + // nnh-cm
+ "NOR" + // no
+ "NQO" + // nqo
+ "NQO" + // nqo-gn
+ "ZZZ" + // nr
+ "ZZZ" + // nr-za
+ "NSO" + // nso
+ "NSO" + // nso-za
+ "ZZZ" + // nus
+ "ZZZ" + // nus-ss
+ "ZZZ" + // nyn
+ "ZZZ" + // nyn-ug
+ "OCI" + // oc
+ "OCI" + // oc-fr
+ "ORM" + // om
+ "ORM" + // om-et
+ "ZZZ" + // om-ke
+ "ORI" + // or
+ "ORI" + // or-in
+ "ZZZ" + // os
+ "ZZZ" + // os-ge
+ "ZZZ" + // os-ru
+ "PAN" + // pa
+ "PAP" + // pa-arab
+ "PAP" + // pa-arab-pk
+ "PAN" + // pa-in
+ "ZZZ" + // pap
+ "ZZZ" + // pap-029
+ "PLK" + // pl
+ "PLK" + // pl-pl
+ "ZZZ" + // prg
+ "ZZZ" + // prg-001
+ "PRS" + // prs
+ "PRS" + // prs-af
+ "PAS" + // ps
+ "PAS" + // ps-af
+ "PTB" + // pt
+ "PTA" + // pt-ao
+ "PTB" + // pt-br
+ "ZZZ" + // pt-ch
+ "ZZZ" + // pt-cv
+ "ZZZ" + // pt-gq
+ "ZZZ" + // pt-gw
+ "ZZZ" + // pt-lu
+ "ZZZ" + // pt-mo
+ "ZZZ" + // pt-mz
+ "PTG" + // pt-pt
+ "ZZZ" + // pt-st
+ "ZZZ" + // pt-tl
+ "ENJ" + // qps-latn-x-sh
+ "ENU" + // qps-ploc
+ "JPN" + // qps-ploca
+ "ARA" + // qps-plocm
+ "QUT" + // quc
+ "QUT" + // quc-latn
+ "QUT" + // quc-latn-gt
+ "QUB" + // quz
+ "QUB" + // quz-bo
+ "QUE" + // quz-ec
+ "QUP" + // quz-pe
+ "RMC" + // rm
+ "RMC" + // rm-ch
+ "ZZZ" + // rn
+ "ZZZ" + // rn-bi
+ "ROM" + // ro
+ "ROD" + // ro-md
+ "ROM" + // ro-ro
+ "ZZZ" + // rof
+ "ZZZ" + // rof-tz
+ "RUS" + // ru
+ "ZZZ" + // ru-by
+ "ZZZ" + // ru-kg
+ "ZZZ" + // ru-kz
+ "RUM" + // ru-md
+ "RUS" + // ru-ru
+ "ZZZ" + // ru-ua
+ "KIN" + // rw
+ "KIN" + // rw-rw
+ "ZZZ" + // rwk
+ "ZZZ" + // rwk-tz
+ "SAN" + // sa
+ "SAN" + // sa-in
+ "SAH" + // sah
+ "SAH" + // sah-ru
+ "ZZZ" + // saq
+ "ZZZ" + // saq-ke
+ "ZZZ" + // sbp
+ "ZZZ" + // sbp-tz
+ "SIP" + // sd
+ "SIP" + // sd-arab
+ "SIP" + // sd-arab-pk
+ "ZZZ" + // sd-deva
+ "ZZZ" + // sd-deva-in
+ "SME" + // se
+ "SMG" + // se-fi
+ "SME" + // se-no
+ "SMF" + // se-se
+ "ZZZ" + // seh
+ "ZZZ" + // seh-mz
+ "ZZZ" + // ses
+ "ZZZ" + // ses-ml
+ "ZZZ" + // sg
+ "ZZZ" + // sg-cf
+ "ZZZ" + // shi
+ "ZZZ" + // shi-latn
+ "ZZZ" + // shi-latn-ma
+ "ZZZ" + // shi-tfng
+ "ZZZ" + // shi-tfng-ma
+ "SIN" + // si
+ "SIN" + // si-lk
+ "SKY" + // sk
+ "SKY" + // sk-sk
+ "SLV" + // sl
+ "SLV" + // sl-si
+ "SMB" + // sma
+ "SMA" + // sma-no
+ "SMB" + // sma-se
+ "SMK" + // smj
+ "SMJ" + // smj-no
+ "SMK" + // smj-se
+ "SMN" + // smn
+ "SMN" + // smn-fi
+ "SMS" + // sms
+ "SMS" + // sms-fi
+ "SNA" + // sn
+ "SNA" + // sn-latn
+ "SNA" + // sn-latn-zw
+ "SOM" + // so
+ "ZZZ" + // so-dj
+ "ZZZ" + // so-et
+ "ZZZ" + // so-ke
+ "SOM" + // so-so
+ "SQI" + // sq
+ "SQI" + // sq-al
+ "ZZZ" + // sq-mk
+ "ZZZ" + // sq-xk
+ "SRM" + // sr
+ "SRO" + // sr-cyrl
+ "SRN" + // sr-cyrl-ba
+ "SRB" + // sr-cyrl-cs
+ "SRQ" + // sr-cyrl-me
+ "SRO" + // sr-cyrl-rs
+ "ZZZ" + // sr-cyrl-xk
+ "SRM" + // sr-latn
+ "SRS" + // sr-latn-ba
+ "SRL" + // sr-latn-cs
+ "SRP" + // sr-latn-me
+ "SRM" + // sr-latn-rs
+ "ZZZ" + // sr-latn-xk
+ "ZZZ" + // ss
+ "ZZZ" + // ss-sz
+ "ZZZ" + // ss-za
+ "ZZZ" + // ssy
+ "ZZZ" + // ssy-er
+ "SOT" + // st
+ "ZZZ" + // st-ls
+ "SOT" + // st-za
+ "SVE" + // sv
+ "ZZZ" + // sv-ax
+ "SVF" + // sv-fi
+ "SVE" + // sv-se
+ "SWK" + // sw
+ "ZZZ" + // sw-cd
+ "SWK" + // sw-ke
+ "ZZZ" + // sw-tz
+ "ZZZ" + // sw-ug
+ "ZZZ" + // swc
+ "ZZZ" + // swc-cd
+ "SYR" + // syr
+ "SYR" + // syr-sy
+ "TAI" + // ta
+ "TAI" + // ta-in
+ "TAM" + // ta-lk
+ "ZZZ" + // ta-my
+ "ZZZ" + // ta-sg
+ "TEL" + // te
+ "TEL" + // te-in
+ "ZZZ" + // teo
+ "ZZZ" + // teo-ke
+ "ZZZ" + // teo-ug
+ "TAJ" + // tg
+ "TAJ" + // tg-cyrl
+ "TAJ" + // tg-cyrl-tj
+ "THA" + // th
+ "THA" + // th-th
+ "TIR" + // ti
+ "TIR" + // ti-er
+ "TIE" + // ti-et
+ "ZZZ" + // tig
+ "ZZZ" + // tig-er
+ "TUK" + // tk
+ "TUK" + // tk-tm
+ "TSN" + // tn
+ "TSB" + // tn-bw
+ "TSN" + // tn-za
+ "ZZZ" + // to
+ "ZZZ" + // to-to
+ "TRK" + // tr
+ "ZZZ" + // tr-cy
+ "TRK" + // tr-tr
+ "TSO" + // ts
+ "TSO" + // ts-za
+ "TTT" + // tt
+ "TTT" + // tt-ru
+ "ZZZ" + // twq
+ "ZZZ" + // twq-ne
+ "TZA" + // tzm
+ "ZZZ" + // tzm-arab
+ "ZZZ" + // tzm-arab-ma
+ "TZA" + // tzm-latn
+ "TZA" + // tzm-latn-dz
+ "ZZZ" + // tzm-latn-ma
+ "TZM" + // tzm-tfng
+ "TZM" + // tzm-tfng-ma
+ "UIG" + // ug
+ "UIG" + // ug-cn
+ "UKR" + // uk
+ "UKR" + // uk-ua
+ "URD" + // ur
+ "URI" + // ur-in
+ "URD" + // ur-pk
+ "UZB" + // uz
+ "ZZZ" + // uz-arab
+ "ZZZ" + // uz-arab-af
+ "UZC" + // uz-cyrl
+ "UZC" + // uz-cyrl-uz
+ "UZB" + // uz-latn
+ "UZB" + // uz-latn-uz
+ "ZZZ" + // vai
+ "ZZZ" + // vai-latn
+ "ZZZ" + // vai-latn-lr
+ "ZZZ" + // vai-vaii
+ "ZZZ" + // vai-vaii-lr
+ "ZZZ" + // ve
+ "ZZZ" + // ve-za
+ "VIT" + // vi
+ "VIT" + // vi-vn
+ "ZZZ" + // vo
+ "ZZZ" + // vo-001
+ "ZZZ" + // vun
+ "ZZZ" + // vun-tz
+ "ZZZ" + // wae
+ "ZZZ" + // wae-ch
+ "ZZZ" + // wal
+ "ZZZ" + // wal-et
+ "WOL" + // wo
+ "WOL" + // wo-sn
+ "IVL" + // x-iv_mathan
+ "XHO" + // xh
+ "XHO" + // xh-za
+ "ZZZ" + // xog
+ "ZZZ" + // xog-ug
+ "ZZZ" + // yav
+ "ZZZ" + // yav-cm
+ "ZZZ" + // yi
+ "ZZZ" + // yi-001
+ "YOR" + // yo
+ "ZZZ" + // yo-bj
+ "YOR" + // yo-ng
+ "ZZZ" + // yue
+ "ZZZ" + // yue-hk
+ "ZHG" + // zgh
+ "ZHG" + // zgh-tfng
+ "ZHG" + // zgh-tfng-ma
+ "CHS" + // zh
+ "CHS" + // zh-chs
+ "CHT" + // zh-cht
+ "CHS" + // zh-cn
+ "CHS" + // zh-cn_phoneb
+ "CHS" + // zh-cn_stroke
+ "CHS" + // zh-hans
+ "ZZZ" + // zh-hans-hk
+ "ZZZ" + // zh-hans-mo
+ "ZHH" + // zh-hant
+ "ZHH" + // zh-hk
+ "ZHH" + // zh-hk_radstr
+ "ZHM" + // zh-mo
+ "ZHM" + // zh-mo_radstr
+ "ZHM" + // zh-mo_stroke
+ "ZHI" + // zh-sg
+ "ZHI" + // zh-sg_phoneb
+ "ZHI" + // zh-sg_stroke
+ "CHT" + // zh-tw
+ "CHT" + // zh-tw_pronun
+ "CHT" + // zh-tw_radstr
+ "ZUL" + // zu
+ "ZUL"; // zu-za
+
+ // s_localeNamesIndices contains the start index of every culture name in the string
+ // s_localeNames. We infer the length of each string by looking at the start index
+ // of the next string.
+ private static readonly int[] s_localeNamesIndices = new int[]
+ {
+ // c_localeNames index, // index to this array - culture name
+ 0 , // 0 - aa
+ 2 , // 1 - aa-dj
+ 7 , // 2 - aa-er
+ 12 , // 3 - aa-et
+ 17 , // 4 - af
+ 19 , // 5 - af-na
+ 24 , // 6 - af-za
+ 29 , // 7 - agq
+ 32 , // 8 - agq-cm
+ 38 , // 9 - ak
+ 40 , // 10 - ak-gh
+ 45 , // 11 - am
+ 47 , // 12 - am-et
+ 52 , // 13 - ar
+ 54 , // 14 - ar-001
+ 60 , // 15 - ar-ae
+ 65 , // 16 - ar-bh
+ 70 , // 17 - ar-dj
+ 75 , // 18 - ar-dz
+ 80 , // 19 - ar-eg
+ 85 , // 20 - ar-er
+ 90 , // 21 - ar-il
+ 95 , // 22 - ar-iq
+ 100 , // 23 - ar-jo
+ 105 , // 24 - ar-km
+ 110 , // 25 - ar-kw
+ 115 , // 26 - ar-lb
+ 120 , // 27 - ar-ly
+ 125 , // 28 - ar-ma
+ 130 , // 29 - ar-mr
+ 135 , // 30 - ar-om
+ 140 , // 31 - ar-ps
+ 145 , // 32 - ar-qa
+ 150 , // 33 - ar-sa
+ 155 , // 34 - ar-sd
+ 160 , // 35 - ar-so
+ 165 , // 36 - ar-ss
+ 170 , // 37 - ar-sy
+ 175 , // 38 - ar-td
+ 180 , // 39 - ar-tn
+ 185 , // 40 - ar-ye
+ 190 , // 41 - arn
+ 193 , // 42 - arn-cl
+ 199 , // 43 - as
+ 201 , // 44 - as-in
+ 206 , // 45 - asa
+ 209 , // 46 - asa-tz
+ 215 , // 47 - ast
+ 218 , // 48 - ast-es
+ 224 , // 49 - az
+ 226 , // 50 - az-cyrl
+ 233 , // 51 - az-cyrl-az
+ 243 , // 52 - az-latn
+ 250 , // 53 - az-latn-az
+ 260 , // 54 - ba
+ 262 , // 55 - ba-ru
+ 267 , // 56 - bas
+ 270 , // 57 - bas-cm
+ 276 , // 58 - be
+ 278 , // 59 - be-by
+ 283 , // 60 - bem
+ 286 , // 61 - bem-zm
+ 292 , // 62 - bez
+ 295 , // 63 - bez-tz
+ 301 , // 64 - bg
+ 303 , // 65 - bg-bg
+ 308 , // 66 - bin
+ 311 , // 67 - bin-ng
+ 317 , // 68 - bm
+ 319 , // 69 - bm-latn
+ 326 , // 70 - bm-latn-ml
+ 336 , // 71 - bn
+ 338 , // 72 - bn-bd
+ 343 , // 73 - bn-in
+ 348 , // 74 - bo
+ 350 , // 75 - bo-cn
+ 355 , // 76 - bo-in
+ 360 , // 77 - br
+ 362 , // 78 - br-fr
+ 367 , // 79 - brx
+ 370 , // 80 - brx-in
+ 376 , // 81 - bs
+ 378 , // 82 - bs-cyrl
+ 385 , // 83 - bs-cyrl-ba
+ 395 , // 84 - bs-latn
+ 402 , // 85 - bs-latn-ba
+ 412 , // 86 - byn
+ 415 , // 87 - byn-er
+ 421 , // 88 - ca
+ 423 , // 89 - ca-ad
+ 428 , // 90 - ca-es
+ 433 , // 91 - ca-es-valencia
+ 447 , // 92 - ca-fr
+ 452 , // 93 - ca-it
+ 457 , // 94 - ce
+ 459 , // 95 - ce-ru
+ 464 , // 96 - cgg
+ 467 , // 97 - cgg-ug
+ 473 , // 98 - chr
+ 476 , // 99 - chr-cher
+ 484 , // 100 - chr-cher-us
+ 495 , // 101 - co
+ 497 , // 102 - co-fr
+ 502 , // 103 - cs
+ 504 , // 104 - cs-cz
+ 509 , // 105 - cu
+ 511 , // 106 - cu-ru
+ 516 , // 107 - cy
+ 518 , // 108 - cy-gb
+ 523 , // 109 - da
+ 525 , // 110 - da-dk
+ 530 , // 111 - da-gl
+ 535 , // 112 - dav
+ 538 , // 113 - dav-ke
+ 544 , // 114 - de
+ 546 , // 115 - de-at
+ 551 , // 116 - de-be
+ 556 , // 117 - de-ch
+ 561 , // 118 - de-de
+ 566 , // 119 - de-de_phoneb
+ 578 , // 120 - de-it
+ 583 , // 121 - de-li
+ 588 , // 122 - de-lu
+ 593 , // 123 - dje
+ 596 , // 124 - dje-ne
+ 602 , // 125 - dsb
+ 605 , // 126 - dsb-de
+ 611 , // 127 - dua
+ 614 , // 128 - dua-cm
+ 620 , // 129 - dv
+ 622 , // 130 - dv-mv
+ 627 , // 131 - dyo
+ 630 , // 132 - dyo-sn
+ 636 , // 133 - dz
+ 638 , // 134 - dz-bt
+ 643 , // 135 - ebu
+ 646 , // 136 - ebu-ke
+ 652 , // 137 - ee
+ 654 , // 138 - ee-gh
+ 659 , // 139 - ee-tg
+ 664 , // 140 - el
+ 666 , // 141 - el-cy
+ 671 , // 142 - el-gr
+ 676 , // 143 - en
+ 678 , // 144 - en-001
+ 684 , // 145 - en-029
+ 690 , // 146 - en-150
+ 696 , // 147 - en-ag
+ 701 , // 148 - en-ai
+ 706 , // 149 - en-as
+ 711 , // 150 - en-at
+ 716 , // 151 - en-au
+ 721 , // 152 - en-bb
+ 726 , // 153 - en-be
+ 731 , // 154 - en-bi
+ 736 , // 155 - en-bm
+ 741 , // 156 - en-bs
+ 746 , // 157 - en-bw
+ 751 , // 158 - en-bz
+ 756 , // 159 - en-ca
+ 761 , // 160 - en-cc
+ 766 , // 161 - en-ch
+ 771 , // 162 - en-ck
+ 776 , // 163 - en-cm
+ 781 , // 164 - en-cx
+ 786 , // 165 - en-cy
+ 791 , // 166 - en-de
+ 796 , // 167 - en-dk
+ 801 , // 168 - en-dm
+ 806 , // 169 - en-er
+ 811 , // 170 - en-fi
+ 816 , // 171 - en-fj
+ 821 , // 172 - en-fk
+ 826 , // 173 - en-fm
+ 831 , // 174 - en-gb
+ 836 , // 175 - en-gd
+ 841 , // 176 - en-gg
+ 846 , // 177 - en-gh
+ 851 , // 178 - en-gi
+ 856 , // 179 - en-gm
+ 861 , // 180 - en-gu
+ 866 , // 181 - en-gy
+ 871 , // 182 - en-hk
+ 876 , // 183 - en-id
+ 881 , // 184 - en-ie
+ 886 , // 185 - en-il
+ 891 , // 186 - en-im
+ 896 , // 187 - en-in
+ 901 , // 188 - en-io
+ 906 , // 189 - en-je
+ 911 , // 190 - en-jm
+ 916 , // 191 - en-ke
+ 921 , // 192 - en-ki
+ 926 , // 193 - en-kn
+ 931 , // 194 - en-ky
+ 936 , // 195 - en-lc
+ 941 , // 196 - en-lr
+ 946 , // 197 - en-ls
+ 951 , // 198 - en-mg
+ 956 , // 199 - en-mh
+ 961 , // 200 - en-mo
+ 966 , // 201 - en-mp
+ 971 , // 202 - en-ms
+ 976 , // 203 - en-mt
+ 981 , // 204 - en-mu
+ 986 , // 205 - en-mw
+ 991 , // 206 - en-my
+ 996 , // 207 - en-na
+ 1001, // 208 - en-nf
+ 1006, // 209 - en-ng
+ 1011, // 210 - en-nl
+ 1016, // 211 - en-nr
+ 1021, // 212 - en-nu
+ 1026, // 213 - en-nz
+ 1031, // 214 - en-pg
+ 1036, // 215 - en-ph
+ 1041, // 216 - en-pk
+ 1046, // 217 - en-pn
+ 1051, // 218 - en-pr
+ 1056, // 219 - en-pw
+ 1061, // 220 - en-rw
+ 1066, // 221 - en-sb
+ 1071, // 222 - en-sc
+ 1076, // 223 - en-sd
+ 1081, // 224 - en-se
+ 1086, // 225 - en-sg
+ 1091, // 226 - en-sh
+ 1096, // 227 - en-si
+ 1101, // 228 - en-sl
+ 1106, // 229 - en-ss
+ 1111, // 230 - en-sx
+ 1116, // 231 - en-sz
+ 1121, // 232 - en-tc
+ 1126, // 233 - en-tk
+ 1131, // 234 - en-to
+ 1136, // 235 - en-tt
+ 1141, // 236 - en-tv
+ 1146, // 237 - en-tz
+ 1151, // 238 - en-ug
+ 1156, // 239 - en-um
+ 1161, // 240 - en-us
+ 1166, // 241 - en-vc
+ 1171, // 242 - en-vg
+ 1176, // 243 - en-vi
+ 1181, // 244 - en-vu
+ 1186, // 245 - en-ws
+ 1191, // 246 - en-za
+ 1196, // 247 - en-zm
+ 1201, // 248 - en-zw
+ 1206, // 249 - eo
+ 1208, // 250 - eo-001
+ 1214, // 251 - es
+ 1216, // 252 - es-419
+ 1222, // 253 - es-ar
+ 1227, // 254 - es-bo
+ 1232, // 255 - es-br
+ 1237, // 256 - es-cl
+ 1242, // 257 - es-co
+ 1247, // 258 - es-cr
+ 1252, // 259 - es-cu
+ 1257, // 260 - es-do
+ 1262, // 261 - es-ec
+ 1267, // 262 - es-es
+ 1272, // 263 - es-es_tradnl
+ 1284, // 264 - es-gq
+ 1289, // 265 - es-gt
+ 1294, // 266 - es-hn
+ 1299, // 267 - es-mx
+ 1304, // 268 - es-ni
+ 1309, // 269 - es-pa
+ 1314, // 270 - es-pe
+ 1319, // 271 - es-ph
+ 1324, // 272 - es-pr
+ 1329, // 273 - es-py
+ 1334, // 274 - es-sv
+ 1339, // 275 - es-us
+ 1344, // 276 - es-uy
+ 1349, // 277 - es-ve
+ 1354, // 278 - et
+ 1356, // 279 - et-ee
+ 1361, // 280 - eu
+ 1363, // 281 - eu-es
+ 1368, // 282 - ewo
+ 1371, // 283 - ewo-cm
+ 1377, // 284 - fa
+ 1379, // 285 - fa-ir
+ 1384, // 286 - ff
+ 1386, // 287 - ff-cm
+ 1391, // 288 - ff-gn
+ 1396, // 289 - ff-latn
+ 1403, // 290 - ff-latn-sn
+ 1413, // 291 - ff-mr
+ 1418, // 292 - ff-ng
+ 1423, // 293 - fi
+ 1425, // 294 - fi-fi
+ 1430, // 295 - fil
+ 1433, // 296 - fil-ph
+ 1439, // 297 - fo
+ 1441, // 298 - fo-dk
+ 1446, // 299 - fo-fo
+ 1451, // 300 - fr
+ 1453, // 301 - fr-029
+ 1459, // 302 - fr-be
+ 1464, // 303 - fr-bf
+ 1469, // 304 - fr-bi
+ 1474, // 305 - fr-bj
+ 1479, // 306 - fr-bl
+ 1484, // 307 - fr-ca
+ 1489, // 308 - fr-cd
+ 1494, // 309 - fr-cf
+ 1499, // 310 - fr-cg
+ 1504, // 311 - fr-ch
+ 1509, // 312 - fr-ci
+ 1514, // 313 - fr-cm
+ 1519, // 314 - fr-dj
+ 1524, // 315 - fr-dz
+ 1529, // 316 - fr-fr
+ 1534, // 317 - fr-ga
+ 1539, // 318 - fr-gf
+ 1544, // 319 - fr-gn
+ 1549, // 320 - fr-gp
+ 1554, // 321 - fr-gq
+ 1559, // 322 - fr-ht
+ 1564, // 323 - fr-km
+ 1569, // 324 - fr-lu
+ 1574, // 325 - fr-ma
+ 1579, // 326 - fr-mc
+ 1584, // 327 - fr-mf
+ 1589, // 328 - fr-mg
+ 1594, // 329 - fr-ml
+ 1599, // 330 - fr-mq
+ 1604, // 331 - fr-mr
+ 1609, // 332 - fr-mu
+ 1614, // 333 - fr-nc
+ 1619, // 334 - fr-ne
+ 1624, // 335 - fr-pf
+ 1629, // 336 - fr-pm
+ 1634, // 337 - fr-re
+ 1639, // 338 - fr-rw
+ 1644, // 339 - fr-sc
+ 1649, // 340 - fr-sn
+ 1654, // 341 - fr-sy
+ 1659, // 342 - fr-td
+ 1664, // 343 - fr-tg
+ 1669, // 344 - fr-tn
+ 1674, // 345 - fr-vu
+ 1679, // 346 - fr-wf
+ 1684, // 347 - fr-yt
+ 1689, // 348 - fur
+ 1692, // 349 - fur-it
+ 1698, // 350 - fy
+ 1700, // 351 - fy-nl
+ 1705, // 352 - ga
+ 1707, // 353 - ga-ie
+ 1712, // 354 - gd
+ 1714, // 355 - gd-gb
+ 1719, // 356 - gl
+ 1721, // 357 - gl-es
+ 1726, // 358 - gn
+ 1728, // 359 - gn-py
+ 1733, // 360 - gsw
+ 1736, // 361 - gsw-ch
+ 1742, // 362 - gsw-fr
+ 1748, // 363 - gsw-li
+ 1754, // 364 - gu
+ 1756, // 365 - gu-in
+ 1761, // 366 - guz
+ 1764, // 367 - guz-ke
+ 1770, // 368 - gv
+ 1772, // 369 - gv-im
+ 1777, // 370 - ha
+ 1779, // 371 - ha-latn
+ 1786, // 372 - ha-latn-gh
+ 1796, // 373 - ha-latn-ne
+ 1806, // 374 - ha-latn-ng
+ 1816, // 375 - haw
+ 1819, // 376 - haw-us
+ 1825, // 377 - he
+ 1827, // 378 - he-il
+ 1832, // 379 - hi
+ 1834, // 380 - hi-in
+ 1839, // 381 - hr
+ 1841, // 382 - hr-ba
+ 1846, // 383 - hr-hr
+ 1851, // 384 - hsb
+ 1854, // 385 - hsb-de
+ 1860, // 386 - hu
+ 1862, // 387 - hu-hu
+ 1867, // 388 - hu-hu_technl
+ 1879, // 389 - hy
+ 1881, // 390 - hy-am
+ 1886, // 391 - ia
+ 1888, // 392 - ia-001
+ 1894, // 393 - ia-fr
+ 1899, // 394 - ibb
+ 1902, // 395 - ibb-ng
+ 1908, // 396 - id
+ 1910, // 397 - id-id
+ 1915, // 398 - ig
+ 1917, // 399 - ig-ng
+ 1922, // 400 - ii
+ 1924, // 401 - ii-cn
+ 1929, // 402 - is
+ 1931, // 403 - is-is
+ 1936, // 404 - it
+ 1938, // 405 - it-ch
+ 1943, // 406 - it-it
+ 1948, // 407 - it-sm
+ 1953, // 408 - iu
+ 1955, // 409 - iu-cans
+ 1962, // 410 - iu-cans-ca
+ 1972, // 411 - iu-latn
+ 1979, // 412 - iu-latn-ca
+ 1989, // 413 - ja
+ 1991, // 414 - ja-jp
+ 1996, // 415 - ja-jp_radstr
+ 2008, // 416 - jgo
+ 2011, // 417 - jgo-cm
+ 2017, // 418 - jmc
+ 2020, // 419 - jmc-tz
+ 2026, // 420 - jv
+ 2028, // 421 - jv-java
+ 2035, // 422 - jv-java-id
+ 2045, // 423 - jv-latn
+ 2052, // 424 - jv-latn-id
+ 2062, // 425 - ka
+ 2064, // 426 - ka-ge
+ 2069, // 427 - ka-ge_modern
+ 2081, // 428 - kab
+ 2084, // 429 - kab-dz
+ 2090, // 430 - kam
+ 2093, // 431 - kam-ke
+ 2099, // 432 - kde
+ 2102, // 433 - kde-tz
+ 2108, // 434 - kea
+ 2111, // 435 - kea-cv
+ 2117, // 436 - khq
+ 2120, // 437 - khq-ml
+ 2126, // 438 - ki
+ 2128, // 439 - ki-ke
+ 2133, // 440 - kk
+ 2135, // 441 - kk-kz
+ 2140, // 442 - kkj
+ 2143, // 443 - kkj-cm
+ 2149, // 444 - kl
+ 2151, // 445 - kl-gl
+ 2156, // 446 - kln
+ 2159, // 447 - kln-ke
+ 2165, // 448 - km
+ 2167, // 449 - km-kh
+ 2172, // 450 - kn
+ 2174, // 451 - kn-in
+ 2179, // 452 - ko
+ 2181, // 453 - ko-kp
+ 2186, // 454 - ko-kr
+ 2191, // 455 - kok
+ 2194, // 456 - kok-in
+ 2200, // 457 - kr
+ 2202, // 458 - kr-ng
+ 2207, // 459 - ks
+ 2209, // 460 - ks-arab
+ 2216, // 461 - ks-arab-in
+ 2226, // 462 - ks-deva
+ 2233, // 463 - ks-deva-in
+ 2243, // 464 - ksb
+ 2246, // 465 - ksb-tz
+ 2252, // 466 - ksf
+ 2255, // 467 - ksf-cm
+ 2261, // 468 - ksh
+ 2264, // 469 - ksh-de
+ 2270, // 470 - ku
+ 2272, // 471 - ku-arab
+ 2279, // 472 - ku-arab-iq
+ 2289, // 473 - ku-arab-ir
+ 2299, // 474 - kw
+ 2301, // 475 - kw-gb
+ 2306, // 476 - ky
+ 2308, // 477 - ky-kg
+ 2313, // 478 - la
+ 2315, // 479 - la-001
+ 2321, // 480 - lag
+ 2324, // 481 - lag-tz
+ 2330, // 482 - lb
+ 2332, // 483 - lb-lu
+ 2337, // 484 - lg
+ 2339, // 485 - lg-ug
+ 2344, // 486 - lkt
+ 2347, // 487 - lkt-us
+ 2353, // 488 - ln
+ 2355, // 489 - ln-ao
+ 2360, // 490 - ln-cd
+ 2365, // 491 - ln-cf
+ 2370, // 492 - ln-cg
+ 2375, // 493 - lo
+ 2377, // 494 - lo-la
+ 2382, // 495 - lrc
+ 2385, // 496 - lrc-iq
+ 2391, // 497 - lrc-ir
+ 2397, // 498 - lt
+ 2399, // 499 - lt-lt
+ 2404, // 500 - lu
+ 2406, // 501 - lu-cd
+ 2411, // 502 - luo
+ 2414, // 503 - luo-ke
+ 2420, // 504 - luy
+ 2423, // 505 - luy-ke
+ 2429, // 506 - lv
+ 2431, // 507 - lv-lv
+ 2436, // 508 - mas
+ 2439, // 509 - mas-ke
+ 2445, // 510 - mas-tz
+ 2451, // 511 - mer
+ 2454, // 512 - mer-ke
+ 2460, // 513 - mfe
+ 2463, // 514 - mfe-mu
+ 2469, // 515 - mg
+ 2471, // 516 - mg-mg
+ 2476, // 517 - mgh
+ 2479, // 518 - mgh-mz
+ 2485, // 519 - mgo
+ 2488, // 520 - mgo-cm
+ 2494, // 521 - mi
+ 2496, // 522 - mi-nz
+ 2501, // 523 - mk
+ 2503, // 524 - mk-mk
+ 2508, // 525 - ml
+ 2510, // 526 - ml-in
+ 2515, // 527 - mn
+ 2517, // 528 - mn-cyrl
+ 2524, // 529 - mn-mn
+ 2529, // 530 - mn-mong
+ 2536, // 531 - mn-mong-cn
+ 2546, // 532 - mn-mong-mn
+ 2556, // 533 - mni
+ 2559, // 534 - mni-in
+ 2565, // 535 - moh
+ 2568, // 536 - moh-ca
+ 2574, // 537 - mr
+ 2576, // 538 - mr-in
+ 2581, // 539 - ms
+ 2583, // 540 - ms-bn
+ 2588, // 541 - ms-my
+ 2593, // 542 - ms-sg
+ 2598, // 543 - mt
+ 2600, // 544 - mt-mt
+ 2605, // 545 - mua
+ 2608, // 546 - mua-cm
+ 2614, // 547 - my
+ 2616, // 548 - my-mm
+ 2621, // 549 - mzn
+ 2624, // 550 - mzn-ir
+ 2630, // 551 - naq
+ 2633, // 552 - naq-na
+ 2639, // 553 - nb
+ 2641, // 554 - nb-no
+ 2646, // 555 - nb-sj
+ 2651, // 556 - nd
+ 2653, // 557 - nd-zw
+ 2658, // 558 - nds
+ 2661, // 559 - nds-de
+ 2667, // 560 - nds-nl
+ 2673, // 561 - ne
+ 2675, // 562 - ne-in
+ 2680, // 563 - ne-np
+ 2685, // 564 - nl
+ 2687, // 565 - nl-aw
+ 2692, // 566 - nl-be
+ 2697, // 567 - nl-bq
+ 2702, // 568 - nl-cw
+ 2707, // 569 - nl-nl
+ 2712, // 570 - nl-sr
+ 2717, // 571 - nl-sx
+ 2722, // 572 - nmg
+ 2725, // 573 - nmg-cm
+ 2731, // 574 - nn
+ 2733, // 575 - nn-no
+ 2738, // 576 - nnh
+ 2741, // 577 - nnh-cm
+ 2747, // 578 - no
+ 2749, // 579 - nqo
+ 2752, // 580 - nqo-gn
+ 2758, // 581 - nr
+ 2760, // 582 - nr-za
+ 2765, // 583 - nso
+ 2768, // 584 - nso-za
+ 2774, // 585 - nus
+ 2777, // 586 - nus-ss
+ 2783, // 587 - nyn
+ 2786, // 588 - nyn-ug
+ 2792, // 589 - oc
+ 2794, // 590 - oc-fr
+ 2799, // 591 - om
+ 2801, // 592 - om-et
+ 2806, // 593 - om-ke
+ 2811, // 594 - or
+ 2813, // 595 - or-in
+ 2818, // 596 - os
+ 2820, // 597 - os-ge
+ 2825, // 598 - os-ru
+ 2830, // 599 - pa
+ 2832, // 600 - pa-arab
+ 2839, // 601 - pa-arab-pk
+ 2849, // 602 - pa-in
+ 2854, // 603 - pap
+ 2857, // 604 - pap-029
+ 2864, // 605 - pl
+ 2866, // 606 - pl-pl
+ 2871, // 607 - prg
+ 2874, // 608 - prg-001
+ 2881, // 609 - prs
+ 2884, // 610 - prs-af
+ 2890, // 611 - ps
+ 2892, // 612 - ps-af
+ 2897, // 613 - pt
+ 2899, // 614 - pt-ao
+ 2904, // 615 - pt-br
+ 2909, // 616 - pt-ch
+ 2914, // 617 - pt-cv
+ 2919, // 618 - pt-gq
+ 2924, // 619 - pt-gw
+ 2929, // 620 - pt-lu
+ 2934, // 621 - pt-mo
+ 2939, // 622 - pt-mz
+ 2944, // 623 - pt-pt
+ 2949, // 624 - pt-st
+ 2954, // 625 - pt-tl
+ 2959, // 626 - qps-latn-x-sh
+ 2972, // 627 - qps-ploc
+ 2980, // 628 - qps-ploca
+ 2989, // 629 - qps-plocm
+ 2998, // 630 - quc
+ 3001, // 631 - quc-latn
+ 3009, // 632 - quc-latn-gt
+ 3020, // 633 - quz
+ 3023, // 634 - quz-bo
+ 3029, // 635 - quz-ec
+ 3035, // 636 - quz-pe
+ 3041, // 637 - rm
+ 3043, // 638 - rm-ch
+ 3048, // 639 - rn
+ 3050, // 640 - rn-bi
+ 3055, // 641 - ro
+ 3057, // 642 - ro-md
+ 3062, // 643 - ro-ro
+ 3067, // 644 - rof
+ 3070, // 645 - rof-tz
+ 3076, // 646 - ru
+ 3078, // 647 - ru-by
+ 3083, // 648 - ru-kg
+ 3088, // 649 - ru-kz
+ 3093, // 650 - ru-md
+ 3098, // 651 - ru-ru
+ 3103, // 652 - ru-ua
+ 3108, // 653 - rw
+ 3110, // 654 - rw-rw
+ 3115, // 655 - rwk
+ 3118, // 656 - rwk-tz
+ 3124, // 657 - sa
+ 3126, // 658 - sa-in
+ 3131, // 659 - sah
+ 3134, // 660 - sah-ru
+ 3140, // 661 - saq
+ 3143, // 662 - saq-ke
+ 3149, // 663 - sbp
+ 3152, // 664 - sbp-tz
+ 3158, // 665 - sd
+ 3160, // 666 - sd-arab
+ 3167, // 667 - sd-arab-pk
+ 3177, // 668 - sd-deva
+ 3184, // 669 - sd-deva-in
+ 3194, // 670 - se
+ 3196, // 671 - se-fi
+ 3201, // 672 - se-no
+ 3206, // 673 - se-se
+ 3211, // 674 - seh
+ 3214, // 675 - seh-mz
+ 3220, // 676 - ses
+ 3223, // 677 - ses-ml
+ 3229, // 678 - sg
+ 3231, // 679 - sg-cf
+ 3236, // 680 - shi
+ 3239, // 681 - shi-latn
+ 3247, // 682 - shi-latn-ma
+ 3258, // 683 - shi-tfng
+ 3266, // 684 - shi-tfng-ma
+ 3277, // 685 - si
+ 3279, // 686 - si-lk
+ 3284, // 687 - sk
+ 3286, // 688 - sk-sk
+ 3291, // 689 - sl
+ 3293, // 690 - sl-si
+ 3298, // 691 - sma
+ 3301, // 692 - sma-no
+ 3307, // 693 - sma-se
+ 3313, // 694 - smj
+ 3316, // 695 - smj-no
+ 3322, // 696 - smj-se
+ 3328, // 697 - smn
+ 3331, // 698 - smn-fi
+ 3337, // 699 - sms
+ 3340, // 700 - sms-fi
+ 3346, // 701 - sn
+ 3348, // 702 - sn-latn
+ 3355, // 703 - sn-latn-zw
+ 3365, // 704 - so
+ 3367, // 705 - so-dj
+ 3372, // 706 - so-et
+ 3377, // 707 - so-ke
+ 3382, // 708 - so-so
+ 3387, // 709 - sq
+ 3389, // 710 - sq-al
+ 3394, // 711 - sq-mk
+ 3399, // 712 - sq-xk
+ 3404, // 713 - sr
+ 3406, // 714 - sr-cyrl
+ 3413, // 715 - sr-cyrl-ba
+ 3423, // 716 - sr-cyrl-cs
+ 3433, // 717 - sr-cyrl-me
+ 3443, // 718 - sr-cyrl-rs
+ 3453, // 719 - sr-cyrl-xk
+ 3463, // 720 - sr-latn
+ 3470, // 721 - sr-latn-ba
+ 3480, // 722 - sr-latn-cs
+ 3490, // 723 - sr-latn-me
+ 3500, // 724 - sr-latn-rs
+ 3510, // 725 - sr-latn-xk
+ 3520, // 726 - ss
+ 3522, // 727 - ss-sz
+ 3527, // 728 - ss-za
+ 3532, // 729 - ssy
+ 3535, // 730 - ssy-er
+ 3541, // 731 - st
+ 3543, // 732 - st-ls
+ 3548, // 733 - st-za
+ 3553, // 734 - sv
+ 3555, // 735 - sv-ax
+ 3560, // 736 - sv-fi
+ 3565, // 737 - sv-se
+ 3570, // 738 - sw
+ 3572, // 739 - sw-cd
+ 3577, // 740 - sw-ke
+ 3582, // 741 - sw-tz
+ 3587, // 742 - sw-ug
+ 3592, // 743 - swc
+ 3595, // 744 - swc-cd
+ 3601, // 745 - syr
+ 3604, // 746 - syr-sy
+ 3610, // 747 - ta
+ 3612, // 748 - ta-in
+ 3617, // 749 - ta-lk
+ 3622, // 750 - ta-my
+ 3627, // 751 - ta-sg
+ 3632, // 752 - te
+ 3634, // 753 - te-in
+ 3639, // 754 - teo
+ 3642, // 755 - teo-ke
+ 3648, // 756 - teo-ug
+ 3654, // 757 - tg
+ 3656, // 758 - tg-cyrl
+ 3663, // 759 - tg-cyrl-tj
+ 3673, // 760 - th
+ 3675, // 761 - th-th
+ 3680, // 762 - ti
+ 3682, // 763 - ti-er
+ 3687, // 764 - ti-et
+ 3692, // 765 - tig
+ 3695, // 766 - tig-er
+ 3701, // 767 - tk
+ 3703, // 768 - tk-tm
+ 3708, // 769 - tn
+ 3710, // 770 - tn-bw
+ 3715, // 771 - tn-za
+ 3720, // 772 - to
+ 3722, // 773 - to-to
+ 3727, // 774 - tr
+ 3729, // 775 - tr-cy
+ 3734, // 776 - tr-tr
+ 3739, // 777 - ts
+ 3741, // 778 - ts-za
+ 3746, // 779 - tt
+ 3748, // 780 - tt-ru
+ 3753, // 781 - twq
+ 3756, // 782 - twq-ne
+ 3762, // 783 - tzm
+ 3765, // 784 - tzm-arab
+ 3773, // 785 - tzm-arab-ma
+ 3784, // 786 - tzm-latn
+ 3792, // 787 - tzm-latn-dz
+ 3803, // 788 - tzm-latn-ma
+ 3814, // 789 - tzm-tfng
+ 3822, // 790 - tzm-tfng-ma
+ 3833, // 791 - ug
+ 3835, // 792 - ug-cn
+ 3840, // 793 - uk
+ 3842, // 794 - uk-ua
+ 3847, // 795 - ur
+ 3849, // 796 - ur-in
+ 3854, // 797 - ur-pk
+ 3859, // 798 - uz
+ 3861, // 799 - uz-arab
+ 3868, // 800 - uz-arab-af
+ 3878, // 801 - uz-cyrl
+ 3885, // 802 - uz-cyrl-uz
+ 3895, // 803 - uz-latn
+ 3902, // 804 - uz-latn-uz
+ 3912, // 805 - vai
+ 3915, // 806 - vai-latn
+ 3923, // 807 - vai-latn-lr
+ 3934, // 808 - vai-vaii
+ 3942, // 809 - vai-vaii-lr
+ 3953, // 810 - ve
+ 3955, // 811 - ve-za
+ 3960, // 812 - vi
+ 3962, // 813 - vi-vn
+ 3967, // 814 - vo
+ 3969, // 815 - vo-001
+ 3975, // 816 - vun
+ 3978, // 817 - vun-tz
+ 3984, // 818 - wae
+ 3987, // 819 - wae-ch
+ 3993, // 820 - wal
+ 3996, // 821 - wal-et
+ 4002, // 822 - wo
+ 4004, // 823 - wo-sn
+ 4009, // 824 - x-iv_mathan
+ 4020, // 825 - xh
+ 4022, // 826 - xh-za
+ 4027, // 827 - xog
+ 4030, // 828 - xog-ug
+ 4036, // 829 - yav
+ 4039, // 830 - yav-cm
+ 4045, // 831 - yi
+ 4047, // 832 - yi-001
+ 4053, // 833 - yo
+ 4055, // 834 - yo-bj
+ 4060, // 835 - yo-ng
+ 4065, // 836 - yue
+ 4068, // 837 - yue-hk
+ 4074, // 838 - zgh
+ 4077, // 839 - zgh-tfng
+ 4085, // 840 - zgh-tfng-ma
+ 4096, // 841 - zh
+ 4098, // 842 - zh-chs
+ 4104, // 843 - zh-cht
+ 4110, // 844 - zh-cn
+ 4115, // 845 - zh-cn_phoneb
+ 4127, // 846 - zh-cn_stroke
+ 4139, // 847 - zh-hans
+ 4146, // 848 - zh-hans-hk
+ 4156, // 849 - zh-hans-mo
+ 4166, // 850 - zh-hant
+ 4173, // 851 - zh-hk
+ 4178, // 852 - zh-hk_radstr
+ 4190, // 853 - zh-mo
+ 4195, // 854 - zh-mo_radstr
+ 4207, // 855 - zh-mo_stroke
+ 4219, // 856 - zh-sg
+ 4224, // 857 - zh-sg_phoneb
+ 4236, // 858 - zh-sg_stroke
+ 4248, // 859 - zh-tw
+ 4253, // 860 - zh-tw_pronun
+ 4265, // 861 - zh-tw_radstr
+ 4277, // 862 - zu
+ 4279, // 863 - zu-za
+ 4284
+ };
+
+ private const int NUMERIC_LOCALE_DATA_COUNT_PER_ROW = 9;
+ // s_nameIndexToNumericData is mapping from index in s_localeNamesIndices to locale data.
+ // each row in the table will have the following data:
+ // Lcid, Ansi codepage, Oem codepage, MAC codepage, EBCDIC codepage, Geo Id, Digit Substitution, specific locale index, Console locale index
+ private static readonly int[] s_nameIndexToNumericData = new int[]
+ {
+ // Lcid, Ansi CP, Oem CP, MAC CP, EBCDIC CP, Geo Id, digit substitution, Specific culture index, keyboard Id, Console locale index // index - locale name
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 3 , 240 , // 0 - aa
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3e , 1 , 1 , 240 , // 1 - aa-dj
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 2 , 240 , // 2 - aa-er
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 3 , 240 , // 3 - aa-et
+ 0x36 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 6 , 6 , // 4 - af
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xfe , 1 , 5 , 240 , // 5 - af-na
+ 0x436 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 6 , 6 , // 6 - af-za
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 8 , 240 , // 7 - agq
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 8 , 240 , // 8 - agq-cm
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x59 , 1 , 10 , 240 , // 9 - ak
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x59 , 1 , 10 , 240 , // 10 - ak-gh
+ 0x5e , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 12 , 143 , // 11 - am
+ 0x45e , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 12 , 143 , // 12 - am-et
+ 0x1 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xcd , 0 , 33 , 143 , // 13 - ar
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x989e, 0 , 14 , 240 , // 14 - ar-001
+ 0x3801 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xe0 , 0 , 15 , 143 , // 15 - ar-ae
+ 0x3c01 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x11 , 0 , 16 , 143 , // 16 - ar-bh
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x3e , 0 , 17 , 240 , // 17 - ar-dj
+ 0x1401 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x4 , 1 , 18 , 300 , // 18 - ar-dz
+ 0xc01 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x43 , 0 , 19 , 143 , // 19 - ar-eg
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x47 , 0 , 20 , 240 , // 20 - ar-er
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x75 , 0 , 21 , 240 , // 21 - ar-il
+ 0x801 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x79 , 0 , 22 , 143 , // 22 - ar-iq
+ 0x2c01 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x7e , 0 , 23 , 143 , // 23 - ar-jo
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x32 , 0 , 24 , 240 , // 24 - ar-km
+ 0x3401 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x88 , 0 , 25 , 143 , // 25 - ar-kw
+ 0x3001 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x8b , 0 , 26 , 143 , // 26 - ar-lb
+ 0x1001 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x94 , 1 , 27 , 143 , // 27 - ar-ly
+ 0x1801 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x9f , 1 , 28 , 300 , // 28 - ar-ma
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xa2 , 0 , 29 , 240 , // 29 - ar-mr
+ 0x2001 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xa4 , 0 , 30 , 143 , // 30 - ar-om
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xb8 , 0 , 31 , 240 , // 31 - ar-ps
+ 0x4001 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xc5 , 0 , 32 , 143 , // 32 - ar-qa
+ 0x401 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xcd , 0 , 33 , 143 , // 33 - ar-sa
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xdb , 0 , 34 , 240 , // 34 - ar-sd
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xd8 , 0 , 35 , 240 , // 35 - ar-so
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x114 , 0 , 36 , 240 , // 36 - ar-ss
+ 0x2801 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xde , 0 , 37 , 143 , // 37 - ar-sy
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x29 , 0 , 38 , 240 , // 38 - ar-td
+ 0x1c01 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xea , 1 , 39 , 300 , // 39 - ar-tn
+ 0x2401 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x105 , 0 , 40 , 143 , // 40 - ar-ye
+ 0x7a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x2e , 1 , 42 , 42 , // 41 - arn
+ 0x47a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x2e , 1 , 42 , 42 , // 42 - arn-cl
+ 0x4d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 44 , 143 , // 43 - as
+ 0x44d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 44 , 143 , // 44 - as-in
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 46 , 240 , // 45 - asa
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 46 , 240 , // 46 - asa-tz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd9 , 1 , 48 , 240 , // 47 - ast
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd9 , 1 , 48 , 240 , // 48 - ast-es
+ 0x2c , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0x5 , 1 , 53 , 53 , // 49 - az
+ 0x742c , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x5 , 1 , 51 , 51 , // 50 - az-cyrl
+ 0x82c , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x5 , 1 , 51 , 51 , // 51 - az-cyrl-az
+ 0x782c , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0x5 , 1 , 53 , 53 , // 52 - az-latn
+ 0x42c , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0x5 , 1 , 53 , 53 , // 53 - az-latn-az
+ 0x6d , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 55 , 55 , // 54 - ba
+ 0x46d , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 55 , 55 , // 55 - ba-ru
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 57 , 240 , // 56 - bas
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 57 , 240 , // 57 - bas-cm
+ 0x23 , 0x4e3 , 0x362 , 0x2717, 0x1f4 , 0x1d , 1 , 59 , 59 , // 58 - be
+ 0x423 , 0x4e3 , 0x362 , 0x2717, 0x1f4 , 0x1d , 1 , 59 , 59 , // 59 - be-by
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x107 , 1 , 61 , 240 , // 60 - bem
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x107 , 1 , 61 , 240 , // 61 - bem-zm
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 63 , 240 , // 62 - bez
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 63 , 240 , // 63 - bez-tz
+ 0x2 , 0x4e3 , 0x362 , 0x2717, 0x5221, 0x23 , 1 , 65 , 65 , // 64 - bg
+ 0x402 , 0x4e3 , 0x362 , 0x2717, 0x5221, 0x23 , 1 , 65 , 65 , // 65 - bg-bg
+ 0x66 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 67 , 240 , // 66 - bin
+ 0x466 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 67 , 240 , // 67 - bin-ng
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 70 , 240 , // 68 - bm
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 70 , 240 , // 69 - bm-latn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 70 , 240 , // 70 - bm-latn-ml
+ 0x45 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x17 , 1 , 72 , 143 , // 71 - bn
+ 0x845 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x17 , 1 , 72 , 143 , // 72 - bn-bd
+ 0x445 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 73 , 143 , // 73 - bn-in
+ 0x51 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 75 , 143 , // 74 - bo
+ 0x451 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 75 , 143 , // 75 - bo-cn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 76 , 240 , // 76 - bo-in
+ 0x7e , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 78 , 78 , // 77 - br
+ 0x47e , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 78 , 78 , // 78 - br-fr
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 80 , 240 , // 79 - brx
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 80 , 240 , // 80 - brx-in
+ 0x781a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 85 , 85 , // 81 - bs
+ 0x641a , 0x4e3 , 0x357 , 0x2762, 0x366 , 0x19 , 1 , 83 , 83 , // 82 - bs-cyrl
+ 0x201a , 0x4e3 , 0x357 , 0x2762, 0x366 , 0x19 , 1 , 83 , 83 , // 83 - bs-cyrl-ba
+ 0x681a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 85 , 85 , // 84 - bs-latn
+ 0x141a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 85 , 85 , // 85 - bs-latn-ba
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 87 , 240 , // 86 - byn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 87 , 240 , // 87 - byn-er
+ 0x3 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 90 , 90 , // 88 - ca
+ 0x1000 , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0x8 , 1 , 89 , 240 , // 89 - ca-ad
+ 0x403 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 90 , 90 , // 90 - ca-es
+ 0x803 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 91 , 90 , // 91 - ca-es-valencia
+ 0x1000 , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0x54 , 1 , 92 , 240 , // 92 - ca-fr
+ 0x1000 , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0x76 , 1 , 93 , 240 , // 93 - ca-it
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 95 , 240 , // 94 - ce
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 95 , 240 , // 95 - ce-ru
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 97 , 240 , // 96 - cgg
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 97 , 240 , // 97 - cgg-ug
+ 0x5c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 100 , 240 , // 98 - chr
+ 0x7c5c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 100 , 240 , // 99 - chr-cher
+ 0x45c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 100 , 240 , // 100 - chr-cher-us
+ 0x83 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 102 , 102 , // 101 - co
+ 0x483 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 102 , 102 , // 102 - co-fr
+ 0x5 , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x4b , 1 , 104 , 104 , // 103 - cs
+ 0x405 , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x4b , 1 , 104 , 104 , // 104 - cs-cz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 106 , 240 , // 105 - cu
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 106 , 240 , // 106 - cu-ru
+ 0x52 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 108 , 108 , // 107 - cy
+ 0x452 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 108 , 108 , // 108 - cy-gb
+ 0x6 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0x3d , 1 , 110 , 110 , // 109 - da
+ 0x406 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0x3d , 1 , 110 , 110 , // 110 - da-dk
+ 0x1000 , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0x5d , 1 , 111 , 240 , // 111 - da-gl
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 113 , 240 , // 112 - dav
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 113 , 240 , // 113 - dav-ke
+ 0x7 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x5e , 1 , 118 , 118 , // 114 - de
+ 0xc07 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0xe , 1 , 115 , 115 , // 115 - de-at
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x15 , 1 , 116 , 240 , // 116 - de-be
+ 0x807 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0xdf , 1 , 117 , 117 , // 117 - de-ch
+ 0x407 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x5e , 1 , 118 , 118 , // 118 - de-de
+ 0x10407, 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x5e , 1 , 118 , 118 , // 119 - de-de_phoneb
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x76 , 1 , 120 , 240 , // 120 - de-it
+ 0x1407 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x91 , 1 , 121 , 121 , // 121 - de-li
+ 0x1007 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x93 , 1 , 122 , 122 , // 122 - de-lu
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xad , 1 , 124 , 240 , // 123 - dje
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xad , 1 , 124 , 240 , // 124 - dje-ne
+ 0x7c2e , 0x4e4 , 0x352 , 0x2710, 0x366 , 0x5e , 1 , 126 , 126 , // 125 - dsb
+ 0x82e , 0x4e4 , 0x352 , 0x2710, 0x366 , 0x5e , 1 , 126 , 126 , // 126 - dsb-de
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 128 , 240 , // 127 - dua
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 128 , 240 , // 128 - dua-cm
+ 0x65 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa5 , 1 , 130 , 143 , // 129 - dv
+ 0x465 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa5 , 1 , 130 , 143 , // 130 - dv-mv
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd2 , 1 , 132 , 240 , // 131 - dyo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd2 , 1 , 132 , 240 , // 132 - dyo-sn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x22 , 2 , 134 , 240 , // 133 - dz
+ 0xc51 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x22 , 2 , 134 , 240 , // 134 - dz-bt
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 136 , 240 , // 135 - ebu
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 136 , 240 , // 136 - ebu-ke
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x59 , 1 , 138 , 240 , // 137 - ee
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x59 , 1 , 138 , 240 , // 138 - ee-gh
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xe8 , 1 , 139 , 240 , // 139 - ee-tg
+ 0x8 , 0x4e5 , 0x2e1 , 0x2716, 0x4f31, 0x62 , 1 , 142 , 142 , // 140 - el
+ 0x1000 , 0x4e5 , 0x2e1 , 0x2716, 0x4f31, 0x3b , 1 , 141 , 240 , // 141 - el-cy
+ 0x408 , 0x4e5 , 0x2e1 , 0x2716, 0x4f31, 0x62 , 1 , 142 , 142 , // 142 - el-gr
+ 0x9 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , 240 , 240 , // 143 - en
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x989e, 1 , 144 , 240 , // 144 - en-001
+ 0x2409 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x993248, 1 , 145 , 145 , // 145 - en-029
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x292d, 1 , 146 , 240 , // 146 - en-150
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x2 , 1 , 147 , 240 , // 147 - en-ag
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x12c , 1 , 148 , 240 , // 148 - en-ai
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa , 1 , 149 , 240 , // 149 - en-as
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xe , 1 , 150 , 240 , // 150 - en-at
+ 0xc09 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc , 1 , 151 , 151 , // 151 - en-au
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x12 , 1 , 152 , 240 , // 152 - en-bb
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15 , 1 , 153 , 240 , // 153 - en-be
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x26 , 1 , 154 , 240 , // 154 - en-bi
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x14 , 1 , 155 , 240 , // 155 - en-bm
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x16 , 1 , 156 , 240 , // 156 - en-bs
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x13 , 1 , 157 , 240 , // 157 - en-bw
+ 0x2809 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x18 , 1 , 158 , 158 , // 158 - en-bz
+ 0x1009 , 0x4e4 , 0x352 , 0x2710, 0x25 , 0x27 , 1 , 159 , 159 , // 159 - en-ca
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x137 , 1 , 160 , 240 , // 160 - en-cc
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdf , 1 , 161 , 240 , // 161 - en-ch
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x138 , 1 , 162 , 240 , // 162 - en-ck
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x31 , 1 , 163 , 240 , // 163 - en-cm
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x135 , 1 , 164 , 240 , // 164 - en-cx
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3b , 1 , 165 , 240 , // 165 - en-cy
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 166 , 240 , // 166 - en-de
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3d , 1 , 167 , 240 , // 167 - en-dk
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x3f , 1 , 168 , 240 , // 168 - en-dm
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x47 , 1 , 169 , 240 , // 169 - en-er
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x4d , 1 , 170 , 240 , // 170 - en-fi
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x4e , 1 , 171 , 240 , // 171 - en-fj
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x13b , 1 , 172 , 240 , // 172 - en-fk
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x50 , 1 , 173 , 240 , // 173 - en-fm
+ 0x809 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 174 , 174 , // 174 - en-gb
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x5b , 1 , 175 , 240 , // 175 - en-gd
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x144 , 1 , 176 , 240 , // 176 - en-gg
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x59 , 1 , 177 , 240 , // 177 - en-gh
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x5a , 1 , 178 , 240 , // 178 - en-gi
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x56 , 1 , 179 , 240 , // 179 - en-gm
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x142 , 1 , 180 , 240 , // 180 - en-gu
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x65 , 1 , 181 , 240 , // 181 - en-gy
+ 0x3c09 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x68 , 1 , 182 , 240 , // 182 - en-hk
+ 0x3809 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 183 , 240 , // 183 - en-id
+ 0x1809 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x44 , 1 , 184 , 184 , // 184 - en-ie
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x75 , 1 , 185 , 240 , // 185 - en-il
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x3b16, 1 , 186 , 240 , // 186 - en-im
+ 0x4009 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x71 , 1 , 187 , 187 , // 187 - en-in
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x72 , 1 , 188 , 240 , // 188 - en-io
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x148 , 1 , 189 , 240 , // 189 - en-je
+ 0x2009 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x7c , 1 , 190 , 190 , // 190 - en-jm
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x81 , 1 , 191 , 240 , // 191 - en-ke
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x85 , 1 , 192 , 240 , // 192 - en-ki
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xcf , 1 , 193 , 240 , // 193 - en-kn
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x133 , 1 , 194 , 240 , // 194 - en-ky
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xda , 1 , 195 , 240 , // 195 - en-lc
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x8e , 1 , 196 , 240 , // 196 - en-lr
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x92 , 1 , 197 , 240 , // 197 - en-ls
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x95 , 1 , 198 , 240 , // 198 - en-mg
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc7 , 1 , 199 , 240 , // 199 - en-mh
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x97 , 1 , 200 , 240 , // 200 - en-mo
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x151 , 1 , 201 , 240 , // 201 - en-mp
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x14c , 1 , 202 , 240 , // 202 - en-ms
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa3 , 1 , 203 , 240 , // 203 - en-mt
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa0 , 1 , 204 , 240 , // 204 - en-mu
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x9c , 1 , 205 , 240 , // 205 - en-mw
+ 0x4409 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xa7 , 1 , 206 , 206 , // 206 - en-my
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xfe , 1 , 207 , 240 , // 207 - en-na
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x150 , 1 , 208 , 240 , // 208 - en-nf
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 209 , 240 , // 209 - en-ng
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb0 , 1 , 210 , 240 , // 210 - en-nl
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb4 , 1 , 211 , 240 , // 211 - en-nr
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x14f , 1 , 212 , 240 , // 212 - en-nu
+ 0x1409 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb7 , 1 , 213 , 213 , // 213 - en-nz
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc2 , 1 , 214 , 240 , // 214 - en-pg
+ 0x3409 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xc9 , 1 , 215 , 215 , // 215 - en-ph
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xbe , 1 , 216 , 240 , // 216 - en-pk
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x153 , 1 , 217 , 240 , // 217 - en-pn
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xca , 1 , 218 , 240 , // 218 - en-pr
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc3 , 1 , 219 , 240 , // 219 - en-pw
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xcc , 1 , 220 , 240 , // 220 - en-rw
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x1e , 1 , 221 , 240 , // 221 - en-sb
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd0 , 1 , 222 , 240 , // 222 - en-sc
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xdb , 1 , 223 , 240 , // 223 - en-sd
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdd , 1 , 224 , 240 , // 224 - en-se
+ 0x4809 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xd7 , 1 , 225 , 225 , // 225 - en-sg
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x157 , 1 , 226 , 240 , // 226 - en-sh
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd4 , 1 , 227 , 240 , // 227 - en-si
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd5 , 1 , 228 , 240 , // 228 - en-sl
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x114 , 1 , 229 , 240 , // 229 - en-ss
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x78f7, 1 , 230 , 240 , // 230 - en-sx
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x104 , 1 , 231 , 240 , // 231 - en-sz
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15d , 1 , 232 , 240 , // 232 - en-tc
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15b , 1 , 233 , 240 , // 233 - en-tk
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xe7 , 1 , 234 , 240 , // 234 - en-to
+ 0x2c09 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xe1 , 1 , 235 , 235 , // 235 - en-tt
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xec , 1 , 236 , 240 , // 236 - en-tv
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xef , 1 , 237 , 240 , // 237 - en-tz
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xf0 , 1 , 238 , 240 , // 238 - en-ug
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x9a55d40, 1 , 239 , 240 , // 239 - en-um
+ 0x409 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , 240 , 240 , // 240 - en-us
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xf8 , 1 , 241 , 240 , // 241 - en-vc
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15f , 1 , 242 , 240 , // 242 - en-vg
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xfc , 1 , 243 , 240 , // 243 - en-vi
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xae , 1 , 244 , 240 , // 244 - en-vu
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x103 , 1 , 245 , 240 , // 245 - en-ws
+ 0x1c09 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xd1 , 1 , 246 , 246 , // 246 - en-za
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x107 , 1 , 247 , 240 , // 247 - en-zm
+ 0x3009 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x108 , 1 , 248 , 248 , // 248 - en-zw
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 250 , 240 , // 249 - eo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 250 , 240 , // 250 - eo-001
+ 0xa , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xd9 , 1 , 262 , 262 , // 251 - es
+ 0x580a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x9a55d41, 1 , 252 , 240 , // 252 - es-419
+ 0x2c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb , 1 , 253 , 253 , // 253 - es-ar
+ 0x400a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x1a , 1 , 254 , 254 , // 254 - es-bo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x20 , 1 , 255 , 240 , // 255 - es-br
+ 0x340a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x2e , 1 , 256 , 256 , // 256 - es-cl
+ 0x240a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x33 , 1 , 257 , 257 , // 257 - es-co
+ 0x140a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x36 , 1 , 258 , 258 , // 258 - es-cr
+ 0x5c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x38 , 1 , 259 , 240 , // 259 - es-cu
+ 0x1c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x41 , 1 , 260 , 260 , // 260 - es-do
+ 0x300a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x42 , 1 , 261 , 261 , // 261 - es-ec
+ 0xc0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xd9 , 1 , 262 , 262 , // 262 - es-es
+ 0x40a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xd9 , 1 , 263 , 263 , // 263 - es-es_tradnl
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x45 , 1 , 264 , 240 , // 264 - es-gq
+ 0x100a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x63 , 1 , 265 , 265 , // 265 - es-gt
+ 0x480a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x6a , 1 , 266 , 266 , // 266 - es-hn
+ 0x80a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xa6 , 1 , 267 , 267 , // 267 - es-mx
+ 0x4c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb6 , 1 , 268 , 268 , // 268 - es-ni
+ 0x180a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xc0 , 1 , 269 , 269 , // 269 - es-pa
+ 0x280a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xbb , 1 , 270 , 270 , // 270 - es-pe
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xc9 , 1 , 271 , 240 , // 271 - es-ph
+ 0x500a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xca , 1 , 272 , 272 , // 272 - es-pr
+ 0x3c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb9 , 1 , 273 , 273 , // 273 - es-py
+ 0x440a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x48 , 1 , 274 , 274 , // 274 - es-sv
+ 0x540a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xf4 , 1 , 275 , 275 , // 275 - es-us
+ 0x380a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xf6 , 1 , 276 , 276 , // 276 - es-uy
+ 0x200a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xf9 , 1 , 277 , 277 , // 277 - es-ve
+ 0x25 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x46 , 1 , 279 , 279 , // 278 - et
+ 0x425 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x46 , 1 , 279 , 279 , // 279 - et-ee
+ 0x2d , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0xd9 , 1 , 281 , 240 , // 280 - eu
+ 0x42d , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0xd9 , 1 , 281 , 240 , // 281 - eu-es
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 283 , 240 , // 282 - ewo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 283 , 240 , // 283 - ewo-cm
+ 0x29 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x74 , 0 , 285 , 143 , // 284 - fa
+ 0x429 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x74 , 0 , 285 , 143 , // 285 - fa-ir
+ 0x67 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 290 , 290 , // 286 - ff
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x31 , 1 , 287 , 240 , // 287 - ff-cm
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x64 , 1 , 288 , 240 , // 288 - ff-gn
+ 0x7c67 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 290 , 290 , // 289 - ff-latn
+ 0x867 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 290 , 290 , // 290 - ff-latn-sn
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xa2 , 1 , 291 , 240 , // 291 - ff-mr
+ 0x467 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xaf , 1 , 292 , 240 , // 292 - ff-ng
+ 0xb , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 294 , 294 , // 293 - fi
+ 0x40b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 294 , 294 , // 294 - fi-fi
+ 0x64 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xc9 , 1 , 296 , 296 , // 295 - fil
+ 0x464 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xc9 , 1 , 296 , 296 , // 296 - fil-ph
+ 0x38 , 0x4e4 , 0x352 , 0x275f, 0x4f35, 0x51 , 1 , 299 , 299 , // 297 - fo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3d , 1 , 298 , 240 , // 298 - fo-dk
+ 0x438 , 0x4e4 , 0x352 , 0x275f, 0x4f35, 0x51 , 1 , 299 , 299 , // 299 - fo-fo
+ 0xc , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 316 , 316 , // 300 - fr
+ 0x1c0c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x993248, 1 , 301 , 316 , // 301 - fr-029
+ 0x80c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x15 , 1 , 302 , 302 , // 302 - fr-be
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xf5 , 1 , 303 , 240 , // 303 - fr-bf
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x26 , 1 , 304 , 240 , // 304 - fr-bi
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x1c , 1 , 305 , 240 , // 305 - fr-bj
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x9a55c4f, 1 , 306 , 240 , // 306 - fr-bl
+ 0xc0c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x27 , 1 , 307 , 307 , // 307 - fr-ca
+ 0x240c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x2c , 1 , 308 , 240 , // 308 - fr-cd
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x37 , 1 , 309 , 240 , // 309 - fr-cf
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x2b , 1 , 310 , 240 , // 310 - fr-cg
+ 0x100c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xdf , 1 , 311 , 311 , // 311 - fr-ch
+ 0x300c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x77 , 1 , 312 , 240 , // 312 - fr-ci
+ 0x2c0c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x31 , 1 , 313 , 240 , // 313 - fr-cm
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x3e , 1 , 314 , 240 , // 314 - fr-dj
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x4 , 1 , 315 , 240 , // 315 - fr-dz
+ 0x40c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 316 , 316 , // 316 - fr-fr
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x57 , 1 , 317 , 240 , // 317 - fr-ga
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x13d , 1 , 318 , 240 , // 318 - fr-gf
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x64 , 1 , 319 , 240 , // 319 - fr-gn
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x141 , 1 , 320 , 240 , // 320 - fr-gp
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x45 , 1 , 321 , 240 , // 321 - fr-gq
+ 0x3c0c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x67 , 1 , 322 , 240 , // 322 - fr-ht
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x32 , 1 , 323 , 240 , // 323 - fr-km
+ 0x140c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x93 , 1 , 324 , 324 , // 324 - fr-lu
+ 0x380c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x9f , 1 , 325 , 240 , // 325 - fr-ma
+ 0x180c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x9e , 1 , 326 , 326 , // 326 - fr-mc
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x7bda, 1 , 327 , 240 , // 327 - fr-mf
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x95 , 1 , 328 , 240 , // 328 - fr-mg
+ 0x340c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x9d , 1 , 329 , 240 , // 329 - fr-ml
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x14a , 1 , 330 , 240 , // 330 - fr-mq
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xa2 , 1 , 331 , 240 , // 331 - fr-mr
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xa0 , 1 , 332 , 240 , // 332 - fr-mu
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x14e , 1 , 333 , 240 , // 333 - fr-nc
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xad , 1 , 334 , 240 , // 334 - fr-ne
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x13e , 1 , 335 , 240 , // 335 - fr-pf
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xce , 1 , 336 , 240 , // 336 - fr-pm
+ 0x200c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xc6 , 1 , 337 , 240 , // 337 - fr-re
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xcc , 1 , 338 , 240 , // 338 - fr-rw
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd0 , 1 , 339 , 240 , // 339 - fr-sc
+ 0x280c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 340 , 240 , // 340 - fr-sn
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xde , 1 , 341 , 240 , // 341 - fr-sy
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x29 , 1 , 342 , 240 , // 342 - fr-td
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xe8 , 1 , 343 , 240 , // 343 - fr-tg
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xea , 1 , 344 , 240 , // 344 - fr-tn
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xae , 1 , 345 , 240 , // 345 - fr-vu
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x160 , 1 , 346 , 240 , // 346 - fr-wf
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x14b , 1 , 347 , 240 , // 347 - fr-yt
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x76 , 1 , 349 , 240 , // 348 - fur
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x76 , 1 , 349 , 240 , // 349 - fur-it
+ 0x62 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb0 , 1 , 351 , 351 , // 350 - fy
+ 0x462 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb0 , 1 , 351 , 351 , // 351 - fy-nl
+ 0x3c , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x44 , 1 , 353 , 353 , // 352 - ga
+ 0x83c , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x44 , 1 , 353 , 353 , // 353 - ga-ie
+ 0x91 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 355 , 355 , // 354 - gd
+ 0x491 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 355 , 355 , // 355 - gd-gb
+ 0x56 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 357 , 357 , // 356 - gl
+ 0x456 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 357 , 357 , // 357 - gl-es
+ 0x74 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb9 , 1 , 359 , 359 , // 358 - gn
+ 0x474 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb9 , 1 , 359 , 359 , // 359 - gn-py
+ 0x84 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xdf , 1 , 361 , 240 , // 360 - gsw
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xdf , 1 , 361 , 240 , // 361 - gsw-ch
+ 0x484 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 362 , 362 , // 362 - gsw-fr
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x91 , 1 , 363 , 240 , // 363 - gsw-li
+ 0x47 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 365 , 143 , // 364 - gu
+ 0x447 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 365 , 143 , // 365 - gu-in
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 367 , 240 , // 366 - guz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 367 , 240 , // 367 - guz-ke
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3b16, 1 , 369 , 240 , // 368 - gv
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3b16, 1 , 369 , 240 , // 369 - gv-im
+ 0x68 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 374 , 374 , // 370 - ha
+ 0x7c68 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 374 , 374 , // 371 - ha-latn
+ 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x59 , 1 , 372 , 240 , // 372 - ha-latn-gh
+ 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xad , 1 , 373 , 240 , // 373 - ha-latn-ne
+ 0x468 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 374 , 374 , // 374 - ha-latn-ng
+ 0x75 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , 376 , 376 , // 375 - haw
+ 0x475 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , 376 , 376 , // 376 - haw-us
+ 0xd , 0x4e7 , 0x35e , 0x2715, 0x1f4 , 0x75 , 1 , 378 , 143 , // 377 - he
+ 0x40d , 0x4e7 , 0x35e , 0x2715, 0x1f4 , 0x75 , 1 , 378 , 143 , // 378 - he-il
+ 0x39 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 380 , 143 , // 379 - hi
+ 0x439 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 380 , 143 , // 380 - hi-in
+ 0x1a , 0x4e2 , 0x354 , 0x2762, 0x1f4 , 0x6c , 1 , 383 , 383 , // 381 - hr
+ 0x101a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 382 , 382 , // 382 - hr-ba
+ 0x41a , 0x4e2 , 0x354 , 0x2762, 0x1f4 , 0x6c , 1 , 383 , 383 , // 383 - hr-hr
+ 0x2e , 0x4e4 , 0x352 , 0x2710, 0x366 , 0x5e , 1 , 385 , 385 , // 384 - hsb
+ 0x42e , 0x4e4 , 0x352 , 0x2710, 0x366 , 0x5e , 1 , 385 , 385 , // 385 - hsb-de
+ 0xe , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x6d , 1 , 387 , 387 , // 386 - hu
+ 0x40e , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x6d , 1 , 387 , 387 , // 387 - hu-hu
+ 0x1040e, 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x6d , 1 , 387 , 387 , // 388 - hu-hu_technl
+ 0x2b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x7 , 1 , 390 , 390 , // 389 - hy
+ 0x42b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x7 , 1 , 390 , 390 , // 390 - hy-am
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x54 , 1 , 393 , 240 , // 391 - ia
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 392 , 240 , // 392 - ia-001
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x54 , 1 , 393 , 240 , // 393 - ia-fr
+ 0x69 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 395 , 240 , // 394 - ibb
+ 0x469 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 395 , 240 , // 395 - ibb-ng
+ 0x21 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 397 , 397 , // 396 - id
+ 0x421 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 397 , 397 , // 397 - id-id
+ 0x70 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 399 , 399 , // 398 - ig
+ 0x470 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 399 , 399 , // 399 - ig-ng
+ 0x78 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 401 , 143 , // 400 - ii
+ 0x478 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 401 , 143 , // 401 - ii-cn
+ 0xf , 0x4e4 , 0x352 , 0x275f, 0x5187, 0x6e , 1 , 403 , 403 , // 402 - is
+ 0x40f , 0x4e4 , 0x352 , 0x275f, 0x5187, 0x6e , 1 , 403 , 403 , // 403 - is-is
+ 0x10 , 0x4e4 , 0x352 , 0x2710, 0x4f38, 0x76 , 1 , 406 , 406 , // 404 - it
+ 0x810 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xdf , 1 , 405 , 405 , // 405 - it-ch
+ 0x410 , 0x4e4 , 0x352 , 0x2710, 0x4f38, 0x76 , 1 , 406 , 406 , // 406 - it-it
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f38, 0xd6 , 1 , 407 , 240 , // 407 - it-sm
+ 0x5d , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x27 , 1 , 412 , 412 , // 408 - iu
+ 0x785d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x27 , 1 , 410 , 143 , // 409 - iu-cans
+ 0x45d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x27 , 1 , 410 , 143 , // 410 - iu-cans-ca
+ 0x7c5d , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x27 , 1 , 412 , 412 , // 411 - iu-latn
+ 0x85d , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x27 , 1 , 412 , 412 , // 412 - iu-latn-ca
+ 0x11 , 0x3a4 , 0x3a4 , 0x2711, 0x4f42, 0x7a , 1 , 414 , 414 , // 413 - ja
+ 0x411 , 0x3a4 , 0x3a4 , 0x2711, 0x4f42, 0x7a , 1 , 414 , 414 , // 414 - ja-jp
+ 0x40411, 0x3a4 , 0x3a4 , 0x2711, 0x4f42, 0x7a , 1 , 414 , 414 , // 415 - ja-jp_radstr
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 417 , 240 , // 416 - jgo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 417 , 240 , // 417 - jgo-cm
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 419 , 240 , // 418 - jmc
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 419 , 240 , // 419 - jmc-tz
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 424 , 424 , // 420 - jv
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 422 , 424 , // 421 - jv-java
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 422 , 424 , // 422 - jv-java-id
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 424 , 424 , // 423 - jv-latn
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 424 , 424 , // 424 - jv-latn-id
+ 0x37 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 426 , 426 , // 425 - ka
+ 0x437 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 426 , 426 , // 426 - ka-ge
+ 0x10437, 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 426 , 426 , // 427 - ka-ge_modern
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x4 , 1 , 429 , 240 , // 428 - kab
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x4 , 1 , 429 , 240 , // 429 - kab-dz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 431 , 240 , // 430 - kam
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 431 , 240 , // 431 - kam-ke
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 433 , 240 , // 432 - kde
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 433 , 240 , // 433 - kde-tz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x39 , 1 , 435 , 240 , // 434 - kea
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x39 , 1 , 435 , 240 , // 435 - kea-cv
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 437 , 240 , // 436 - khq
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 437 , 240 , // 437 - khq-ml
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 439 , 240 , // 438 - ki
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 439 , 240 , // 439 - ki-ke
+ 0x3f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x89 , 1 , 441 , 441 , // 440 - kk
+ 0x43f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x89 , 1 , 441 , 441 , // 441 - kk-kz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 443 , 240 , // 442 - kkj
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 443 , 240 , // 443 - kkj-cm
+ 0x6f , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0x5d , 1 , 445 , 445 , // 444 - kl
+ 0x46f , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0x5d , 1 , 445 , 445 , // 445 - kl-gl
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 447 , 240 , // 446 - kln
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 447 , 240 , // 447 - kln-ke
+ 0x53 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x28 , 2 , 449 , 143 , // 448 - km
+ 0x453 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x28 , 2 , 449 , 143 , // 449 - km-kh
+ 0x4b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 451 , 143 , // 450 - kn
+ 0x44b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 451 , 143 , // 451 - kn-in
+ 0x12 , 0x3b5 , 0x3b5 , 0x2713, 0x5161, 0x86 , 1 , 454 , 454 , // 452 - ko
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x83 , 1 , 453 , 240 , // 453 - ko-kp
+ 0x412 , 0x3b5 , 0x3b5 , 0x2713, 0x5161, 0x86 , 1 , 454 , 454 , // 454 - ko-kr
+ 0x57 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 456 , 143 , // 455 - kok
+ 0x457 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 456 , 143 , // 456 - kok-in
+ 0x71 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 458 , 240 , // 457 - kr
+ 0x471 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 458 , 240 , // 458 - kr-ng
+ 0x60 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 2 , 461 , 240 , // 459 - ks
+ 0x460 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 2 , 461 , 240 , // 460 - ks-arab
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 2 , 461 , 240 , // 461 - ks-arab-in
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 463 , 187 , // 462 - ks-deva
+ 0x860 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 463 , 187 , // 463 - ks-deva-in
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 465 , 240 , // 464 - ksb
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 465 , 240 , // 465 - ksb-tz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 467 , 240 , // 466 - ksf
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 467 , 240 , // 467 - ksf-cm
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 469 , 240 , // 468 - ksh
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 469 , 240 , // 469 - ksh-de
+ 0x92 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x79 , 0 , 472 , 143 , // 470 - ku
+ 0x7c92 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x79 , 0 , 472 , 143 , // 471 - ku-arab
+ 0x492 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x79 , 0 , 472 , 143 , // 472 - ku-arab-iq
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 0 , 473 , 240 , // 473 - ku-arab-ir
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf2 , 1 , 475 , 240 , // 474 - kw
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf2 , 1 , 475 , 240 , // 475 - kw-gb
+ 0x40 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x82 , 1 , 477 , 477 , // 476 - ky
+ 0x440 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x82 , 1 , 477 , 477 , // 477 - ky-kg
+ 0x76 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x989e, 1 , 479 , 143 , // 478 - la
+ 0x476 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x989e, 1 , 479 , 143 , // 479 - la-001
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 481 , 240 , // 480 - lag
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 481 , 240 , // 481 - lag-tz
+ 0x6e , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x93 , 1 , 483 , 483 , // 482 - lb
+ 0x46e , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x93 , 1 , 483 , 483 , // 483 - lb-lu
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 485 , 240 , // 484 - lg
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 485 , 240 , // 485 - lg-ug
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 487 , 240 , // 486 - lkt
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 487 , 240 , // 487 - lkt-us
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2c , 1 , 490 , 240 , // 488 - ln
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9 , 1 , 489 , 240 , // 489 - ln-ao
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2c , 1 , 490 , 240 , // 490 - ln-cd
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x37 , 1 , 491 , 240 , // 491 - ln-cf
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2b , 1 , 492 , 240 , // 492 - ln-cg
+ 0x54 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8a , 1 , 494 , 143 , // 493 - lo
+ 0x454 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8a , 1 , 494 , 143 , // 494 - lo-la
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 2 , 497 , 240 , // 495 - lrc
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x79 , 2 , 496 , 240 , // 496 - lrc-iq
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 2 , 497 , 240 , // 497 - lrc-ir
+ 0x27 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x8d , 1 , 499 , 499 , // 498 - lt
+ 0x427 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x8d , 1 , 499 , 499 , // 499 - lt-lt
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2c , 1 , 501 , 240 , // 500 - lu
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2c , 1 , 501 , 240 , // 501 - lu-cd
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 503 , 240 , // 502 - luo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 503 , 240 , // 503 - luo-ke
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 505 , 240 , // 504 - luy
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 505 , 240 , // 505 - luy-ke
+ 0x26 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x8c , 1 , 507 , 507 , // 506 - lv
+ 0x426 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x8c , 1 , 507 , 507 , // 507 - lv-lv
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 509 , 240 , // 508 - mas
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 509 , 240 , // 509 - mas-ke
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 510 , 240 , // 510 - mas-tz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 512 , 240 , // 511 - mer
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 512 , 240 , // 512 - mer-ke
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa0 , 1 , 514 , 240 , // 513 - mfe
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa0 , 1 , 514 , 240 , // 514 - mfe-mu
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x95 , 1 , 516 , 240 , // 515 - mg
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x95 , 1 , 516 , 240 , // 516 - mg-mg
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa8 , 1 , 518 , 240 , // 517 - mgh
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa8 , 1 , 518 , 240 , // 518 - mgh-mz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 520 , 240 , // 519 - mgo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 520 , 240 , // 520 - mgo-cm
+ 0x81 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb7 , 1 , 522 , 522 , // 521 - mi
+ 0x481 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb7 , 1 , 522 , 522 , // 522 - mi-nz
+ 0x2f , 0x4e3 , 0x362 , 0x2717, 0x1f4 , 0x4ca2, 1 , 524 , 524 , // 523 - mk
+ 0x42f , 0x4e3 , 0x362 , 0x2717, 0x1f4 , 0x4ca2, 1 , 524 , 524 , // 524 - mk-mk
+ 0x4c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 526 , 143 , // 525 - ml
+ 0x44c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 526 , 143 , // 526 - ml-in
+ 0x50 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x9a , 1 , 529 , 529 , // 527 - mn
+ 0x7850 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x9a , 1 , 529 , 529 , // 528 - mn-cyrl
+ 0x450 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x9a , 1 , 529 , 529 , // 529 - mn-mn
+ 0x7c50 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 531 , 531 , // 530 - mn-mong
+ 0x850 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 531 , 531 , // 531 - mn-mong-cn
+ 0xc50 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9a , 1 , 532 , 532 , // 532 - mn-mong-mn
+ 0x58 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 534 , 187 , // 533 - mni
+ 0x458 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 534 , 187 , // 534 - mni-in
+ 0x7c , 0x4e4 , 0x352 , 0x2710, 0x25 , 0x27 , 1 , 536 , 240 , // 535 - moh
+ 0x47c , 0x4e4 , 0x352 , 0x2710, 0x25 , 0x27 , 1 , 536 , 240 , // 536 - moh-ca
+ 0x4e , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 538 , 143 , // 537 - mr
+ 0x44e , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 538 , 143 , // 538 - mr-in
+ 0x3e , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa7 , 1 , 541 , 541 , // 539 - ms
+ 0x83e , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x25 , 1 , 540 , 540 , // 540 - ms-bn
+ 0x43e , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa7 , 1 , 541 , 541 , // 541 - ms-my
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd7 , 1 , 542 , 240 , // 542 - ms-sg
+ 0x3a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa3 , 1 , 544 , 544 , // 543 - mt
+ 0x43a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa3 , 1 , 544 , 544 , // 544 - mt-mt
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 546 , 240 , // 545 - mua
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 546 , 240 , // 546 - mua-cm
+ 0x55 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x1b , 2 , 548 , 240 , // 547 - my
+ 0x455 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x1b , 2 , 548 , 240 , // 548 - my-mm
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 2 , 550 , 240 , // 549 - mzn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 2 , 550 , 240 , // 550 - mzn-ir
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xfe , 1 , 552 , 240 , // 551 - naq
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xfe , 1 , 552 , 240 , // 552 - naq-na
+ 0x7c14 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 554 , 554 , // 553 - nb
+ 0x414 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 554 , 554 , // 554 - nb-no
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xdc , 1 , 555 , 240 , // 555 - nb-sj
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 557 , 240 , // 556 - nd
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 557 , 240 , // 557 - nd-zw
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 559 , 240 , // 558 - nds
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 559 , 240 , // 559 - nds-de
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb0 , 1 , 560 , 240 , // 560 - nds-nl
+ 0x61 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb2 , 1 , 563 , 143 , // 561 - ne
+ 0x861 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 2 , 562 , 240 , // 562 - ne-in
+ 0x461 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb2 , 1 , 563 , 143 , // 563 - ne-np
+ 0x13 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb0 , 1 , 569 , 569 , // 564 - nl
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x12e , 1 , 565 , 240 , // 565 - nl-aw
+ 0x813 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15 , 1 , 566 , 566 , // 566 - nl-be
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x9a55d42, 1 , 567 , 240 , // 567 - nl-bq
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x111 , 1 , 568 , 240 , // 568 - nl-cw
+ 0x413 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb0 , 1 , 569 , 569 , // 569 - nl-nl
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb5 , 1 , 570 , 240 , // 570 - nl-sr
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x78f7, 1 , 571 , 240 , // 571 - nl-sx
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 573 , 240 , // 572 - nmg
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 573 , 240 , // 573 - nmg-cm
+ 0x7814 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 575 , 575 , // 574 - nn
+ 0x814 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 575 , 575 , // 575 - nn-no
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 577 , 240 , // 576 - nnh
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 577 , 240 , // 577 - nnh-cm
+ 0x14 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 554 , 554 , // 578 - no
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x64 , 2 , 580 , 143 , // 579 - nqo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x64 , 2 , 580 , 143 , // 580 - nqo-gn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 582 , 240 , // 581 - nr
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 582 , 240 , // 582 - nr-za
+ 0x6c , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 584 , 584 , // 583 - nso
+ 0x46c , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 584 , 584 , // 584 - nso-za
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x114 , 1 , 586 , 240 , // 585 - nus
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x114 , 1 , 586 , 240 , // 586 - nus-ss
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 588 , 240 , // 587 - nyn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 588 , 240 , // 588 - nyn-ug
+ 0x82 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 590 , 590 , // 589 - oc
+ 0x482 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 590 , 590 , // 590 - oc-fr
+ 0x72 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 592 , 240 , // 591 - om
+ 0x472 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 592 , 240 , // 592 - om-et
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 593 , 240 , // 593 - om-ke
+ 0x48 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 595 , 143 , // 594 - or
+ 0x448 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 595 , 143 , // 595 - or-in
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 597 , 240 , // 596 - os
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 597 , 240 , // 597 - os-ge
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 598 , 240 , // 598 - os-ru
+ 0x46 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 602 , 143 , // 599 - pa
+ 0x7c46 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 601 , 143 , // 600 - pa-arab
+ 0x846 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 601 , 143 , // 601 - pa-arab-pk
+ 0x446 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 602 , 143 , // 602 - pa-in
+ 0x79 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x993248, 1 , 604 , 145 , // 603 - pap
+ 0x479 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x993248, 1 , 604 , 145 , // 604 - pap-029
+ 0x15 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xbf , 1 , 606 , 606 , // 605 - pl
+ 0x415 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xbf , 1 , 606 , 606 , // 606 - pl-pl
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 608 , 240 , // 607 - prg
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 608 , 240 , // 608 - prg-001
+ 0x8c , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x3 , 2 , 610 , 143 , // 609 - prs
+ 0x48c , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x3 , 2 , 610 , 143 , // 610 - prs-af
+ 0x63 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3 , 2 , 612 , 143 , // 611 - ps
+ 0x463 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3 , 2 , 612 , 143 , // 612 - ps-af
+ 0x16 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x20 , 1 , 615 , 615 , // 613 - pt
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x9 , 1 , 614 , 240 , // 614 - pt-ao
+ 0x416 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x20 , 1 , 615 , 615 , // 615 - pt-br
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdf , 1 , 616 , 240 , // 616 - pt-ch
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x39 , 1 , 617 , 240 , // 617 - pt-cv
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x45 , 1 , 618 , 240 , // 618 - pt-gq
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc4 , 1 , 619 , 240 , // 619 - pt-gw
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x93 , 1 , 620 , 240 , // 620 - pt-lu
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x97 , 1 , 621 , 240 , // 621 - pt-mo
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa8 , 1 , 622 , 240 , // 622 - pt-mz
+ 0x816 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc1 , 1 , 623 , 623 , // 623 - pt-pt
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xe9 , 1 , 624 , 240 , // 624 - pt-st
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f60e7, 1 , 625 , 240 , // 625 - pt-tl
+ 0x901 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x7c , 1 , 626 , 190 , // 626 - qps-latn-x-sh
+ 0x501 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xf4 , 1 , 627 , 627 , // 627 - qps-ploc
+ 0x5fe , 0x3a4 , 0x3a4 , 0x2711, 0x4f42, 0x7a , 1 , 628 , 628 , // 628 - qps-ploca
+ 0x9ff , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xcd , 0 , 629 , 143 , // 629 - qps-plocm
+ 0x86 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x63 , 1 , 632 , 632 , // 630 - quc
+ 0x7c86 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x63 , 1 , 632 , 632 , // 631 - quc-latn
+ 0x486 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x63 , 1 , 632 , 632 , // 632 - quc-latn-gt
+ 0x6b , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x1a , 1 , 634 , 634 , // 633 - quz
+ 0x46b , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x1a , 1 , 634 , 634 , // 634 - quz-bo
+ 0x86b , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x42 , 1 , 635 , 635 , // 635 - quz-ec
+ 0xc6b , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xbb , 1 , 636 , 636 , // 636 - quz-pe
+ 0x17 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0xdf , 1 , 638 , 638 , // 637 - rm
+ 0x417 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0xdf , 1 , 638 , 638 , // 638 - rm-ch
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x26 , 1 , 640 , 240 , // 639 - rn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x26 , 1 , 640 , 240 , // 640 - rn-bi
+ 0x18 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xc8 , 1 , 643 , 643 , // 641 - ro
+ 0x818 , 0x4e2 , 0x354 , 0x2 , 0x1f4 , 0x98 , 1 , 642 , 240 , // 642 - ro-md
+ 0x418 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xc8 , 1 , 643 , 643 , // 643 - ro-ro
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 645 , 240 , // 644 - rof
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 645 , 240 , // 645 - rof-tz
+ 0x19 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 651 , 651 , // 646 - ru
+ 0x1000 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0x1d , 1 , 647 , 240 , // 647 - ru-by
+ 0x1000 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0x82 , 1 , 648 , 240 , // 648 - ru-kg
+ 0x1000 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0x89 , 1 , 649 , 240 , // 649 - ru-kz
+ 0x819 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0x98 , 1 , 650 , 240 , // 650 - ru-md
+ 0x419 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 651 , 651 , // 651 - ru-ru
+ 0x1000 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0xf1 , 1 , 652 , 240 , // 652 - ru-ua
+ 0x87 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xcc , 1 , 654 , 654 , // 653 - rw
+ 0x487 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xcc , 1 , 654 , 654 , // 654 - rw-rw
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 656 , 240 , // 655 - rwk
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 656 , 240 , // 656 - rwk-tz
+ 0x4f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 658 , 143 , // 657 - sa
+ 0x44f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 658 , 143 , // 658 - sa-in
+ 0x85 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 660 , 660 , // 659 - sah
+ 0x485 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 660 , 660 , // 660 - sah-ru
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 662 , 240 , // 661 - saq
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 662 , 240 , // 662 - saq-ke
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 664 , 240 , // 663 - sbp
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 664 , 240 , // 664 - sbp-tz
+ 0x59 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 667 , 143 , // 665 - sd
+ 0x7c59 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 667 , 143 , // 666 - sd-arab
+ 0x859 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 667 , 143 , // 667 - sd-arab-pk
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 669 , 187 , // 668 - sd-deva
+ 0x459 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 669 , 187 , // 669 - sd-deva-in
+ 0x3b , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 672 , 672 , // 670 - se
+ 0xc3b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 671 , 671 , // 671 - se-fi
+ 0x43b , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 672 , 672 , // 672 - se-no
+ 0x83b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 673 , 673 , // 673 - se-se
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa8 , 1 , 675 , 240 , // 674 - seh
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa8 , 1 , 675 , 240 , // 675 - seh-mz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 677 , 240 , // 676 - ses
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 677 , 240 , // 677 - ses-ml
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x37 , 1 , 679 , 240 , // 678 - sg
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x37 , 1 , 679 , 240 , // 679 - sg-cf
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 684 , 240 , // 680 - shi
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 682 , 240 , // 681 - shi-latn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 682 , 240 , // 682 - shi-latn-ma
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 684 , 240 , // 683 - shi-tfng
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 684 , 240 , // 684 - shi-tfng-ma
+ 0x5b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2a , 1 , 686 , 143 , // 685 - si
+ 0x45b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2a , 1 , 686 , 143 , // 686 - si-lk
+ 0x1b , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x8f , 1 , 688 , 688 , // 687 - sk
+ 0x41b , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x8f , 1 , 688 , 688 , // 688 - sk-sk
+ 0x24 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xd4 , 1 , 690 , 690 , // 689 - sl
+ 0x424 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xd4 , 1 , 690 , 690 , // 690 - sl-si
+ 0x783b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 693 , 693 , // 691 - sma
+ 0x183b , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 692 , 692 , // 692 - sma-no
+ 0x1c3b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 693 , 693 , // 693 - sma-se
+ 0x7c3b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 696 , 696 , // 694 - smj
+ 0x103b , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 695 , 695 , // 695 - smj-no
+ 0x143b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 696 , 696 , // 696 - smj-se
+ 0x703b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 698 , 698 , // 697 - smn
+ 0x243b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 698 , 698 , // 698 - smn-fi
+ 0x743b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 700 , 700 , // 699 - sms
+ 0x203b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 700 , 700 , // 700 - sms-fi
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 703 , 240 , // 701 - sn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 703 , 240 , // 702 - sn-latn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 703 , 240 , // 703 - sn-latn-zw
+ 0x77 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd8 , 1 , 708 , 240 , // 704 - so
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3e , 1 , 705 , 240 , // 705 - so-dj
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 706 , 240 , // 706 - so-et
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 707 , 240 , // 707 - so-ke
+ 0x477 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd8 , 1 , 708 , 240 , // 708 - so-so
+ 0x1c , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x6 , 1 , 710 , 710 , // 709 - sq
+ 0x41c , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x6 , 1 , 710 , 710 , // 710 - sq-al
+ 0x1000 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x4ca2, 1 , 711 , 240 , // 711 - sq-mk
+ 0x1000 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x974941, 1 , 712 , 240 , // 712 - sq-xk
+ 0x7c1a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10f , 1 , 724 , 724 , // 713 - sr
+ 0x6c1a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x10f , 1 , 718 , 718 , // 714 - sr-cyrl
+ 0x1c1a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x19 , 1 , 715 , 715 , // 715 - sr-cyrl-ba
+ 0xc1a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x10d , 1 , 716 , 716 , // 716 - sr-cyrl-cs
+ 0x301a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x10e , 1 , 717 , 717 , // 717 - sr-cyrl-me
+ 0x281a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x10f , 1 , 718 , 718 , // 718 - sr-cyrl-rs
+ 0x1000 , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x974941, 1 , 719 , 240 , // 719 - sr-cyrl-xk
+ 0x701a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10f , 1 , 724 , 724 , // 720 - sr-latn
+ 0x181a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 721 , 721 , // 721 - sr-latn-ba
+ 0x81a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10d , 1 , 722 , 722 , // 722 - sr-latn-cs
+ 0x2c1a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10e , 1 , 723 , 723 , // 723 - sr-latn-me
+ 0x241a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10f , 1 , 724 , 724 , // 724 - sr-latn-rs
+ 0x1000 , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x974941, 1 , 725 , 240 , // 725 - sr-latn-xk
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 728 , 240 , // 726 - ss
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x104 , 1 , 727 , 240 , // 727 - ss-sz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 728 , 240 , // 728 - ss-za
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 730 , 240 , // 729 - ssy
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 730 , 240 , // 730 - ssy-er
+ 0x30 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 733 , 240 , // 731 - st
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x92 , 1 , 732 , 240 , // 732 - st-ls
+ 0x430 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 733 , 240 , // 733 - st-za
+ 0x1d , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 737 , 737 , // 734 - sv
+ 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x9906f5, 1 , 735 , 240 , // 735 - sv-ax
+ 0x81d , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 736 , 736 , // 736 - sv-fi
+ 0x41d , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 737 , 737 , // 737 - sv-se
+ 0x41 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x81 , 1 , 740 , 740 , // 738 - sw
+ 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x2c , 1 , 739 , 740 , // 739 - sw-cd
+ 0x441 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x81 , 1 , 740 , 740 , // 740 - sw-ke
+ 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xef , 1 , 741 , 240 , // 741 - sw-tz
+ 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xf0 , 1 , 742 , 240 , // 742 - sw-ug
+ 0x1000 , 0x0 , 0x1 , 0x0 , 0x1f4 , 0x2c , 1 , 744 , 240 , // 743 - swc
+ 0x1000 , 0x0 , 0x1 , 0x0 , 0x1f4 , 0x2c , 1 , 744 , 240 , // 744 - swc-cd
+ 0x5a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xde , 1 , 746 , 143 , // 745 - syr
+ 0x45a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xde , 1 , 746 , 143 , // 746 - syr-sy
+ 0x49 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 748 , 143 , // 747 - ta
+ 0x449 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 748 , 143 , // 748 - ta-in
+ 0x849 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2a , 1 , 749 , 143 , // 749 - ta-lk
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa7 , 1 , 750 , 240 , // 750 - ta-my
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd7 , 1 , 751 , 240 , // 751 - ta-sg
+ 0x4a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 753 , 143 , // 752 - te
+ 0x44a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 753 , 143 , // 753 - te-in
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 756 , 240 , // 754 - teo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 755 , 240 , // 755 - teo-ke
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 756 , 240 , // 756 - teo-ug
+ 0x28 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xe4 , 1 , 759 , 759 , // 757 - tg
+ 0x7c28 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xe4 , 1 , 759 , 759 , // 758 - tg-cyrl
+ 0x428 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xe4 , 1 , 759 , 759 , // 759 - tg-cyrl-tj
+ 0x1e , 0x36a , 0x36a , 0x2725, 0x5166, 0xe3 , 1 , 761 , 143 , // 760 - th
+ 0x41e , 0x36a , 0x36a , 0x2725, 0x5166, 0xe3 , 1 , 761 , 143 , // 761 - th-th
+ 0x73 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 763 , 143 , // 762 - ti
+ 0x873 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 763 , 143 , // 763 - ti-er
+ 0x473 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 764 , 143 , // 764 - ti-et
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 766 , 240 , // 765 - tig
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 766 , 240 , // 766 - tig-er
+ 0x42 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xee , 1 , 768 , 768 , // 767 - tk
+ 0x442 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xee , 1 , 768 , 768 , // 768 - tk-tm
+ 0x32 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 771 , 771 , // 769 - tn
+ 0x832 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x13 , 1 , 770 , 770 , // 770 - tn-bw
+ 0x432 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 771 , 771 , // 771 - tn-za
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xe7 , 1 , 773 , 240 , // 772 - to
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xe7 , 1 , 773 , 240 , // 773 - to-to
+ 0x1f , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0xeb , 1 , 776 , 776 , // 774 - tr
+ 0x1000 , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0x3b , 1 , 775 , 240 , // 775 - tr-cy
+ 0x41f , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0xeb , 1 , 776 , 776 , // 776 - tr-tr
+ 0x31 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 778 , 240 , // 777 - ts
+ 0x431 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 778 , 240 , // 778 - ts-za
+ 0x44 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 780 , 780 , // 779 - tt
+ 0x444 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 780 , 780 , // 780 - tt-ru
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xad , 1 , 782 , 240 , // 781 - twq
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xad , 1 , 782 , 240 , // 782 - twq-ne
+ 0x5f , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x4 , 1 , 787 , 787 , // 783 - tzm
+ 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x9f , 1 , 785 , 240 , // 784 - tzm-arab
+ 0x45f , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x9f , 1 , 785 , 240 , // 785 - tzm-arab-ma
+ 0x7c5f , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x4 , 1 , 787 , 787 , // 786 - tzm-latn
+ 0x85f , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x4 , 1 , 787 , 787 , // 787 - tzm-latn-dz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 788 , 240 , // 788 - tzm-latn-ma
+ 0x785f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 790 , 316 , // 789 - tzm-tfng
+ 0x105f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 790 , 316 , // 790 - tzm-tfng-ma
+ 0x80 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x2d , 1 , 792 , 143 , // 791 - ug
+ 0x480 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x2d , 1 , 792 , 143 , // 792 - ug-cn
+ 0x22 , 0x4e3 , 0x362 , 0x2721, 0x1f4 , 0xf1 , 1 , 794 , 794 , // 793 - uk
+ 0x422 , 0x4e3 , 0x362 , 0x2721, 0x1f4 , 0xf1 , 1 , 794 , 794 , // 794 - uk-ua
+ 0x20 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 1 , 797 , 143 , // 795 - ur
+ 0x820 , 0x4e8 , 0x2d0 , 0x2 , 0x1f4 , 0x71 , 2 , 796 , 240 , // 796 - ur-in
+ 0x420 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 1 , 797 , 143 , // 797 - ur-pk
+ 0x43 , 0x4e6 , 0x359 , 0x272d, 0x1f4 , 0xf7 , 1 , 804 , 804 , // 798 - uz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3 , 2 , 800 , 240 , // 799 - uz-arab
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3 , 2 , 800 , 240 , // 800 - uz-arab-af
+ 0x7843 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xf7 , 1 , 802 , 802 , // 801 - uz-cyrl
+ 0x843 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xf7 , 1 , 802 , 802 , // 802 - uz-cyrl-uz
+ 0x7c43 , 0x4e6 , 0x359 , 0x272d, 0x1f4 , 0xf7 , 1 , 804 , 804 , // 803 - uz-latn
+ 0x443 , 0x4e6 , 0x359 , 0x272d, 0x1f4 , 0xf7 , 1 , 804 , 804 , // 804 - uz-latn-uz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 809 , 240 , // 805 - vai
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 807 , 240 , // 806 - vai-latn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 807 , 240 , // 807 - vai-latn-lr
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 809 , 240 , // 808 - vai-vaii
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 809 , 240 , // 809 - vai-vaii-lr
+ 0x33 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 811 , 240 , // 810 - ve
+ 0x433 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 811 , 240 , // 811 - ve-za
+ 0x2a , 0x4ea , 0x4ea , 0x2710, 0x1f4 , 0xfb , 1 , 813 , 143 , // 812 - vi
+ 0x42a , 0x4ea , 0x4ea , 0x2710, 0x1f4 , 0xfb , 1 , 813 , 143 , // 813 - vi-vn
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 815 , 240 , // 814 - vo
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 815 , 240 , // 815 - vo-001
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 817 , 240 , // 816 - vun
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 817 , 240 , // 817 - vun-tz
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdf , 1 , 819 , 240 , // 818 - wae
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdf , 1 , 819 , 240 , // 819 - wae-ch
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 821 , 240 , // 820 - wal
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 821 , 240 , // 821 - wal-et
+ 0x88 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 823 , 823 , // 822 - wo
+ 0x488 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 823 , 823 , // 823 - wo-sn
+ 0x1007f, 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , -1 , -1 , // 824 - x-iv_mathan
+ 0x34 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 826 , 826 , // 825 - xh
+ 0x434 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 826 , 826 , // 826 - xh-za
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 828 , 240 , // 827 - xog
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 828 , 240 , // 828 - xog-ug
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 830 , 240 , // 829 - yav
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 830 , 240 , // 830 - yav-cm
+ 0x3d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 832 , 240 , // 831 - yi
+ 0x43d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 832 , 240 , // 832 - yi-001
+ 0x6a , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 835 , 835 , // 833 - yo
+ 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x1c , 1 , 834 , 240 , // 834 - yo-bj
+ 0x46a , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 835 , 835 , // 835 - yo-ng
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x68 , 1 , 837 , 240 , // 836 - yue
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x68 , 1 , 837 , 240 , // 837 - yue-hk
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 840 , 316 , // 838 - zgh
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 840 , 316 , // 839 - zgh-tfng
+ 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 840 , 316 , // 840 - zgh-tfng-ma
+ 0x7804 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 841 - zh
+ 0x4 , 0x3a8 , 0x3a8 , 0x0 , 0x1f4 , 0x2d , 1 , 844 , 844 , // 842 - zh-chs
+ 0x7c04 , 0x3b6 , 0x3b6 , 0x0 , 0x1f4 , 0x68 , 1 , 851 , 851 , // 843 - zh-cht
+ 0x804 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 844 - zh-cn
+ 0x50804, 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 845 - zh-cn_phoneb
+ 0x20804, 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 846 - zh-cn_stroke
+ 0x4 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 847 - zh-hans
+ 0x1000 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x68 , 1 , 848 , 240 , // 848 - zh-hans-hk
+ 0x1000 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x97 , 1 , 849 , 240 , // 849 - zh-hans-mo
+ 0x7c04 , 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x68 , 1 , 851 , 851 , // 850 - zh-hant
+ 0xc04 , 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x68 , 1 , 851 , 851 , // 851 - zh-hk
+ 0x40c04, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x68 , 1 , 851 , 851 , // 852 - zh-hk_radstr
+ 0x1404 , 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x97 , 1 , 853 , 853 , // 853 - zh-mo
+ 0x41404, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x97 , 1 , 853 , 853 , // 854 - zh-mo_radstr
+ 0x21404, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x97 , 1 , 853 , 853 , // 855 - zh-mo_stroke
+ 0x1004 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0xd7 , 1 , 856 , 856 , // 856 - zh-sg
+ 0x51004, 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0xd7 , 1 , 856 , 856 , // 857 - zh-sg_phoneb
+ 0x21004, 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0xd7 , 1 , 856 , 856 , // 858 - zh-sg_stroke
+ 0x404 , 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0xed , 1 , 859 , 859 , // 859 - zh-tw
+ 0x30404, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0xed , 1 , 859 , 859 , // 860 - zh-tw_pronun
+ 0x40404, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0xed , 1 , 859 , 859 , // 861 - zh-tw_radstr
+ 0x35 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 863 , 863 , // 862 - zu
+ 0x435 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 863 , 863 , // 863 - zu-za
+ };
+
+ // s_lcids list all supported lcids. used to binary search and we use the index of the matched lcid to
+ // get the index in s_localeNamesIndices using s_lcidToCultureNameIndices
+ private static readonly int[] s_lcids = new int[]
+ {
+ // Lcid , index - index in c_localeNames
+ 0x1 , // 0 - 52
+ 0x2 , // 1 - 301
+ 0x3 , // 2 - 421
+ 0x4 , // 3 - 4139
+ 0x5 , // 4 - 502
+ 0x6 , // 5 - 523
+ 0x7 , // 6 - 544
+ 0x8 , // 7 - 664
+ 0x9 , // 8 - 676
+ 0xa , // 9 - 1214
+ 0xb , // 10 - 1423
+ 0xc , // 11 - 1451
+ 0xd , // 12 - 1825
+ 0xe , // 13 - 1860
+ 0xf , // 14 - 1929
+ 0x10 , // 15 - 1936
+ 0x11 , // 16 - 1989
+ 0x12 , // 17 - 2179
+ 0x13 , // 18 - 2685
+ 0x14 , // 19 - 2747
+ 0x15 , // 20 - 2864
+ 0x16 , // 21 - 2897
+ 0x17 , // 22 - 3041
+ 0x18 , // 23 - 3055
+ 0x19 , // 24 - 3076
+ 0x1a , // 25 - 1839
+ 0x1b , // 26 - 3284
+ 0x1c , // 27 - 3387
+ 0x1d , // 28 - 3553
+ 0x1e , // 29 - 3673
+ 0x1f , // 30 - 3727
+ 0x20 , // 31 - 3847
+ 0x21 , // 32 - 1908
+ 0x22 , // 33 - 3840
+ 0x23 , // 34 - 276
+ 0x24 , // 35 - 3291
+ 0x25 , // 36 - 1354
+ 0x26 , // 37 - 2429
+ 0x27 , // 38 - 2397
+ 0x28 , // 39 - 3654
+ 0x29 , // 40 - 1377
+ 0x2a , // 41 - 3960
+ 0x2b , // 42 - 1879
+ 0x2c , // 43 - 224
+ 0x2d , // 44 - 1361
+ 0x2e , // 45 - 1851
+ 0x2f , // 46 - 2501
+ 0x30 , // 47 - 3541
+ 0x31 , // 48 - 3739
+ 0x32 , // 49 - 3708
+ 0x33 , // 50 - 3953
+ 0x34 , // 51 - 4020
+ 0x35 , // 52 - 4277
+ 0x36 , // 53 - 17
+ 0x37 , // 54 - 2062
+ 0x38 , // 55 - 1439
+ 0x39 , // 56 - 1832
+ 0x3a , // 57 - 2598
+ 0x3b , // 58 - 3194
+ 0x3c , // 59 - 1705
+ 0x3d , // 60 - 4045
+ 0x3e , // 61 - 2581
+ 0x3f , // 62 - 2133
+ 0x40 , // 63 - 2306
+ 0x41 , // 64 - 3570
+ 0x42 , // 65 - 3701
+ 0x43 , // 66 - 3859
+ 0x44 , // 67 - 3746
+ 0x45 , // 68 - 336
+ 0x46 , // 69 - 2830
+ 0x47 , // 70 - 1754
+ 0x48 , // 71 - 2811
+ 0x49 , // 72 - 3610
+ 0x4a , // 73 - 3632
+ 0x4b , // 74 - 2172
+ 0x4c , // 75 - 2508
+ 0x4d , // 76 - 199
+ 0x4e , // 77 - 2574
+ 0x4f , // 78 - 3124
+ 0x50 , // 79 - 2515
+ 0x51 , // 80 - 348
+ 0x52 , // 81 - 516
+ 0x53 , // 82 - 2165
+ 0x54 , // 83 - 2375
+ 0x55 , // 84 - 2614
+ 0x56 , // 85 - 1719
+ 0x57 , // 86 - 2191
+ 0x58 , // 87 - 2556
+ 0x59 , // 88 - 3158
+ 0x5a , // 89 - 3601
+ 0x5b , // 90 - 3277
+ 0x5c , // 91 - 473
+ 0x5d , // 92 - 1953
+ 0x5e , // 93 - 45
+ 0x5f , // 94 - 3762
+ 0x60 , // 95 - 2207
+ 0x61 , // 96 - 2673
+ 0x62 , // 97 - 1698
+ 0x63 , // 98 - 2890
+ 0x64 , // 99 - 1430
+ 0x65 , // 100 - 620
+ 0x66 , // 101 - 308
+ 0x67 , // 102 - 1384
+ 0x68 , // 103 - 1777
+ 0x69 , // 104 - 1899
+ 0x6a , // 105 - 4053
+ 0x6b , // 106 - 3020
+ 0x6c , // 107 - 2765
+ 0x6d , // 108 - 260
+ 0x6e , // 109 - 2330
+ 0x6f , // 110 - 2149
+ 0x70 , // 111 - 1915
+ 0x71 , // 112 - 2200
+ 0x72 , // 113 - 2799
+ 0x73 , // 114 - 3680
+ 0x74 , // 115 - 1726
+ 0x75 , // 116 - 1816
+ 0x76 , // 117 - 2313
+ 0x77 , // 118 - 3365
+ 0x78 , // 119 - 1922
+ 0x79 , // 120 - 2854
+ 0x7a , // 121 - 190
+ 0x7c , // 122 - 2565
+ 0x7e , // 123 - 360
+ 0x80 , // 124 - 3833
+ 0x81 , // 125 - 2494
+ 0x82 , // 126 - 2792
+ 0x83 , // 127 - 495
+ 0x84 , // 128 - 1733
+ 0x85 , // 129 - 3131
+ 0x86 , // 130 - 2998
+ 0x87 , // 131 - 3108
+ 0x88 , // 132 - 4002
+ 0x8c , // 133 - 2881
+ 0x91 , // 134 - 1712
+ 0x92 , // 135 - 2270
+ 0x401 , // 136 - 150
+ 0x402 , // 137 - 303
+ 0x403 , // 138 - 428
+ 0x404 , // 139 - 4248
+ 0x405 , // 140 - 504
+ 0x406 , // 141 - 525
+ 0x407 , // 142 - 561
+ 0x408 , // 143 - 671
+ 0x409 , // 144 - 1161
+ 0x40a , // 145 - 1272
+ 0x40b , // 146 - 1425
+ 0x40c , // 147 - 1529
+ 0x40d , // 148 - 1827
+ 0x40e , // 149 - 1862
+ 0x40f , // 150 - 1931
+ 0x410 , // 151 - 1943
+ 0x411 , // 152 - 1991
+ 0x412 , // 153 - 2186
+ 0x413 , // 154 - 2707
+ 0x414 , // 155 - 2641
+ 0x415 , // 156 - 2866
+ 0x416 , // 157 - 2904
+ 0x417 , // 158 - 3043
+ 0x418 , // 159 - 3062
+ 0x419 , // 160 - 3098
+ 0x41a , // 161 - 1846
+ 0x41b , // 162 - 3286
+ 0x41c , // 163 - 3389
+ 0x41d , // 164 - 3565
+ 0x41e , // 165 - 3675
+ 0x41f , // 166 - 3734
+ 0x420 , // 167 - 3854
+ 0x421 , // 168 - 1910
+ 0x422 , // 169 - 3842
+ 0x423 , // 170 - 278
+ 0x424 , // 171 - 3293
+ 0x425 , // 172 - 1356
+ 0x426 , // 173 - 2431
+ 0x427 , // 174 - 2399
+ 0x428 , // 175 - 3663
+ 0x429 , // 176 - 1379
+ 0x42a , // 177 - 3962
+ 0x42b , // 178 - 1881
+ 0x42c , // 179 - 250
+ 0x42d , // 180 - 1363
+ 0x42e , // 181 - 1854
+ 0x42f , // 182 - 2503
+ 0x430 , // 183 - 3548
+ 0x431 , // 184 - 3741
+ 0x432 , // 185 - 3715
+ 0x433 , // 186 - 3955
+ 0x434 , // 187 - 4022
+ 0x435 , // 188 - 4279
+ 0x436 , // 189 - 24
+ 0x437 , // 190 - 2064
+ 0x438 , // 191 - 1446
+ 0x439 , // 192 - 1834
+ 0x43a , // 193 - 2600
+ 0x43b , // 194 - 3201
+ 0x43d , // 195 - 4047
+ 0x43e , // 196 - 2588
+ 0x43f , // 197 - 2135
+ 0x440 , // 198 - 2308
+ 0x441 , // 199 - 3577
+ 0x442 , // 200 - 3703
+ 0x443 , // 201 - 3902
+ 0x444 , // 202 - 3748
+ 0x445 , // 203 - 343
+ 0x446 , // 204 - 2849
+ 0x447 , // 205 - 1756
+ 0x448 , // 206 - 2813
+ 0x449 , // 207 - 3612
+ 0x44a , // 208 - 3634
+ 0x44b , // 209 - 2174
+ 0x44c , // 210 - 2510
+ 0x44d , // 211 - 201
+ 0x44e , // 212 - 2576
+ 0x44f , // 213 - 3126
+ 0x450 , // 214 - 2524
+ 0x451 , // 215 - 350
+ 0x452 , // 216 - 518
+ 0x453 , // 217 - 2167
+ 0x454 , // 218 - 2377
+ 0x455 , // 219 - 2616
+ 0x456 , // 220 - 1721
+ 0x457 , // 221 - 2194
+ 0x458 , // 222 - 2559
+ 0x459 , // 223 - 3184
+ 0x45a , // 224 - 3604
+ 0x45b , // 225 - 3279
+ 0x45c , // 226 - 484
+ 0x45d , // 227 - 1962
+ 0x45e , // 228 - 47
+ 0x45f , // 229 - 3773
+ 0x460 , // 230 - 2209
+ 0x461 , // 231 - 2680
+ 0x462 , // 232 - 1700
+ 0x463 , // 233 - 2892
+ 0x464 , // 234 - 1433
+ 0x465 , // 235 - 622
+ 0x466 , // 236 - 311
+ 0x467 , // 237 - 1418
+ 0x468 , // 238 - 1806
+ 0x469 , // 239 - 1902
+ 0x46a , // 240 - 4060
+ 0x46b , // 241 - 3023
+ 0x46c , // 242 - 2768
+ 0x46d , // 243 - 262
+ 0x46e , // 244 - 2332
+ 0x46f , // 245 - 2151
+ 0x470 , // 246 - 1917
+ 0x471 , // 247 - 2202
+ 0x472 , // 248 - 2801
+ 0x473 , // 249 - 3687
+ 0x474 , // 250 - 1728
+ 0x475 , // 251 - 1819
+ 0x476 , // 252 - 2315
+ 0x477 , // 253 - 3382
+ 0x478 , // 254 - 1924
+ 0x479 , // 255 - 2857
+ 0x47a , // 256 - 193
+ 0x47c , // 257 - 2568
+ 0x47e , // 258 - 362
+ 0x480 , // 259 - 3835
+ 0x481 , // 260 - 2496
+ 0x482 , // 261 - 2794
+ 0x483 , // 262 - 497
+ 0x484 , // 263 - 1742
+ 0x485 , // 264 - 3134
+ 0x486 , // 265 - 3009
+ 0x487 , // 266 - 3110
+ 0x488 , // 267 - 4004
+ 0x48c , // 268 - 2884
+ 0x491 , // 269 - 1714
+ 0x492 , // 270 - 2279
+ 0x501 , // 271 - 2972
+ 0x5fe , // 272 - 2980
+ 0x801 , // 273 - 95
+ 0x803 , // 274 - 433
+ 0x804 , // 275 - 4110
+ 0x807 , // 276 - 556
+ 0x809 , // 277 - 831
+ 0x80a , // 278 - 1299
+ 0x80c , // 279 - 1459
+ 0x810 , // 280 - 1938
+ 0x813 , // 281 - 2692
+ 0x814 , // 282 - 2733
+ 0x816 , // 283 - 2944
+ 0x818 , // 284 - 3057
+ 0x819 , // 285 - 3093
+ 0x81a , // 286 - 3480
+ 0x81d , // 287 - 3560
+ 0x820 , // 288 - 3849
+ 0x82c , // 289 - 233
+ 0x82e , // 290 - 605
+ 0x832 , // 291 - 3710
+ 0x83b , // 292 - 3206
+ 0x83c , // 293 - 1707
+ 0x83e , // 294 - 2583
+ 0x843 , // 295 - 3885
+ 0x845 , // 296 - 338
+ 0x846 , // 297 - 2839
+ 0x849 , // 298 - 3617
+ 0x850 , // 299 - 2536
+ 0x859 , // 300 - 3167
+ 0x85d , // 301 - 1979
+ 0x85f , // 302 - 3792
+ 0x860 , // 303 - 2233
+ 0x861 , // 304 - 2675
+ 0x867 , // 305 - 1403
+ 0x86b , // 306 - 3029
+ 0x873 , // 307 - 3682
+ 0x901 , // 308 - 2959
+ 0x9ff , // 309 - 2989
+ 0xc01 , // 310 - 80
+ 0xc04 , // 311 - 4173
+ 0xc07 , // 312 - 546
+ 0xc09 , // 313 - 716
+ 0xc0a , // 314 - 1267
+ 0xc0c , // 315 - 1484
+ 0xc1a , // 316 - 3423
+ 0xc3b , // 317 - 3196
+ 0xc50 , // 318 - 2546
+ 0xc51 , // 319 - 638
+ 0xc6b , // 320 - 3035
+ 0x1001 , // 321 - 120
+ 0x1004 , // 322 - 4219
+ 0x1007 , // 323 - 588
+ 0x1009 , // 324 - 756
+ 0x100a , // 325 - 1289
+ 0x100c , // 326 - 1504
+ 0x101a , // 327 - 1841
+ 0x103b , // 328 - 3316
+ 0x105f , // 329 - 3822
+ 0x1401 , // 330 - 75
+ 0x1404 , // 331 - 4190
+ 0x1407 , // 332 - 583
+ 0x1409 , // 333 - 1026
+ 0x140a , // 334 - 1247
+ 0x140c , // 335 - 1569
+ 0x141a , // 336 - 402
+ 0x143b , // 337 - 3322
+ 0x1801 , // 338 - 125
+ 0x1809 , // 339 - 881
+ 0x180a , // 340 - 1309
+ 0x180c , // 341 - 1579
+ 0x181a , // 342 - 3470
+ 0x183b , // 343 - 3301
+ 0x1c01 , // 344 - 180
+ 0x1c09 , // 345 - 1191
+ 0x1c0a , // 346 - 1257
+ 0x1c0c , // 347 - 1453
+ 0x1c1a , // 348 - 3413
+ 0x1c3b , // 349 - 3307
+ 0x2001 , // 350 - 135
+ 0x2009 , // 351 - 911
+ 0x200a , // 352 - 1349
+ 0x200c , // 353 - 1634
+ 0x201a , // 354 - 385
+ 0x203b , // 355 - 3340
+ 0x2401 , // 356 - 185
+ 0x2409 , // 357 - 684
+ 0x240a , // 358 - 1242
+ 0x240c , // 359 - 1489
+ 0x241a , // 360 - 3500
+ 0x243b , // 361 - 3331
+ 0x2801 , // 362 - 170
+ 0x2809 , // 363 - 751
+ 0x280a , // 364 - 1314
+ 0x280c , // 365 - 1649
+ 0x281a , // 366 - 3443
+ 0x2c01 , // 367 - 100
+ 0x2c09 , // 368 - 1136
+ 0x2c0a , // 369 - 1222
+ 0x2c0c , // 370 - 1514
+ 0x2c1a , // 371 - 3490
+ 0x3001 , // 372 - 115
+ 0x3009 , // 373 - 1201
+ 0x300a , // 374 - 1262
+ 0x300c , // 375 - 1509
+ 0x301a , // 376 - 3433
+ 0x3401 , // 377 - 110
+ 0x3409 , // 378 - 1036
+ 0x340a , // 379 - 1237
+ 0x340c , // 380 - 1594
+ 0x3801 , // 381 - 60
+ 0x3809 , // 382 - 876
+ 0x380a , // 383 - 1344
+ 0x380c , // 384 - 1574
+ 0x3c01 , // 385 - 65
+ 0x3c09 , // 386 - 871
+ 0x3c0a , // 387 - 1329
+ 0x3c0c , // 388 - 1559
+ 0x4001 , // 389 - 145
+ 0x4009 , // 390 - 896
+ 0x400a , // 391 - 1227
+ 0x4409 , // 392 - 991
+ 0x440a , // 393 - 1334
+ 0x4809 , // 394 - 1086
+ 0x480a , // 395 - 1294
+ 0x4c0a , // 396 - 1304
+ 0x500a , // 397 - 1324
+ 0x540a , // 398 - 1339
+ 0x580a , // 399 - 1216
+ 0x5c0a , // 400 - 1252
+ 0x641a , // 401 - 378
+ 0x681a , // 402 - 395
+ 0x6c1a , // 403 - 3406
+ 0x701a , // 404 - 3463
+ 0x703b , // 405 - 3328
+ 0x742c , // 406 - 226
+ 0x743b , // 407 - 3337
+ 0x7804 , // 408 - 4096
+ 0x7814 , // 409 - 2731
+ 0x781a , // 410 - 376
+ 0x782c , // 411 - 243
+ 0x783b , // 412 - 3298
+ 0x7843 , // 413 - 3878
+ 0x7850 , // 414 - 2517
+ 0x785d , // 415 - 1955
+ 0x785f , // 416 - 3814
+ 0x7c04 , // 417 - 4166
+ 0x7c14 , // 418 - 2639
+ 0x7c1a , // 419 - 3404
+ 0x7c28 , // 420 - 3656
+ 0x7c2e , // 421 - 602
+ 0x7c3b , // 422 - 3313
+ 0x7c43 , // 423 - 3895
+ 0x7c46 , // 424 - 2832
+ 0x7c50 , // 425 - 2529
+ 0x7c59 , // 426 - 3160
+ 0x7c5c , // 427 - 476
+ 0x7c5d , // 428 - 1972
+ 0x7c5f , // 429 - 3784
+ 0x7c67 , // 430 - 1396
+ 0x7c68 , // 431 - 1779
+ 0x7c86 , // 432 - 3001
+ 0x7c92 , // 433 - 2272
+ 0x1007f, // 434 - 4009
+ 0x10407, // 435 - 566
+ 0x1040e, // 436 - 1867
+ 0x10437, // 437 - 2069
+ 0x20804, // 438 - 4127
+ 0x21004, // 439 - 4236
+ 0x21404, // 440 - 4207
+ 0x30404, // 441 - 4253
+ 0x40404, // 442 - 4265
+ 0x40411, // 443 - 1996
+ 0x40c04, // 444 - 4178
+ 0x41404, // 445 - 4195
+ 0x50804, // 446 - 4115
+ 0x51004 // 447 - 4224
+ };
+ // each element in s_lcidToCultureNameIndices is index to s_localeNamesIndices
+ private static readonly int[] s_lcidToCultureNameIndices = new int[]
+ {
+ // Index to s_localeNamesIndices, index to this array - lcid - index to the c_localeNames
+ 13 , // 0 - 1 - 52
+ 64 , // 1 - 2 - 301
+ 88 , // 2 - 3 - 421
+ 847 , // 3 - 4 - 4139
+ 103 , // 4 - 5 - 502
+ 109 , // 5 - 6 - 523
+ 114 , // 6 - 7 - 544
+ 140 , // 7 - 8 - 664
+ 143 , // 8 - 9 - 676
+ 251 , // 9 - a - 1214
+ 293 , // 10 - b - 1423
+ 300 , // 11 - c - 1451
+ 377 , // 12 - d - 1825
+ 386 , // 13 - e - 1860
+ 402 , // 14 - f - 1929
+ 404 , // 15 - 10 - 1936
+ 413 , // 16 - 11 - 1989
+ 452 , // 17 - 12 - 2179
+ 564 , // 18 - 13 - 2685
+ 578 , // 19 - 14 - 2747
+ 605 , // 20 - 15 - 2864
+ 613 , // 21 - 16 - 2897
+ 637 , // 22 - 17 - 3041
+ 641 , // 23 - 18 - 3055
+ 646 , // 24 - 19 - 3076
+ 381 , // 25 - 1a - 1839
+ 687 , // 26 - 1b - 3284
+ 709 , // 27 - 1c - 3387
+ 734 , // 28 - 1d - 3553
+ 760 , // 29 - 1e - 3673
+ 774 , // 30 - 1f - 3727
+ 795 , // 31 - 20 - 3847
+ 396 , // 32 - 21 - 1908
+ 793 , // 33 - 22 - 3840
+ 58 , // 34 - 23 - 276
+ 689 , // 35 - 24 - 3291
+ 278 , // 36 - 25 - 1354
+ 506 , // 37 - 26 - 2429
+ 498 , // 38 - 27 - 2397
+ 757 , // 39 - 28 - 3654
+ 284 , // 40 - 29 - 1377
+ 812 , // 41 - 2a - 3960
+ 389 , // 42 - 2b - 1879
+ 49 , // 43 - 2c - 224
+ 280 , // 44 - 2d - 1361
+ 384 , // 45 - 2e - 1851
+ 523 , // 46 - 2f - 2501
+ 731 , // 47 - 30 - 3541
+ 777 , // 48 - 31 - 3739
+ 769 , // 49 - 32 - 3708
+ 810 , // 50 - 33 - 3953
+ 825 , // 51 - 34 - 4020
+ 862 , // 52 - 35 - 4277
+ 4 , // 53 - 36 - 17
+ 425 , // 54 - 37 - 2062
+ 297 , // 55 - 38 - 1439
+ 379 , // 56 - 39 - 1832
+ 543 , // 57 - 3a - 2598
+ 670 , // 58 - 3b - 3194
+ 352 , // 59 - 3c - 1705
+ 831 , // 60 - 3d - 4045
+ 539 , // 61 - 3e - 2581
+ 440 , // 62 - 3f - 2133
+ 476 , // 63 - 40 - 2306
+ 738 , // 64 - 41 - 3570
+ 767 , // 65 - 42 - 3701
+ 798 , // 66 - 43 - 3859
+ 779 , // 67 - 44 - 3746
+ 71 , // 68 - 45 - 336
+ 599 , // 69 - 46 - 2830
+ 364 , // 70 - 47 - 1754
+ 594 , // 71 - 48 - 2811
+ 747 , // 72 - 49 - 3610
+ 752 , // 73 - 4a - 3632
+ 450 , // 74 - 4b - 2172
+ 525 , // 75 - 4c - 2508
+ 43 , // 76 - 4d - 199
+ 537 , // 77 - 4e - 2574
+ 657 , // 78 - 4f - 3124
+ 527 , // 79 - 50 - 2515
+ 74 , // 80 - 51 - 348
+ 107 , // 81 - 52 - 516
+ 448 , // 82 - 53 - 2165
+ 493 , // 83 - 54 - 2375
+ 547 , // 84 - 55 - 2614
+ 356 , // 85 - 56 - 1719
+ 455 , // 86 - 57 - 2191
+ 533 , // 87 - 58 - 2556
+ 665 , // 88 - 59 - 3158
+ 745 , // 89 - 5a - 3601
+ 685 , // 90 - 5b - 3277
+ 98 , // 91 - 5c - 473
+ 408 , // 92 - 5d - 1953
+ 11 , // 93 - 5e - 45
+ 783 , // 94 - 5f - 3762
+ 459 , // 95 - 60 - 2207
+ 561 , // 96 - 61 - 2673
+ 350 , // 97 - 62 - 1698
+ 611 , // 98 - 63 - 2890
+ 295 , // 99 - 64 - 1430
+ 129 , // 100 - 65 - 620
+ 66 , // 101 - 66 - 308
+ 286 , // 102 - 67 - 1384
+ 370 , // 103 - 68 - 1777
+ 394 , // 104 - 69 - 1899
+ 833 , // 105 - 6a - 4053
+ 633 , // 106 - 6b - 3020
+ 583 , // 107 - 6c - 2765
+ 54 , // 108 - 6d - 260
+ 482 , // 109 - 6e - 2330
+ 444 , // 110 - 6f - 2149
+ 398 , // 111 - 70 - 1915
+ 457 , // 112 - 71 - 2200
+ 591 , // 113 - 72 - 2799
+ 762 , // 114 - 73 - 3680
+ 358 , // 115 - 74 - 1726
+ 375 , // 116 - 75 - 1816
+ 478 , // 117 - 76 - 2313
+ 704 , // 118 - 77 - 3365
+ 400 , // 119 - 78 - 1922
+ 603 , // 120 - 79 - 2854
+ 41 , // 121 - 7a - 190
+ 535 , // 122 - 7c - 2565
+ 77 , // 123 - 7e - 360
+ 791 , // 124 - 80 - 3833
+ 521 , // 125 - 81 - 2494
+ 589 , // 126 - 82 - 2792
+ 101 , // 127 - 83 - 495
+ 360 , // 128 - 84 - 1733
+ 659 , // 129 - 85 - 3131
+ 630 , // 130 - 86 - 2998
+ 653 , // 131 - 87 - 3108
+ 822 , // 132 - 88 - 4002
+ 609 , // 133 - 8c - 2881
+ 354 , // 134 - 91 - 1712
+ 470 , // 135 - 92 - 2270
+ 33 , // 136 - 401 - 150
+ 65 , // 137 - 402 - 303
+ 90 , // 138 - 403 - 428
+ 859 , // 139 - 404 - 4248
+ 104 , // 140 - 405 - 504
+ 110 , // 141 - 406 - 525
+ 118 , // 142 - 407 - 561
+ 142 , // 143 - 408 - 671
+ 240 , // 144 - 409 - 1161
+ 263 , // 145 - 40a - 1272
+ 294 , // 146 - 40b - 1425
+ 316 , // 147 - 40c - 1529
+ 378 , // 148 - 40d - 1827
+ 387 , // 149 - 40e - 1862
+ 403 , // 150 - 40f - 1931
+ 406 , // 151 - 410 - 1943
+ 414 , // 152 - 411 - 1991
+ 454 , // 153 - 412 - 2186
+ 569 , // 154 - 413 - 2707
+ 554 , // 155 - 414 - 2641
+ 606 , // 156 - 415 - 2866
+ 615 , // 157 - 416 - 2904
+ 638 , // 158 - 417 - 3043
+ 643 , // 159 - 418 - 3062
+ 651 , // 160 - 419 - 3098
+ 383 , // 161 - 41a - 1846
+ 688 , // 162 - 41b - 3286
+ 710 , // 163 - 41c - 3389
+ 737 , // 164 - 41d - 3565
+ 761 , // 165 - 41e - 3675
+ 776 , // 166 - 41f - 3734
+ 797 , // 167 - 420 - 3854
+ 397 , // 168 - 421 - 1910
+ 794 , // 169 - 422 - 3842
+ 59 , // 170 - 423 - 278
+ 690 , // 171 - 424 - 3293
+ 279 , // 172 - 425 - 1356
+ 507 , // 173 - 426 - 2431
+ 499 , // 174 - 427 - 2399
+ 759 , // 175 - 428 - 3663
+ 285 , // 176 - 429 - 1379
+ 813 , // 177 - 42a - 3962
+ 390 , // 178 - 42b - 1881
+ 53 , // 179 - 42c - 250
+ 281 , // 180 - 42d - 1363
+ 385 , // 181 - 42e - 1854
+ 524 , // 182 - 42f - 2503
+ 733 , // 183 - 430 - 3548
+ 778 , // 184 - 431 - 3741
+ 771 , // 185 - 432 - 3715
+ 811 , // 186 - 433 - 3955
+ 826 , // 187 - 434 - 4022
+ 863 , // 188 - 435 - 4279
+ 6 , // 189 - 436 - 24
+ 426 , // 190 - 437 - 2064
+ 299 , // 191 - 438 - 1446
+ 380 , // 192 - 439 - 1834
+ 544 , // 193 - 43a - 2600
+ 672 , // 194 - 43b - 3201
+ 832 , // 195 - 43d - 4047
+ 541 , // 196 - 43e - 2588
+ 441 , // 197 - 43f - 2135
+ 477 , // 198 - 440 - 2308
+ 740 , // 199 - 441 - 3577
+ 768 , // 200 - 442 - 3703
+ 804 , // 201 - 443 - 3902
+ 780 , // 202 - 444 - 3748
+ 73 , // 203 - 445 - 343
+ 602 , // 204 - 446 - 2849
+ 365 , // 205 - 447 - 1756
+ 595 , // 206 - 448 - 2813
+ 748 , // 207 - 449 - 3612
+ 753 , // 208 - 44a - 3634
+ 451 , // 209 - 44b - 2174
+ 526 , // 210 - 44c - 2510
+ 44 , // 211 - 44d - 201
+ 538 , // 212 - 44e - 2576
+ 658 , // 213 - 44f - 3126
+ 529 , // 214 - 450 - 2524
+ 75 , // 215 - 451 - 350
+ 108 , // 216 - 452 - 518
+ 449 , // 217 - 453 - 2167
+ 494 , // 218 - 454 - 2377
+ 548 , // 219 - 455 - 2616
+ 357 , // 220 - 456 - 1721
+ 456 , // 221 - 457 - 2194
+ 534 , // 222 - 458 - 2559
+ 669 , // 223 - 459 - 3184
+ 746 , // 224 - 45a - 3604
+ 686 , // 225 - 45b - 3279
+ 100 , // 226 - 45c - 484
+ 410 , // 227 - 45d - 1962
+ 12 , // 228 - 45e - 47
+ 785 , // 229 - 45f - 3773
+ 460 , // 230 - 460 - 2209
+ 563 , // 231 - 461 - 2680
+ 351 , // 232 - 462 - 1700
+ 612 , // 233 - 463 - 2892
+ 296 , // 234 - 464 - 1433
+ 130 , // 235 - 465 - 622
+ 67 , // 236 - 466 - 311
+ 292 , // 237 - 467 - 1418
+ 374 , // 238 - 468 - 1806
+ 395 , // 239 - 469 - 1902
+ 835 , // 240 - 46a - 4060
+ 634 , // 241 - 46b - 3023
+ 584 , // 242 - 46c - 2768
+ 55 , // 243 - 46d - 262
+ 483 , // 244 - 46e - 2332
+ 445 , // 245 - 46f - 2151
+ 399 , // 246 - 470 - 1917
+ 458 , // 247 - 471 - 2202
+ 592 , // 248 - 472 - 2801
+ 764 , // 249 - 473 - 3687
+ 359 , // 250 - 474 - 1728
+ 376 , // 251 - 475 - 1819
+ 479 , // 252 - 476 - 2315
+ 708 , // 253 - 477 - 3382
+ 401 , // 254 - 478 - 1924
+ 604 , // 255 - 479 - 2857
+ 42 , // 256 - 47a - 193
+ 536 , // 257 - 47c - 2568
+ 78 , // 258 - 47e - 362
+ 792 , // 259 - 480 - 3835
+ 522 , // 260 - 481 - 2496
+ 590 , // 261 - 482 - 2794
+ 102 , // 262 - 483 - 497
+ 362 , // 263 - 484 - 1742
+ 660 , // 264 - 485 - 3134
+ 632 , // 265 - 486 - 3009
+ 654 , // 266 - 487 - 3110
+ 823 , // 267 - 488 - 4004
+ 610 , // 268 - 48c - 2884
+ 355 , // 269 - 491 - 1714
+ 472 , // 270 - 492 - 2279
+ 627 , // 271 - 501 - 2972
+ 628 , // 272 - 5fe - 2980
+ 22 , // 273 - 801 - 95
+ 91 , // 274 - 803 - 433
+ 844 , // 275 - 804 - 4110
+ 117 , // 276 - 807 - 556
+ 174 , // 277 - 809 - 831
+ 267 , // 278 - 80a - 1299
+ 302 , // 279 - 80c - 1459
+ 405 , // 280 - 810 - 1938
+ 566 , // 281 - 813 - 2692
+ 575 , // 282 - 814 - 2733
+ 623 , // 283 - 816 - 2944
+ 642 , // 284 - 818 - 3057
+ 650 , // 285 - 819 - 3093
+ 722 , // 286 - 81a - 3480
+ 736 , // 287 - 81d - 3560
+ 796 , // 288 - 820 - 3849
+ 51 , // 289 - 82c - 233
+ 126 , // 290 - 82e - 605
+ 770 , // 291 - 832 - 3710
+ 673 , // 292 - 83b - 3206
+ 353 , // 293 - 83c - 1707
+ 540 , // 294 - 83e - 2583
+ 802 , // 295 - 843 - 3885
+ 72 , // 296 - 845 - 338
+ 601 , // 297 - 846 - 2839
+ 749 , // 298 - 849 - 3617
+ 531 , // 299 - 850 - 2536
+ 667 , // 300 - 859 - 3167
+ 412 , // 301 - 85d - 1979
+ 787 , // 302 - 85f - 3792
+ 463 , // 303 - 860 - 2233
+ 562 , // 304 - 861 - 2675
+ 290 , // 305 - 867 - 1403
+ 635 , // 306 - 86b - 3029
+ 763 , // 307 - 873 - 3682
+ 626 , // 308 - 901 - 2959
+ 629 , // 309 - 9ff - 2989
+ 19 , // 310 - c01 - 80
+ 851 , // 311 - c04 - 4173
+ 115 , // 312 - c07 - 546
+ 151 , // 313 - c09 - 716
+ 262 , // 314 - c0a - 1267
+ 307 , // 315 - c0c - 1484
+ 716 , // 316 - c1a - 3423
+ 671 , // 317 - c3b - 3196
+ 532 , // 318 - c50 - 2546
+ 134 , // 319 - c51 - 638
+ 636 , // 320 - c6b - 3035
+ 27 , // 321 - 1001 - 120
+ 856 , // 322 - 1004 - 4219
+ 122 , // 323 - 1007 - 588
+ 159 , // 324 - 1009 - 756
+ 265 , // 325 - 100a - 1289
+ 311 , // 326 - 100c - 1504
+ 382 , // 327 - 101a - 1841
+ 695 , // 328 - 103b - 3316
+ 790 , // 329 - 105f - 3822
+ 18 , // 330 - 1401 - 75
+ 853 , // 331 - 1404 - 4190
+ 121 , // 332 - 1407 - 583
+ 213 , // 333 - 1409 - 1026
+ 258 , // 334 - 140a - 1247
+ 324 , // 335 - 140c - 1569
+ 85 , // 336 - 141a - 402
+ 696 , // 337 - 143b - 3322
+ 28 , // 338 - 1801 - 125
+ 184 , // 339 - 1809 - 881
+ 269 , // 340 - 180a - 1309
+ 326 , // 341 - 180c - 1579
+ 721 , // 342 - 181a - 3470
+ 692 , // 343 - 183b - 3301
+ 39 , // 344 - 1c01 - 180
+ 246 , // 345 - 1c09 - 1191
+ 260 , // 346 - 1c0a - 1257
+ 301 , // 347 - 1c0c - 1453
+ 715 , // 348 - 1c1a - 3413
+ 693 , // 349 - 1c3b - 3307
+ 30 , // 350 - 2001 - 135
+ 190 , // 351 - 2009 - 911
+ 277 , // 352 - 200a - 1349
+ 337 , // 353 - 200c - 1634
+ 83 , // 354 - 201a - 385
+ 700 , // 355 - 203b - 3340
+ 40 , // 356 - 2401 - 185
+ 145 , // 357 - 2409 - 684
+ 257 , // 358 - 240a - 1242
+ 308 , // 359 - 240c - 1489
+ 724 , // 360 - 241a - 3500
+ 698 , // 361 - 243b - 3331
+ 37 , // 362 - 2801 - 170
+ 158 , // 363 - 2809 - 751
+ 270 , // 364 - 280a - 1314
+ 340 , // 365 - 280c - 1649
+ 718 , // 366 - 281a - 3443
+ 23 , // 367 - 2c01 - 100
+ 235 , // 368 - 2c09 - 1136
+ 253 , // 369 - 2c0a - 1222
+ 313 , // 370 - 2c0c - 1514
+ 723 , // 371 - 2c1a - 3490
+ 26 , // 372 - 3001 - 115
+ 248 , // 373 - 3009 - 1201
+ 261 , // 374 - 300a - 1262
+ 312 , // 375 - 300c - 1509
+ 717 , // 376 - 301a - 3433
+ 25 , // 377 - 3401 - 110
+ 215 , // 378 - 3409 - 1036
+ 256 , // 379 - 340a - 1237
+ 329 , // 380 - 340c - 1594
+ 15 , // 381 - 3801 - 60
+ 183 , // 382 - 3809 - 876
+ 276 , // 383 - 380a - 1344
+ 325 , // 384 - 380c - 1574
+ 16 , // 385 - 3c01 - 65
+ 182 , // 386 - 3c09 - 871
+ 273 , // 387 - 3c0a - 1329
+ 322 , // 388 - 3c0c - 1559
+ 32 , // 389 - 4001 - 145
+ 187 , // 390 - 4009 - 896
+ 254 , // 391 - 400a - 1227
+ 206 , // 392 - 4409 - 991
+ 274 , // 393 - 440a - 1334
+ 225 , // 394 - 4809 - 1086
+ 266 , // 395 - 480a - 1294
+ 268 , // 396 - 4c0a - 1304
+ 272 , // 397 - 500a - 1324
+ 275 , // 398 - 540a - 1339
+ 252 , // 399 - 580a - 1216
+ 259 , // 400 - 5c0a - 1252
+ 82 , // 401 - 641a - 378
+ 84 , // 402 - 681a - 395
+ 714 , // 403 - 6c1a - 3406
+ 720 , // 404 - 701a - 3463
+ 697 , // 405 - 703b - 3328
+ 50 , // 406 - 742c - 226
+ 699 , // 407 - 743b - 3337
+ 841 , // 408 - 7804 - 4096
+ 574 , // 409 - 7814 - 2731
+ 81 , // 410 - 781a - 376
+ 52 , // 411 - 782c - 243
+ 691 , // 412 - 783b - 3298
+ 801 , // 413 - 7843 - 3878
+ 528 , // 414 - 7850 - 2517
+ 409 , // 415 - 785d - 1955
+ 789 , // 416 - 785f - 3814
+ 850 , // 417 - 7c04 - 4166
+ 553 , // 418 - 7c14 - 2639
+ 713 , // 419 - 7c1a - 3404
+ 758 , // 420 - 7c28 - 3656
+ 125 , // 421 - 7c2e - 602
+ 694 , // 422 - 7c3b - 3313
+ 803 , // 423 - 7c43 - 3895
+ 600 , // 424 - 7c46 - 2832
+ 530 , // 425 - 7c50 - 2529
+ 666 , // 426 - 7c59 - 3160
+ 99 , // 427 - 7c5c - 476
+ 411 , // 428 - 7c5d - 1972
+ 786 , // 429 - 7c5f - 3784
+ 289 , // 430 - 7c67 - 1396
+ 371 , // 431 - 7c68 - 1779
+ 631 , // 432 - 7c86 - 3001
+ 471 , // 433 - 7c92 - 2272
+ 824 , // 434 - 1007f - 4009
+ 119 , // 435 - 10407 - 566
+ 388 , // 436 - 1040e - 1867
+ 427 , // 437 - 10437 - 2069
+ 846 , // 438 - 20804 - 4127
+ 858 , // 439 - 21004 - 4236
+ 855 , // 440 - 21404 - 4207
+ 860 , // 441 - 30404 - 4253
+ 861 , // 442 - 40404 - 4265
+ 415 , // 443 - 40411 - 1996
+ 852 , // 444 - 40c04 - 4178
+ 854 , // 445 - 41404 - 4195
+ 845 , // 446 - 50804 - 4115
+ 857 // 447 - 51004 - 4224
+ };
+
+ internal static string LCIDToLocaleName(int culture)
+ {
+ int left = 0;
+ int right = s_lcids.Length - 1;
+ int index;
+
+ Debug.Assert(s_lcids.Length == s_lcidToCultureNameIndices.Length);
+
+ while (left <= right)
+ {
+ index = (right + left) / 2;
+
+ if (culture == s_lcids[index])
+ {
+ int indexToLocaleNamesIndices = s_lcidToCultureNameIndices[index];
+ Debug.Assert(indexToLocaleNamesIndices < s_localeNamesIndices.Length - 1);
+
+ return c_localeNames.Substring(s_localeNamesIndices[indexToLocaleNamesIndices],
+ s_localeNamesIndices[indexToLocaleNamesIndices + 1] -
+ s_localeNamesIndices[indexToLocaleNamesIndices]);
+ }
+ else if (culture < s_lcids[index])
+ {
+ right = index - 1;
+ }
+ else
+ {
+ left = index + 1;
+ }
+ }
+
+ return null;
+ }
+
+ internal static int GetLocaleDataNumericPart(string cultureName, LocaleDataParts part)
+ {
+ int index = SearchCultureName(cultureName);
+ if (index < 0)
+ {
+ return -1;
+ }
+
+ Debug.Assert((s_localeNamesIndices.Length-1 == (s_nameIndexToNumericData.Length/NUMERIC_LOCALE_DATA_COUNT_PER_ROW)) &&
+ index < s_localeNamesIndices.Length);
+
+ return s_nameIndexToNumericData[index * NUMERIC_LOCALE_DATA_COUNT_PER_ROW + (int) part];
+ }
+
+ internal static string GetThreeLetterWindowsLangageName(string cultureName)
+ {
+ int index = SearchCultureName(cultureName);
+ if (index < 0)
+ {
+ return null;
+ }
+
+ Debug.Assert(s_localeNamesIndices.Length-1 == (c_threeLetterWindowsLanguageName.Length / 3));
+ return c_threeLetterWindowsLanguageName.Substring(index * 3, 3);
+ }
+
+ internal static string GetLocaleDataMappedCulture(string cultureName, LocaleDataParts part)
+ {
+ int indexToIndicesTable = GetLocaleDataNumericPart(cultureName, part);
+ if (indexToIndicesTable < 0)
+ {
+ return ""; // fallback to invariant
+ }
+
+ Debug.Assert(indexToIndicesTable < s_localeNamesIndices.Length-1);
+
+ return c_localeNames.Substring(s_localeNamesIndices[indexToIndicesTable],
+ s_localeNamesIndices[indexToIndicesTable+1] - s_localeNamesIndices[indexToIndicesTable]);
+ }
+
+ internal static string GetSpecificCultureName(string cultureName)
+ {
+ return GetLocaleDataMappedCulture(cultureName, LocaleDataParts.SpecificLocaleIndex);
+ }
+
+ internal static string GetConsoleUICulture(string cultureName)
+ {
+ return GetLocaleDataMappedCulture(cultureName, LocaleDataParts.ConsoleLocaleIndex);
+ }
+
+ // SearchCultureName will binary search c_localeNames using s_localeNamesIndices.
+ // return index in s_localeNamesIndices, or -1 if it fail finding any match
+ private static int SearchCultureName(string name)
+ {
+ int left = 0;
+ int right = s_localeNamesIndices.Length - 2;
+ int index;
+ int result;
+
+ Debug.Assert(s_localeNamesIndices[s_localeNamesIndices.Length - 1] == c_localeNames.Length);
+
+ name = CultureData.AnsiToLower(name);
+
+ // Binary search the array until we have only a couple of elements left and then
+ // just walk those elements.
+ while ((right - left) > 3)
+ {
+ index = ((right - left) / 2) + left;
+
+ Debug.Assert(index < s_localeNamesIndices.Length - 1);
+ result = CompareOrdinal(name, c_localeNames, s_localeNamesIndices[index], s_localeNamesIndices[index + 1] - s_localeNamesIndices[index]);
+ if (result == 0)
+ {
+ return index;
+ }
+ else if (result < 0)
+ {
+ right = index;
+ }
+ else
+ {
+ left = index;
+ }
+ }
+
+ // Walk the remaining elements (it'll be 3 or fewer).
+ for (; left <= right; left++)
+ {
+ Debug.Assert(left < s_localeNamesIndices.Length - 1);
+ if (CompareOrdinal(name, c_localeNames, s_localeNamesIndices[left], s_localeNamesIndices[left + 1] - s_localeNamesIndices[left]) == 0)
+ {
+ return (left);
+ }
+ }
+
+ // couldn't find culture name
+ return -1;
+ }
+
+ // optimized to avoid parameters checking
+ private static int CompareOrdinal(string s1, string s2, int index, int length)
+ {
+ int count = s1.Length;
+ if (count > length)
+ count = length;
+
+ int i = 0;
+ while (i < count && s1[i] == s2[index + i])
+ i++;
+
+ if (i < count)
+ return (int)(s1[i] - s2[index + i]);
+
+ return s1.Length - length;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/Normalization.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/Normalization.Unix.cs
new file mode 100644
index 0000000000..443dbae530
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/Normalization.Unix.cs
@@ -0,0 +1,134 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.Globalization
+{
+ internal static partial class Normalization
+ {
+ internal static bool IsNormalized(string strInput, NormalizationForm normalizationForm)
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ // In Invariant mode we assume all characters are normalized.
+ // This is because we don't support any linguistic operation on the strings
+ return true;
+ }
+
+ ValidateArguments(strInput, normalizationForm);
+
+ int ret = Interop.Globalization.IsNormalized(normalizationForm, strInput, strInput.Length);
+
+ if (ret == -1)
+ {
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput));
+ }
+
+ return ret == 1;
+ }
+
+ internal static string Normalize(string strInput, NormalizationForm normalizationForm)
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ // In Invariant mode we assume all characters are normalized.
+ // This is because we don't support any linguistic operation on the strings
+ return strInput;
+ }
+
+ ValidateArguments(strInput, normalizationForm);
+
+ char[] buf = new char[strInput.Length];
+
+ for (int attempts = 2; attempts > 0; attempts--)
+ {
+ int realLen = Interop.Globalization.NormalizeString(normalizationForm, strInput, strInput.Length, buf, buf.Length);
+
+ if (realLen == -1)
+ {
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput));
+ }
+
+ if (realLen <= buf.Length)
+ {
+ return new string(buf, 0, realLen);
+ }
+
+ buf = new char[realLen];
+ }
+
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput));
+ }
+
+ // -----------------------------
+ // ---- PAL layer ends here ----
+ // -----------------------------
+
+ private static void ValidateArguments(string strInput, NormalizationForm normalizationForm)
+ {
+ Debug.Assert(strInput != null);
+
+ if (normalizationForm != NormalizationForm.FormC && normalizationForm != NormalizationForm.FormD &&
+ normalizationForm != NormalizationForm.FormKC && normalizationForm != NormalizationForm.FormKD)
+ {
+ throw new ArgumentException(SR.Argument_InvalidNormalizationForm, nameof(normalizationForm));
+ }
+
+ if (HasInvalidUnicodeSequence(strInput))
+ {
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput));
+ }
+ }
+
+ /// <summary>
+ /// ICU does not signal an error during normalization if the input string has invalid unicode,
+ /// unlike Windows (which uses the ERROR_NO_UNICODE_TRANSLATION error value to signal an error).
+ ///
+ /// We walk the string ourselves looking for these bad sequences so we can continue to throw
+ /// ArgumentException in these cases.
+ /// </summary>
+ private static bool HasInvalidUnicodeSequence(string s)
+ {
+ for (int i = 0; i < s.Length; i++)
+ {
+ char c = s[i];
+
+ if (c < '\ud800')
+ {
+ continue;
+ }
+
+ if (c == '\uFFFE')
+ {
+ return true;
+ }
+
+ // If we see low surrogate before a high one, the string is invalid.
+ if (char.IsLowSurrogate(c))
+ {
+ return true;
+ }
+
+ if (char.IsHighSurrogate(c))
+ {
+ if (i + 1 >= s.Length || !char.IsLowSurrogate(s[i + 1]))
+ {
+ // A high surrogate at the end of the string or a high surrogate
+ // not followed by a low surrogate
+ return true;
+ }
+ else
+ {
+ i++; // consume the low surrogate.
+ continue;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/Normalization.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/Normalization.Windows.cs
new file mode 100644
index 0000000000..8418c53b6c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/Normalization.Windows.cs
@@ -0,0 +1,148 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Globalization
+{
+ internal static partial class Normalization
+ {
+ internal static bool IsNormalized(string strInput, NormalizationForm normalizationForm)
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ // In Invariant mode we assume all characters are normalized.
+ // This is because we don't support any linguistic operation on the strings
+ return true;
+ }
+
+ Debug.Assert(strInput != null);
+
+ // The only way to know if IsNormalizedString failed is through checking the Win32 last error
+ // IsNormalizedString pinvoke has SetLastError attribute property which will set the last error
+ // to 0 (ERROR_SUCCESS) before executing the calls.
+ bool result = Interop.Normaliz.IsNormalizedString((int)normalizationForm, strInput, strInput.Length);
+
+ int lastError = Marshal.GetLastWin32Error();
+ switch (lastError)
+ {
+ case Interop.Errors.ERROR_SUCCESS:
+ break;
+
+ case Interop.Errors.ERROR_INVALID_PARAMETER:
+ case Interop.Errors.ERROR_NO_UNICODE_TRANSLATION:
+ if (normalizationForm != NormalizationForm.FormC &&
+ normalizationForm != NormalizationForm.FormD &&
+ normalizationForm != NormalizationForm.FormKC &&
+ normalizationForm != NormalizationForm.FormKD)
+ {
+ throw new ArgumentException(SR.Argument_InvalidNormalizationForm, nameof(normalizationForm));
+ }
+
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput));
+
+ case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY:
+ throw new OutOfMemoryException();
+
+ default:
+ throw new InvalidOperationException(SR.Format(SR.UnknownError_Num, lastError));
+ }
+
+ return result;
+ }
+
+ internal static string Normalize(string strInput, NormalizationForm normalizationForm)
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ // In Invariant mode we assume all characters are normalized.
+ // This is because we don't support any linguistic operation on the strings
+ return strInput;
+ }
+
+ Debug.Assert(strInput != null);
+
+ // we depend on Win32 last error when calling NormalizeString
+ // NormalizeString pinvoke has SetLastError attribute property which will set the last error
+ // to 0 (ERROR_SUCCESS) before executing the calls.
+
+ // Guess our buffer size first
+ int iLength = Interop.Normaliz.NormalizeString((int)normalizationForm, strInput, strInput.Length, null, 0);
+
+ int lastError = Marshal.GetLastWin32Error();
+ // Could have an error (actually it'd be quite hard to have an error here)
+ if ((lastError != Interop.Errors.ERROR_SUCCESS) || iLength < 0)
+ {
+ if (lastError == Interop.Errors.ERROR_INVALID_PARAMETER)
+ {
+ if (normalizationForm != NormalizationForm.FormC &&
+ normalizationForm != NormalizationForm.FormD &&
+ normalizationForm != NormalizationForm.FormKC &&
+ normalizationForm != NormalizationForm.FormKD)
+ {
+ throw new ArgumentException(SR.Argument_InvalidNormalizationForm, nameof(normalizationForm));
+ }
+
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput));
+ }
+
+ // We shouldn't really be able to get here..., guessing length is
+ // a trivial math function...
+ // Can't really be Out of Memory, but just in case:
+ if (lastError == Interop.Errors.ERROR_NOT_ENOUGH_MEMORY)
+ throw new OutOfMemoryException();
+
+ // Who knows what happened? Not us!
+ throw new InvalidOperationException(SR.Format(SR.UnknownError_Num, lastError));
+ }
+
+ // Don't break for empty strings (only possible for D & KD and not really possible at that)
+ if (iLength == 0) return string.Empty;
+
+ // Someplace to stick our buffer
+ char[] cBuffer = null;
+
+ for (;;)
+ {
+ // (re)allocation buffer and normalize string
+ cBuffer = new char[iLength];
+
+ // NormalizeString pinvoke has SetLastError attribute property which will set the last error
+ // to 0 (ERROR_SUCCESS) before executing the calls.
+ iLength = Interop.Normaliz.NormalizeString((int)normalizationForm, strInput, strInput.Length, cBuffer, cBuffer.Length);
+ lastError = Marshal.GetLastWin32Error();
+
+ if (lastError == Interop.Errors.ERROR_SUCCESS)
+ break;
+
+ // Could have an error (actually it'd be quite hard to have an error here)
+ switch (lastError)
+ {
+ // Do appropriate stuff for the individual errors:
+ case Interop.Errors.ERROR_INSUFFICIENT_BUFFER:
+ iLength = Math.Abs(iLength);
+ Debug.Assert(iLength > cBuffer.Length, "Buffer overflow should have iLength > cBuffer.Length");
+ continue;
+
+ case Interop.Errors.ERROR_INVALID_PARAMETER:
+ case Interop.Errors.ERROR_NO_UNICODE_TRANSLATION:
+ // Illegal code point or order found. Ie: FFFE or D800 D800, etc.
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput));
+
+ case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY:
+ throw new OutOfMemoryException();
+
+ default:
+ // We shouldn't get here...
+ throw new InvalidOperationException(SR.Format(SR.UnknownError_Num, lastError));
+ }
+ }
+
+ // Copy our buffer into our new string, which will be the appropriate size
+ return new string(cBuffer, 0, iLength);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/NumberFormatInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/NumberFormatInfo.cs
new file mode 100644
index 0000000000..a98023dc96
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/NumberFormatInfo.cs
@@ -0,0 +1,842 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+
+namespace System.Globalization
+{
+ //
+ // Property Default Description
+ // PositiveSign '+' Character used to indicate positive values.
+ // NegativeSign '-' Character used to indicate negative values.
+ // NumberDecimalSeparator '.' The character used as the decimal separator.
+ // NumberGroupSeparator ',' The character used to separate groups of
+ // digits to the left of the decimal point.
+ // NumberDecimalDigits 2 The default number of decimal places.
+ // NumberGroupSizes 3 The number of digits in each group to the
+ // left of the decimal point.
+ // NaNSymbol "NaN" The string used to represent NaN values.
+ // PositiveInfinitySymbol"Infinity" The string used to represent positive
+ // infinities.
+ // NegativeInfinitySymbol"-Infinity" The string used to represent negative
+ // infinities.
+ //
+ //
+ //
+ // Property Default Description
+ // CurrencyDecimalSeparator '.' The character used as the decimal
+ // separator.
+ // CurrencyGroupSeparator ',' The character used to separate groups
+ // of digits to the left of the decimal
+ // point.
+ // CurrencyDecimalDigits 2 The default number of decimal places.
+ // CurrencyGroupSizes 3 The number of digits in each group to
+ // the left of the decimal point.
+ // CurrencyPositivePattern 0 The format of positive values.
+ // CurrencyNegativePattern 0 The format of negative values.
+ // CurrencySymbol "$" String used as local monetary symbol.
+ //
+
+ sealed public class NumberFormatInfo : IFormatProvider, ICloneable
+ {
+ // invariantInfo is constant irrespective of your current culture.
+ private static volatile NumberFormatInfo s_invariantInfo;
+
+ // READTHIS READTHIS READTHIS
+ // This class has an exact mapping onto a native structure defined in COMNumber.cpp
+ // DO NOT UPDATE THIS WITHOUT UPDATING THAT STRUCTURE. IF YOU ADD BOOL, ADD THEM AT THE END.
+ // ALSO MAKE SURE TO UPDATE mscorlib.h in the VM directory to check field offsets.
+ // READTHIS READTHIS READTHIS
+ internal int[] numberGroupSizes = new int[] { 3 };
+ internal int[] currencyGroupSizes = new int[] { 3 };
+ internal int[] percentGroupSizes = new int[] { 3 };
+ internal String positiveSign = "+";
+ internal String negativeSign = "-";
+ internal String numberDecimalSeparator = ".";
+ internal String numberGroupSeparator = ",";
+ internal String currencyGroupSeparator = ",";
+ internal String currencyDecimalSeparator = ".";
+ internal String currencySymbol = "\x00a4"; // U+00a4 is the symbol for International Monetary Fund.
+ internal String nanSymbol = "NaN";
+ internal String positiveInfinitySymbol = "Infinity";
+ internal String negativeInfinitySymbol = "-Infinity";
+ internal String percentDecimalSeparator = ".";
+ internal String percentGroupSeparator = ",";
+ internal String percentSymbol = "%";
+ internal String perMilleSymbol = "\u2030";
+
+
+ internal String[] nativeDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+ internal int numberDecimalDigits = 2;
+ internal int currencyDecimalDigits = 2;
+ internal int currencyPositivePattern = 0;
+ internal int currencyNegativePattern = 0;
+ internal int numberNegativePattern = 1;
+ internal int percentPositivePattern = 0;
+ internal int percentNegativePattern = 0;
+ internal int percentDecimalDigits = 2;
+
+ internal int digitSubstitution = (int)DigitShapes.None;
+
+ internal bool isReadOnly = false;
+
+ // Is this NumberFormatInfo for invariant culture?
+ internal bool m_isInvariant = false;
+
+ public NumberFormatInfo() : this(null)
+ {
+ }
+
+ private static void VerifyDecimalSeparator(String decSep, String propertyName)
+ {
+ if (decSep == null)
+ {
+ throw new ArgumentNullException(propertyName,
+ SR.ArgumentNull_String);
+ }
+
+ if (decSep.Length == 0)
+ {
+ throw new ArgumentException(SR.Argument_EmptyDecString);
+ }
+ }
+
+ private static void VerifyGroupSeparator(String groupSep, String propertyName)
+ {
+ if (groupSep == null)
+ {
+ throw new ArgumentNullException(propertyName,
+ SR.ArgumentNull_String);
+ }
+ }
+
+ private static void VerifyNativeDigits(string[] nativeDig, string propertyName)
+ {
+ if (nativeDig == null)
+ {
+ throw new ArgumentNullException(propertyName, SR.ArgumentNull_Array);
+ }
+
+ if (nativeDig.Length != 10)
+ {
+ throw new ArgumentException(SR.Argument_InvalidNativeDigitCount, propertyName);
+ }
+
+ for (int i = 0; i < nativeDig.Length; i++)
+ {
+ if (nativeDig[i] == null)
+ {
+ throw new ArgumentNullException(propertyName, SR.ArgumentNull_ArrayValue);
+ }
+
+ if (nativeDig[i].Length != 1)
+ {
+ if (nativeDig[i].Length != 2)
+ {
+ // Not 1 or 2 UTF-16 code points
+ throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
+ }
+ else if (!char.IsSurrogatePair(nativeDig[i][0], nativeDig[i][1]))
+ {
+ // 2 UTF-6 code points, but not a surrogate pair
+ throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
+ }
+ }
+
+ if (CharUnicodeInfo.GetDecimalDigitValue(nativeDig[i], 0) != i &&
+ CharUnicodeInfo.GetUnicodeCategory(nativeDig[i], 0) != UnicodeCategory.PrivateUse)
+ {
+ // Not the appropriate digit according to the Unicode data properties
+ // (Digit 0 must be a 0, etc.).
+ throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName);
+ }
+ }
+ }
+
+ private static void VerifyDigitSubstitution(DigitShapes digitSub, string propertyName)
+ {
+ switch (digitSub)
+ {
+ case DigitShapes.Context:
+ case DigitShapes.None:
+ case DigitShapes.NativeNational:
+ // Success.
+ break;
+
+ default:
+ throw new ArgumentException(SR.Argument_InvalidDigitSubstitution, propertyName);
+ }
+ }
+
+ internal NumberFormatInfo(CultureData cultureData)
+ {
+ if (cultureData != null)
+ {
+ // We directly use fields here since these data is coming from data table or Win32, so we
+ // don't need to verify their values (except for invalid parsing situations).
+ cultureData.GetNFIValues(this);
+
+ if (cultureData.IsInvariantCulture)
+ {
+ // For invariant culture
+ this.m_isInvariant = true;
+ }
+ }
+ }
+
+ private void VerifyWritable()
+ {
+ if (isReadOnly)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ }
+ }
+
+ // Returns a default NumberFormatInfo that will be universally
+ // supported and constant irrespective of the current culture.
+ // Used by FromString methods.
+ //
+
+ public static NumberFormatInfo InvariantInfo
+ {
+ get
+ {
+ if (s_invariantInfo == null)
+ {
+ // Lazy create the invariant info. This cannot be done in a .cctor because exceptions can
+ // be thrown out of a .cctor stack that will need this.
+ NumberFormatInfo nfi = new NumberFormatInfo();
+ nfi.m_isInvariant = true;
+ s_invariantInfo = ReadOnly(nfi);
+ }
+ return s_invariantInfo;
+ }
+ }
+
+ public static NumberFormatInfo GetInstance(IFormatProvider formatProvider)
+ {
+ return formatProvider == null ?
+ CurrentInfo : // Fast path for a null provider
+ GetProviderNonNull(formatProvider);
+
+ NumberFormatInfo GetProviderNonNull(IFormatProvider provider)
+ {
+ // Fast path for a regular CultureInfo
+ if (provider is CultureInfo cultureProvider && !cultureProvider._isInherited)
+ {
+ return cultureProvider.numInfo ?? cultureProvider.NumberFormat;
+ }
+
+ return
+ provider as NumberFormatInfo ?? // Fast path for an NFI
+ provider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo ??
+ CurrentInfo;
+ }
+ }
+
+ public Object Clone()
+ {
+ NumberFormatInfo n = (NumberFormatInfo)MemberwiseClone();
+ n.isReadOnly = false;
+ return n;
+ }
+
+
+ public int CurrencyDecimalDigits
+ {
+ get { return currencyDecimalDigits; }
+ set
+ {
+ if (value < 0 || value > 99)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(CurrencyDecimalDigits),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ 99));
+ }
+ VerifyWritable();
+ currencyDecimalDigits = value;
+ }
+ }
+
+
+ public String CurrencyDecimalSeparator
+ {
+ get { return currencyDecimalSeparator; }
+ set
+ {
+ VerifyWritable();
+ VerifyDecimalSeparator(value, nameof(CurrencyDecimalSeparator));
+ currencyDecimalSeparator = value;
+ }
+ }
+
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return isReadOnly;
+ }
+ }
+
+ //
+ // Check the values of the groupSize array.
+ //
+ // Every element in the groupSize array should be between 1 and 9
+ // excpet the last element could be zero.
+ //
+ internal static void CheckGroupSize(String propName, int[] groupSize)
+ {
+ for (int i = 0; i < groupSize.Length; i++)
+ {
+ if (groupSize[i] < 1)
+ {
+ if (i == groupSize.Length - 1 && groupSize[i] == 0)
+ return;
+ throw new ArgumentException(SR.Argument_InvalidGroupSize, propName);
+ }
+ else if (groupSize[i] > 9)
+ {
+ throw new ArgumentException(SR.Argument_InvalidGroupSize, propName);
+ }
+ }
+ }
+
+
+ public int[] CurrencyGroupSizes
+ {
+ get
+ {
+ return ((int[])currencyGroupSizes.Clone());
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(CurrencyGroupSizes),
+ SR.ArgumentNull_Obj);
+ }
+ VerifyWritable();
+
+ Int32[] inputSizes = (Int32[])value.Clone();
+ CheckGroupSize(nameof(CurrencyGroupSizes), inputSizes);
+ currencyGroupSizes = inputSizes;
+ }
+ }
+
+
+
+ public int[] NumberGroupSizes
+ {
+ get
+ {
+ return ((int[])numberGroupSizes.Clone());
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(NumberGroupSizes),
+ SR.ArgumentNull_Obj);
+ }
+ VerifyWritable();
+
+ Int32[] inputSizes = (Int32[])value.Clone();
+ CheckGroupSize(nameof(NumberGroupSizes), inputSizes);
+ numberGroupSizes = inputSizes;
+ }
+ }
+
+
+ public int[] PercentGroupSizes
+ {
+ get
+ {
+ return ((int[])percentGroupSizes.Clone());
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(PercentGroupSizes),
+ SR.ArgumentNull_Obj);
+ }
+ VerifyWritable();
+ Int32[] inputSizes = (Int32[])value.Clone();
+ CheckGroupSize(nameof(PercentGroupSizes), inputSizes);
+ percentGroupSizes = inputSizes;
+ }
+ }
+
+
+ public String CurrencyGroupSeparator
+ {
+ get { return currencyGroupSeparator; }
+ set
+ {
+ VerifyWritable();
+ VerifyGroupSeparator(value, nameof(CurrencyGroupSeparator));
+ currencyGroupSeparator = value;
+ }
+ }
+
+
+ public String CurrencySymbol
+ {
+ get { return currencySymbol; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(CurrencySymbol),
+ SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ currencySymbol = value;
+ }
+ }
+
+ // Returns the current culture's NumberFormatInfo. Used by Parse methods.
+ //
+
+ public static NumberFormatInfo CurrentInfo
+ {
+ get
+ {
+ System.Globalization.CultureInfo culture = CultureInfo.CurrentCulture;
+ if (!culture._isInherited)
+ {
+ NumberFormatInfo info = culture.numInfo;
+ if (info != null)
+ {
+ return info;
+ }
+ }
+ return ((NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo)));
+ }
+ }
+
+
+ public String NaNSymbol
+ {
+ get
+ {
+ return nanSymbol;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(NaNSymbol),
+ SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ nanSymbol = value;
+ }
+ }
+
+
+
+ public int CurrencyNegativePattern
+ {
+ get { return currencyNegativePattern; }
+ set
+ {
+ if (value < 0 || value > 15)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(CurrencyNegativePattern),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ 15));
+ }
+ VerifyWritable();
+ currencyNegativePattern = value;
+ }
+ }
+
+
+ public int NumberNegativePattern
+ {
+ get { return numberNegativePattern; }
+ set
+ {
+ //
+ // NOTENOTE: the range of value should correspond to negNumberFormats[] in vm\COMNumber.cpp.
+ //
+ if (value < 0 || value > 4)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(NumberNegativePattern),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ 4));
+ }
+ VerifyWritable();
+ numberNegativePattern = value;
+ }
+ }
+
+
+ public int PercentPositivePattern
+ {
+ get { return percentPositivePattern; }
+ set
+ {
+ //
+ // NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp.
+ //
+ if (value < 0 || value > 3)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(PercentPositivePattern),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ 3));
+ }
+ VerifyWritable();
+ percentPositivePattern = value;
+ }
+ }
+
+
+ public int PercentNegativePattern
+ {
+ get { return percentNegativePattern; }
+ set
+ {
+ //
+ // NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp.
+ //
+ if (value < 0 || value > 11)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(PercentNegativePattern),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ 11));
+ }
+ VerifyWritable();
+ percentNegativePattern = value;
+ }
+ }
+
+
+ public String NegativeInfinitySymbol
+ {
+ get
+ {
+ return negativeInfinitySymbol;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(NegativeInfinitySymbol),
+ SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ negativeInfinitySymbol = value;
+ }
+ }
+
+
+ public String NegativeSign
+ {
+ get { return negativeSign; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(NegativeSign),
+ SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ negativeSign = value;
+ }
+ }
+
+
+ public int NumberDecimalDigits
+ {
+ get { return numberDecimalDigits; }
+ set
+ {
+ if (value < 0 || value > 99)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(NumberDecimalDigits),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ 99));
+ }
+ VerifyWritable();
+ numberDecimalDigits = value;
+ }
+ }
+
+
+ public String NumberDecimalSeparator
+ {
+ get { return numberDecimalSeparator; }
+ set
+ {
+ VerifyWritable();
+ VerifyDecimalSeparator(value, nameof(NumberDecimalSeparator));
+ numberDecimalSeparator = value;
+ }
+ }
+
+
+ public String NumberGroupSeparator
+ {
+ get { return numberGroupSeparator; }
+ set
+ {
+ VerifyWritable();
+ VerifyGroupSeparator(value, nameof(NumberGroupSeparator));
+ numberGroupSeparator = value;
+ }
+ }
+
+
+ public int CurrencyPositivePattern
+ {
+ get { return currencyPositivePattern; }
+ set
+ {
+ if (value < 0 || value > 3)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(CurrencyPositivePattern),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ 3));
+ }
+ VerifyWritable();
+ currencyPositivePattern = value;
+ }
+ }
+
+
+ public String PositiveInfinitySymbol
+ {
+ get
+ {
+ return positiveInfinitySymbol;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(PositiveInfinitySymbol),
+ SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ positiveInfinitySymbol = value;
+ }
+ }
+
+
+ public String PositiveSign
+ {
+ get { return positiveSign; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(PositiveSign),
+ SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ positiveSign = value;
+ }
+ }
+
+
+ public int PercentDecimalDigits
+ {
+ get { return percentDecimalDigits; }
+ set
+ {
+ if (value < 0 || value > 99)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(PercentDecimalDigits),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 0,
+ 99));
+ }
+ VerifyWritable();
+ percentDecimalDigits = value;
+ }
+ }
+
+
+ public String PercentDecimalSeparator
+ {
+ get { return percentDecimalSeparator; }
+ set
+ {
+ VerifyWritable();
+ VerifyDecimalSeparator(value, nameof(PercentDecimalSeparator));
+ percentDecimalSeparator = value;
+ }
+ }
+
+
+ public String PercentGroupSeparator
+ {
+ get { return percentGroupSeparator; }
+ set
+ {
+ VerifyWritable();
+ VerifyGroupSeparator(value, nameof(PercentGroupSeparator));
+ percentGroupSeparator = value;
+ }
+ }
+
+
+ public String PercentSymbol
+ {
+ get
+ {
+ return percentSymbol;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(PercentSymbol),
+ SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ percentSymbol = value;
+ }
+ }
+
+
+ public String PerMilleSymbol
+ {
+ get { return perMilleSymbol; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(PerMilleSymbol),
+ SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ perMilleSymbol = value;
+ }
+ }
+
+ public string[] NativeDigits
+ {
+ get { return (String[])nativeDigits.Clone(); }
+ set
+ {
+ VerifyWritable();
+ VerifyNativeDigits(value, nameof(NativeDigits));
+ nativeDigits = value;
+ }
+ }
+
+ public DigitShapes DigitSubstitution
+ {
+ get { return (DigitShapes)digitSubstitution; }
+ set
+ {
+ VerifyWritable();
+ VerifyDigitSubstitution(value, nameof(DigitSubstitution));
+ digitSubstitution = (int)value;
+ }
+ }
+
+ public Object GetFormat(Type formatType)
+ {
+ return formatType == typeof(NumberFormatInfo) ? this : null;
+ }
+
+ public static NumberFormatInfo ReadOnly(NumberFormatInfo nfi)
+ {
+ if (nfi == null)
+ {
+ throw new ArgumentNullException(nameof(nfi));
+ }
+ if (nfi.IsReadOnly)
+ {
+ return (nfi);
+ }
+ NumberFormatInfo info = (NumberFormatInfo)(nfi.MemberwiseClone());
+ info.isReadOnly = true;
+ return info;
+ }
+
+ // private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFFC00);
+ private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
+ | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign
+ | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint
+ | NumberStyles.AllowThousands | NumberStyles.AllowExponent
+ | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier);
+
+ internal static void ValidateParseStyleInteger(NumberStyles style)
+ {
+ // Check for undefined flags
+ if ((style & InvalidNumberStyles) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style));
+ }
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ { // Check for hex number
+ if ((style & ~NumberStyles.HexNumber) != 0)
+ {
+ throw new ArgumentException(SR.Arg_InvalidHexStyle);
+ }
+ }
+ }
+
+ internal static void ValidateParseStyleFloatingPoint(NumberStyles style)
+ {
+ // Check for undefined flags
+ if ((style & InvalidNumberStyles) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style));
+ }
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ { // Check for hex number
+ throw new ArgumentException(SR.Arg_HexStyleNotSupported);
+ }
+ }
+ } // NumberFormatInfo
+}
+
+
+
+
+
+
+
+
+
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/NumberStyles.cs b/src/System.Private.CoreLib/shared/System/Globalization/NumberStyles.cs
new file mode 100644
index 0000000000..5909d65a2c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/NumberStyles.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Contains valid formats for Numbers recognized by
+** the Number class' parsing code.
+**
+**
+===========================================================*/
+
+namespace System.Globalization
+{
+ [Flags]
+ public enum NumberStyles
+ {
+ // Bit flag indicating that leading whitespace is allowed. Character values
+ // 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, and 0x0020 are considered to be
+ // whitespace.
+
+ None = 0x00000000,
+
+ AllowLeadingWhite = 0x00000001,
+
+ AllowTrailingWhite = 0x00000002, //Bitflag indicating trailing whitespace is allowed.
+
+ AllowLeadingSign = 0x00000004, //Can the number start with a sign char.
+ //Specified by NumberFormatInfo.PositiveSign and NumberFormatInfo.NegativeSign
+
+ AllowTrailingSign = 0x00000008, //Allow the number to end with a sign char
+
+ AllowParentheses = 0x00000010, //Allow the number to be enclosed in parens
+
+ AllowDecimalPoint = 0x00000020, //Allow a decimal point
+
+ AllowThousands = 0x00000040, //Allow thousands separators (more properly, allow group separators)
+
+ AllowExponent = 0x00000080, //Allow an exponent
+
+ AllowCurrencySymbol = 0x00000100, //Allow a currency symbol.
+
+ AllowHexSpecifier = 0x00000200, //Allow specifiying hexadecimal.
+ //Common uses. These represent some of the most common combinations of these flags.
+
+
+ Integer = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign,
+
+ HexNumber = AllowLeadingWhite | AllowTrailingWhite | AllowHexSpecifier,
+
+ Number = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign | AllowTrailingSign |
+ AllowDecimalPoint | AllowThousands,
+
+ Float = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign |
+ AllowDecimalPoint | AllowExponent,
+
+ Currency = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign | AllowTrailingSign |
+ AllowParentheses | AllowDecimalPoint | AllowThousands | AllowCurrencySymbol,
+
+ Any = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign | AllowTrailingSign |
+ AllowParentheses | AllowDecimalPoint | AllowThousands | AllowCurrencySymbol | AllowExponent,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/PersianCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/PersianCalendar.cs
new file mode 100644
index 0000000000..10912f85b1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/PersianCalendar.cs
@@ -0,0 +1,601 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ // Modern Persian calendar is a solar observation based calendar. Each new year begins on the day when the vernal equinox occurs before noon.
+ // The epoch is the date of the vernal equinox prior to the epoch of the Islamic calendar (March 19, 622 Julian or March 22, 622 Gregorian)
+
+ // There is no Persian year 0. Ordinary years have 365 days. Leap years have 366 days with the last month (Esfand) gaining the extra day.
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 0622/03/22 9999/12/31
+ ** Persian 0001/01/01 9378/10/13
+ */
+
+ public class PersianCalendar : Calendar
+ {
+ public static readonly int PersianEra = 1;
+
+ internal static long PersianEpoch = new DateTime(622, 3, 22).Ticks / GregorianCalendar.TicksPerDay;
+ private const int ApproximateHalfYear = 180;
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+ internal const int MonthsPerYear = 12;
+
+ internal static int[] DaysToMonth = { 0, 31, 62, 93, 124, 155, 186, 216, 246, 276, 306, 336, 366 };
+
+ internal const int MaxCalendarYear = 9378;
+ internal const int MaxCalendarMonth = 10;
+ internal const int MaxCalendarDay = 13;
+
+ // Persian calendar (year: 1, month: 1, day:1 ) = Gregorian (year: 622, month: 3, day: 22)
+ // This is the minimal Gregorian date that we support in the PersianCalendar.
+ internal static DateTime minDate = new DateTime(622, 3, 22);
+ internal static DateTime maxDate = DateTime.MaxValue;
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ // Construct an instance of Persian calendar.
+
+ public PersianCalendar()
+ {
+ }
+
+
+ internal override CalendarId BaseCalendarID
+ {
+ get
+ {
+ return CalendarId.GREGORIAN;
+ }
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return CalendarId.PERSIAN;
+ }
+ }
+
+
+ /*=================================GetAbsoluteDatePersian==========================
+ **Action: Gets the Absolute date for the given Persian date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ private long GetAbsoluteDatePersian(int year, int month, int day)
+ {
+ if (year >= 1 && year <= MaxCalendarYear && month >= 1 && month <= 12)
+ {
+ int ordinalDay = DaysInPreviousMonths(month) + day - 1; // day is one based, make 0 based since this will be the number of days we add to beginning of year below
+ int approximateDaysFromEpochForYearStart = (int)(CalendricalCalculationsHelper.MeanTropicalYearInDays * (year - 1));
+ long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(PersianEpoch + approximateDaysFromEpochForYearStart + ApproximateHalfYear);
+ yearStart += ordinalDay;
+ return yearStart;
+ }
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+
+ internal static void CheckTicksRange(long ticks)
+ {
+ if (ticks < minDate.Ticks || ticks > maxDate.Ticks)
+ {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ SR.ArgumentOutOfRange_CalendarRange,
+ minDate,
+ maxDate));
+ }
+ }
+
+ internal static void CheckEraRange(int era)
+ {
+ if (era != CurrentEra && era != PersianEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ internal static void CheckYearRange(int year, int era)
+ {
+ CheckEraRange(era);
+ if (year < 1 || year > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarYear));
+ }
+ }
+
+ internal static void CheckYearMonthRange(int year, int month, int era)
+ {
+ CheckYearRange(year, era);
+ if (year == MaxCalendarYear)
+ {
+ if (month > MaxCalendarMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(month),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarMonth));
+ }
+ }
+
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+ }
+
+ private static int MonthFromOrdinalDay(int ordinalDay)
+ {
+ Debug.Assert(ordinalDay <= 366);
+ int index = 0;
+ while (ordinalDay > DaysToMonth[index])
+ index++;
+
+ return index;
+ }
+
+ private static int DaysInPreviousMonths(int month)
+ {
+ Debug.Assert(1 <= month && month <= 12);
+ --month; // months are one based but for calculations use 0 based
+ return DaysToMonth[month];
+ }
+
+ /*=================================GetDatePart==========================
+ **Action: Returns a given date part of this <i>DateTime</i>. This method is used
+ ** to compute the year, day-of-year, month, or day part.
+ **Returns:
+ **Arguments:
+ **Exceptions: ArgumentException if part is incorrect.
+ ============================================================================*/
+
+ internal int GetDatePart(long ticks, int part)
+ {
+ long NumDays; // The calculation buffer in number of days.
+
+ CheckTicksRange(ticks);
+
+ //
+ // Get the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
+ // 1/1/0001 is absolute date 1.
+ //
+ NumDays = ticks / GregorianCalendar.TicksPerDay + 1;
+
+ //
+ // Calculate the appromixate Persian Year.
+ //
+
+ long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(NumDays);
+ int y = (int)(Math.Floor(((yearStart - PersianEpoch) / CalendricalCalculationsHelper.MeanTropicalYearInDays) + 0.5)) + 1;
+ Debug.Assert(y >= 1);
+
+ if (part == DatePartYear)
+ {
+ return y;
+ }
+
+ //
+ // Calculate the Persian Month.
+ //
+
+ int ordinalDay = (int)(NumDays - CalendricalCalculationsHelper.GetNumberOfDays(this.ToDateTime(y, 1, 1, 0, 0, 0, 0, 1)));
+
+ if (part == DatePartDayOfYear)
+ {
+ return ordinalDay;
+ }
+
+ int m = MonthFromOrdinalDay(ordinalDay);
+ Debug.Assert(ordinalDay >= 1);
+ Debug.Assert(m >= 1 && m <= 12);
+ if (part == DatePartMonth)
+ {
+ return m;
+ }
+
+ int d = ordinalDay - DaysInPreviousMonths(m);
+ Debug.Assert(1 <= d);
+ Debug.Assert(d <= 31);
+
+ //
+ // Calculate the Persian Day.
+ //
+
+ if (part == DatePartDay)
+ {
+ return (d);
+ }
+
+ // Incorrect part value.
+ throw new InvalidOperationException(SR.InvalidOperation_DateTimeParsing);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ -120000,
+ 120000));
+ }
+ // Get the date in Persian calendar.
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int days = GetDaysInMonth(y, m);
+ if (d > days)
+ {
+ d = days;
+ }
+ long ticks = GetAbsoluteDatePersian(y, m, d) * TicksPerDay + time.Ticks % TicksPerDay;
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ CheckYearMonthRange(year, month, era);
+
+ if ((month == MaxCalendarMonth) && (year == MaxCalendarYear))
+ {
+ return MaxCalendarDay;
+ }
+
+ int daysInMonth = DaysToMonth[month] - DaysToMonth[month - 1];
+ if ((month == MonthsPerYear) && !IsLeapYear(year))
+ {
+ Debug.Assert(daysInMonth == 30);
+ --daysInMonth;
+ }
+ return daysInMonth;
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ if (year == MaxCalendarYear)
+ {
+ return DaysToMonth[MaxCalendarMonth - 1] + MaxCalendarDay;
+ }
+ // Common years have 365 days. Leap years have 366 days.
+ return (IsLeapYear(year, CurrentEra) ? 366 : 365);
+ }
+
+ // Returns the era for the specified DateTime value.
+
+
+ public override int GetEra(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return (PersianEra);
+ }
+
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { PersianEra });
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+
+ public override int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ if (year == MaxCalendarYear)
+ {
+ return MaxCalendarMonth;
+ }
+ return (12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and MaxCalendarYear.
+ //
+
+
+ public override int GetYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ // The year/month/era value checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Day,
+ daysInMonth,
+ month));
+ }
+ return (IsLeapYear(year, era) && month == 12 && day == 30);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ CheckYearMonthRange(year, month, era);
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+
+ if (year == MaxCalendarYear)
+ {
+ return false;
+ }
+
+ return (GetAbsoluteDatePersian(year + 1, 1, 1) - GetAbsoluteDatePersian(year, 1, 1)) == 366;
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ // The year/month/era checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Day,
+ daysInMonth,
+ month));
+ }
+
+ long lDate = GetAbsoluteDatePersian(year, month, day);
+
+ if (lDate >= 0)
+ {
+ return (new DateTime(lDate * GregorianCalendar.TicksPerDay + TimeToTicks(hour, minute, second, millisecond)));
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 1410;
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(value),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ MaxCalendarYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (year < 100)
+ {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if (year > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarYear));
+ }
+ return (year);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/RegionInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/RegionInfo.cs
new file mode 100644
index 0000000000..2cad7bb31c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/RegionInfo.cs
@@ -0,0 +1,399 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class represents settings specified by de jure or
+// de facto standards for a particular country/region. In
+// contrast to CultureInfo, the RegionInfo does not represent
+// preferences of the user and does not depend on the user's
+// language or culture.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ public class RegionInfo
+ {
+ //--------------------------------------------------------------------//
+ // Internal Information //
+ //--------------------------------------------------------------------//
+
+ //
+ // Variables.
+ //
+
+ //
+ // Name of this region (ie: es-US): serialized, the field used for deserialization
+ //
+ internal String _name;
+
+ //
+ // The CultureData instance that we are going to read data from.
+ //
+ internal CultureData _cultureData;
+
+ //
+ // The RegionInfo for our current region
+ //
+ internal static volatile RegionInfo s_currentRegionInfo;
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // RegionInfo Constructors
+ //
+ // Note: We prefer that a region be created with a full culture name (ie: en-US)
+ // because otherwise the native strings won't be right.
+ //
+ // In Silverlight we enforce that RegionInfos must be created with a full culture name
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public RegionInfo(String name)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+
+ if (name.Length == 0) //The InvariantCulture has no matching region
+ {
+ throw new ArgumentException(SR.Argument_NoRegionInvariantCulture, nameof(name));
+ }
+
+
+ //
+ // For CoreCLR we only want the region names that are full culture names
+ //
+ _cultureData = CultureData.GetCultureDataForRegion(name, true);
+ if (_cultureData == null)
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.Argument_InvalidCultureName, name), nameof(name));
+
+
+ // Not supposed to be neutral
+ if (_cultureData.IsNeutralCulture)
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidNeutralRegionName, name), nameof(name));
+
+ SetName(name);
+ }
+
+ public RegionInfo(int culture)
+ {
+ if (culture == CultureInfo.LOCALE_INVARIANT) //The InvariantCulture has no matching region
+ {
+ throw new ArgumentException(SR.Argument_NoRegionInvariantCulture);
+ }
+
+ if (culture == CultureInfo.LOCALE_NEUTRAL)
+ {
+ // Not supposed to be neutral
+ throw new ArgumentException(SR.Format(SR.Argument_CultureIsNeutral, culture), nameof(culture));
+ }
+
+ if (culture == CultureInfo.LOCALE_CUSTOM_DEFAULT)
+ {
+ // Not supposed to be neutral
+ throw new ArgumentException(SR.Format(SR.Argument_CustomCultureCannotBePassedByNumber, culture), nameof(culture));
+ }
+
+ _cultureData = CultureData.GetCultureData(culture, true);
+ _name = _cultureData.SREGIONNAME;
+
+ if (_cultureData.IsNeutralCulture)
+ {
+ // Not supposed to be neutral
+ throw new ArgumentException(SR.Format(SR.Argument_CultureIsNeutral, culture), nameof(culture));
+ }
+ }
+
+ internal RegionInfo(CultureData cultureData)
+ {
+ _cultureData = cultureData;
+ _name = _cultureData.SREGIONNAME;
+ }
+
+ private void SetName(string name)
+ {
+ // Use the name of the region we found
+ _name = _cultureData.SREGIONNAME;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetCurrentRegion
+ //
+ // This instance provides methods based on the current user settings.
+ // These settings are volatile and may change over the lifetime of the
+ // thread.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public static RegionInfo CurrentRegion
+ {
+ get
+ {
+ RegionInfo temp = s_currentRegionInfo;
+ if (temp == null)
+ {
+ temp = new RegionInfo(CultureInfo.CurrentCulture._cultureData);
+
+ // Need full name for custom cultures
+ temp._name = temp._cultureData.SREGIONNAME;
+ s_currentRegionInfo = temp;
+ }
+ return temp;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetName
+ //
+ // Returns the name of the region (ie: en-US)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String Name
+ {
+ get
+ {
+ Debug.Assert(_name != null, "Expected RegionInfo._name to be populated already");
+ return (_name);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetEnglishName
+ //
+ // Returns the name of the region in English. (ie: United States)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String EnglishName
+ {
+ get
+ {
+ return (_cultureData.SENGCOUNTRY);
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetDisplayName
+ //
+ // Returns the display name (localized) of the region. (ie: United States
+ // if the current UI language is en-US)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String DisplayName
+ {
+ get
+ {
+ return (_cultureData.SLOCALIZEDCOUNTRY);
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetNativeName
+ //
+ // Returns the native name of the region. (ie: Deutschland)
+ // WARNING: You need a full locale name for this to make sense.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String NativeName
+ {
+ get
+ {
+ return (_cultureData.SNATIVECOUNTRY);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // TwoLetterISORegionName
+ //
+ // Returns the two letter ISO region name (ie: US)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String TwoLetterISORegionName
+ {
+ get
+ {
+ return (_cultureData.SISO3166CTRYNAME);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ThreeLetterISORegionName
+ //
+ // Returns the three letter ISO region name (ie: USA)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String ThreeLetterISORegionName
+ {
+ get
+ {
+ return (_cultureData.SISO3166CTRYNAME2);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ThreeLetterWindowsRegionName
+ //
+ // Returns the three letter windows region name (ie: USA)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String ThreeLetterWindowsRegionName
+ {
+ get
+ {
+ // ThreeLetterWindowsRegionName is really same as ThreeLetterISORegionName
+ return ThreeLetterISORegionName;
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsMetric
+ //
+ // Returns true if this region uses the metric measurement system
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual bool IsMetric
+ {
+ get
+ {
+ int value = _cultureData.IMEASURE;
+ return (value == 0);
+ }
+ }
+
+ public virtual int GeoId
+ {
+ get
+ {
+ return (_cultureData.IGEOID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CurrencyEnglishName
+ //
+ // English name for this region's currency, ie: Swiss Franc
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual string CurrencyEnglishName
+ {
+ get
+ {
+ return (_cultureData.SENGLISHCURRENCY);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CurrencyNativeName
+ //
+ // Native name for this region's currency, ie: Schweizer Franken
+ // WARNING: You need a full locale name for this to make sense.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual string CurrencyNativeName
+ {
+ get
+ {
+ return (_cultureData.SNATIVECURRENCY);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CurrencySymbol
+ //
+ // Currency Symbol for this locale, ie: Fr. or $
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String CurrencySymbol
+ {
+ get
+ {
+ return (_cultureData.SCURRENCY);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ISOCurrencySymbol
+ //
+ // ISO Currency Symbol for this locale, ie: CHF
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String ISOCurrencySymbol
+ {
+ get
+ {
+ return (_cultureData.SINTLSYMBOL);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same RegionInfo as the current instance.
+ //
+ // RegionInfos are considered equal if and only if they have the same name
+ // (ie: en-US)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override bool Equals(Object value)
+ {
+ RegionInfo that = value as RegionInfo;
+ if (that != null)
+ {
+ return this.Name.Equals(that.Name);
+ }
+
+ return (false);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // CultureInfo. The hash code is guaranteed to be the same for RegionInfo
+ // A and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override int GetHashCode()
+ {
+ return (this.Name.GetHashCode());
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns the name of the Region, ie: es-US
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override String ToString()
+ {
+ return (Name);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/SortKey.cs b/src/System.Private.CoreLib/shared/System/Globalization/SortKey.cs
new file mode 100644
index 0000000000..647db75b63
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/SortKey.cs
@@ -0,0 +1,175 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class implements a set of methods for retrieving
+// sort key information.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ public partial class SortKey
+ {
+ //--------------------------------------------------------------------//
+ // Internal Information //
+ //--------------------------------------------------------------------//
+
+ internal string _localeName; // locale identifier
+
+ internal CompareOptions _options; // options
+ internal string _string; // original string
+ internal byte[] _keyData; // sortkey data
+
+ //
+ // The following constructor is designed to be called from CompareInfo to get the
+ // the sort key of specific string for synthetic culture
+ //
+ internal SortKey(String localeName, String str, CompareOptions options, byte[] keyData)
+ {
+ _keyData = keyData;
+ _localeName = localeName;
+ _options = options;
+ _string = str;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetOriginalString
+ //
+ // Returns the original string used to create the current instance
+ // of SortKey.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String OriginalString
+ {
+ get
+ {
+ return (_string);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetKeyData
+ //
+ // Returns a byte array representing the current instance of the
+ // sort key.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual byte[] KeyData
+ {
+ get
+ {
+ return (byte[])(_keyData.Clone());
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Compare
+ //
+ // Compares the two sort keys. Returns 0 if the two sort keys are
+ // equal, a number less than 0 if sortkey1 is less than sortkey2,
+ // and a number greater than 0 if sortkey1 is greater than sortkey2.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public static int Compare(SortKey sortkey1, SortKey sortkey2)
+ {
+ if (sortkey1 == null || sortkey2 == null)
+ {
+ throw new ArgumentNullException((sortkey1 == null ? nameof(sortkey1) : nameof(sortkey2)));
+ }
+
+ byte[] key1Data = sortkey1._keyData;
+ byte[] key2Data = sortkey2._keyData;
+
+ Debug.Assert(key1Data != null, "key1Data != null");
+ Debug.Assert(key2Data != null, "key2Data != null");
+
+ if (key1Data.Length == 0)
+ {
+ if (key2Data.Length == 0)
+ {
+ return (0);
+ }
+ return (-1);
+ }
+ if (key2Data.Length == 0)
+ {
+ return (1);
+ }
+
+ int compLen = (key1Data.Length < key2Data.Length) ? key1Data.Length : key2Data.Length;
+
+ for (int i = 0; i < compLen; i++)
+ {
+ if (key1Data[i] > key2Data[i])
+ {
+ return (1);
+ }
+ if (key1Data[i] < key2Data[i])
+ {
+ return (-1);
+ }
+ }
+
+ return 0;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same SortKey as the current instance.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override bool Equals(Object value)
+ {
+ SortKey that = value as SortKey;
+
+ if (that != null)
+ {
+ return Compare(this, that) == 0;
+ }
+
+ return (false);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // SortKey. The hash code is guaranteed to be the same for
+ // SortKey A and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override int GetHashCode()
+ {
+ return (CompareInfo.GetCompareInfo(_localeName).GetHashCodeOfString(_string, _options));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns a string describing the
+ // SortKey.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override String ToString()
+ {
+ return ("SortKey - " + _localeName + ", " + _options + ", " + _string);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/SortVersion.cs b/src/System.Private.CoreLib/shared/System/Globalization/SortVersion.cs
new file mode 100644
index 0000000000..46e9a833ec
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/SortVersion.cs
@@ -0,0 +1,99 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class SortVersion : IEquatable<SortVersion>
+ {
+ private int m_NlsVersion; // Do not rename (binary serialization)
+ private Guid m_SortId; // Do not rename (binary serialization)
+
+ public int FullVersion
+ {
+ get
+ {
+ return m_NlsVersion;
+ }
+ }
+
+ public Guid SortId
+ {
+ get
+ {
+ return m_SortId;
+ }
+ }
+
+ public SortVersion(int fullVersion, Guid sortId)
+ {
+ m_SortId = sortId;
+ m_NlsVersion = fullVersion;
+ }
+
+ internal SortVersion(int nlsVersion, int effectiveId, Guid customVersion)
+ {
+ m_NlsVersion = nlsVersion;
+
+ if (customVersion == Guid.Empty)
+ {
+ byte b1 = (byte)(effectiveId >> 24);
+ byte b2 = (byte)((effectiveId & 0x00FF0000) >> 16);
+ byte b3 = (byte)((effectiveId & 0x0000FF00) >> 8);
+ byte b4 = (byte)(effectiveId & 0xFF);
+ customVersion = new Guid(0, 0, 0, 0, 0, 0, 0, b1, b2, b3, b4);
+ }
+
+ m_SortId = customVersion;
+ }
+
+ public override bool Equals(object obj)
+ {
+ SortVersion n = obj as SortVersion;
+ if (n != null)
+ {
+ return this.Equals(n);
+ }
+
+ return false;
+ }
+
+ public bool Equals(SortVersion other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ return m_NlsVersion == other.m_NlsVersion && m_SortId == other.m_SortId;
+ }
+
+ public override int GetHashCode()
+ {
+ return m_NlsVersion * 7 | m_SortId.GetHashCode();
+ }
+
+ public static bool operator ==(SortVersion left, SortVersion right)
+ {
+ if (((object)left) != null)
+ {
+ return left.Equals(right);
+ }
+
+ if (((object)right) != null)
+ {
+ return right.Equals(left);
+ }
+
+ // Both null.
+ return true;
+ }
+
+ public static bool operator !=(SortVersion left, SortVersion right)
+ {
+ return !(left == right);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/StringInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/StringInfo.cs
new file mode 100644
index 0000000000..aa62b2c474
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/StringInfo.cs
@@ -0,0 +1,350 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class defines behaviors specific to a writing system.
+// A writing system is the collection of scripts and
+// orthographic rules required to represent a language as text.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ public class StringInfo
+ {
+ private string _str;
+
+ private int[] _indexes;
+
+ // Legacy constructor
+ public StringInfo() : this("") { }
+
+ // Primary, useful constructor
+ public StringInfo(string value)
+ {
+ this.String = value;
+ }
+
+ public override bool Equals(Object value)
+ {
+ StringInfo that = value as StringInfo;
+ if (that != null)
+ {
+ return (_str.Equals(that._str));
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return _str.GetHashCode();
+ }
+
+
+ // Our zero-based array of index values into the string. Initialize if
+ // our private array is not yet, in fact, initialized.
+ private int[] Indexes
+ {
+ get
+ {
+ if ((null == _indexes) && (0 < this.String.Length))
+ {
+ _indexes = StringInfo.ParseCombiningCharacters(this.String);
+ }
+
+ return (_indexes);
+ }
+ }
+
+ public string String
+ {
+ get
+ {
+ return (_str);
+ }
+ set
+ {
+ if (null == value)
+ {
+ throw new ArgumentNullException(nameof(String),
+ SR.ArgumentNull_String);
+ }
+
+ _str = value;
+ _indexes = null;
+ }
+ }
+
+ public int LengthInTextElements
+ {
+ get
+ {
+ if (null == this.Indexes)
+ {
+ // Indexes not initialized, so assume length zero
+ return (0);
+ }
+
+ return (this.Indexes.Length);
+ }
+ }
+
+ public string SubstringByTextElements(int startingTextElement)
+ {
+ // If the string is empty, no sense going further.
+ if (null == this.Indexes)
+ {
+ // Just decide which error to give depending on the param they gave us....
+ if (startingTextElement < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.ArgumentOutOfRange_NeedPosNum);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.Arg_ArgumentOutOfRangeException);
+ }
+ }
+ return (SubstringByTextElements(startingTextElement, Indexes.Length - startingTextElement));
+ }
+
+ public string SubstringByTextElements(int startingTextElement, int lengthInTextElements)
+ {
+ if (startingTextElement < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.ArgumentOutOfRange_NeedPosNum);
+ }
+
+ if (this.String.Length == 0 || startingTextElement >= Indexes.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.Arg_ArgumentOutOfRangeException);
+ }
+
+ if (lengthInTextElements < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(lengthInTextElements), SR.ArgumentOutOfRange_NeedPosNum);
+ }
+
+ if (startingTextElement > Indexes.Length - lengthInTextElements)
+ {
+ throw new ArgumentOutOfRangeException(nameof(lengthInTextElements), SR.Arg_ArgumentOutOfRangeException);
+ }
+
+ int start = Indexes[startingTextElement];
+
+ if (startingTextElement + lengthInTextElements == Indexes.Length)
+ {
+ // We are at the last text element in the string and because of that
+ // must handle the call differently.
+ return (this.String.Substring(start));
+ }
+ else
+ {
+ return (this.String.Substring(start, (Indexes[lengthInTextElements + startingTextElement] - start)));
+ }
+ }
+
+ public static string GetNextTextElement(string str)
+ {
+ return (GetNextTextElement(str, 0));
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Get the code point count of the current text element.
+ //
+ // A combining class is defined as:
+ // A character/surrogate that has the following Unicode category:
+ // * NonSpacingMark (e.g. U+0300 COMBINING GRAVE ACCENT)
+ // * SpacingCombiningMark (e.g. U+ 0903 DEVANGARI SIGN VISARGA)
+ // * EnclosingMark (e.g. U+20DD COMBINING ENCLOSING CIRCLE)
+ //
+ // In the context of GetNextTextElement() and ParseCombiningCharacters(), a text element is defined as:
+ //
+ // 1. If a character/surrogate is in the following category, it is a text element.
+ // It can NOT further combine with characters in the combinging class to form a text element.
+ // * one of the Unicode category in the combinging class
+ // * UnicodeCategory.Format
+ // * UnicodeCateogry.Control
+ // * UnicodeCategory.OtherNotAssigned
+ // 2. Otherwise, the character/surrogate can be combined with characters in the combinging class to form a text element.
+ //
+ // Return:
+ // The length of the current text element
+ //
+ // Parameters:
+ // String str
+ // index The starting index
+ // len The total length of str (to define the upper boundary)
+ // ucCurrent The Unicode category pointed by Index. It will be updated to the uc of next character if this is not the last text element.
+ // currentCharCount The char count of an abstract char pointed by Index. It will be updated to the char count of next abstract character if this is not the last text element.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static int GetCurrentTextElementLen(string str, int index, int len, ref UnicodeCategory ucCurrent, ref int currentCharCount)
+ {
+ Debug.Assert(index >= 0 && len >= 0, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len);
+ Debug.Assert(index < len, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len);
+ if (index + currentCharCount == len)
+ {
+ // This is the last character/surrogate in the string.
+ return (currentCharCount);
+ }
+
+ // Call an internal GetUnicodeCategory, which will tell us both the unicode category, and also tell us if it is a surrogate pair or not.
+ int nextCharCount;
+ UnicodeCategory ucNext = CharUnicodeInfo.InternalGetUnicodeCategory(str, index + currentCharCount, out nextCharCount);
+ if (CharUnicodeInfo.IsCombiningCategory(ucNext))
+ {
+ // The next element is a combining class.
+ // Check if the current text element to see if it is a valid base category (i.e. it should not be a combining category,
+ // not a format character, and not a control character).
+
+ if (CharUnicodeInfo.IsCombiningCategory(ucCurrent)
+ || (ucCurrent == UnicodeCategory.Format)
+ || (ucCurrent == UnicodeCategory.Control)
+ || (ucCurrent == UnicodeCategory.OtherNotAssigned)
+ || (ucCurrent == UnicodeCategory.Surrogate)) // An unpair high surrogate or low surrogate
+ {
+ // Will fall thru and return the currentCharCount
+ }
+ else
+ {
+ int startIndex = index; // Remember the current index.
+
+ // We have a valid base characters, and we have a character (or surrogate) that is combining.
+ // Check if there are more combining characters to follow.
+ // Check if the next character is a nonspacing character.
+ index += currentCharCount + nextCharCount;
+
+ while (index < len)
+ {
+ ucNext = CharUnicodeInfo.InternalGetUnicodeCategory(str, index, out nextCharCount);
+ if (!CharUnicodeInfo.IsCombiningCategory(ucNext))
+ {
+ ucCurrent = ucNext;
+ currentCharCount = nextCharCount;
+ break;
+ }
+ index += nextCharCount;
+ }
+ return (index - startIndex);
+ }
+ }
+ // The return value will be the currentCharCount.
+ int ret = currentCharCount;
+ ucCurrent = ucNext;
+ // Update currentCharCount.
+ currentCharCount = nextCharCount;
+ return (ret);
+ }
+
+ // Returns the str containing the next text element in str starting at
+ // index index. If index is not supplied, then it will start at the beginning
+ // of str. It recognizes a base character plus one or more combining
+ // characters or a properly formed surrogate pair as a text element. See also
+ // the ParseCombiningCharacters() and the ParseSurrogates() methods.
+ public static string GetNextTextElement(string str, int index)
+ {
+ //
+ // Validate parameters.
+ //
+ if (str == null)
+ {
+ throw new ArgumentNullException(nameof(str));
+ }
+
+ int len = str.Length;
+ if (index < 0 || index >= len)
+ {
+ if (index == len)
+ {
+ return (String.Empty);
+ }
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ int charLen;
+ UnicodeCategory uc = CharUnicodeInfo.InternalGetUnicodeCategory(str, index, out charLen);
+ return (str.Substring(index, GetCurrentTextElementLen(str, index, len, ref uc, ref charLen)));
+ }
+
+ public static TextElementEnumerator GetTextElementEnumerator(string str)
+ {
+ return (GetTextElementEnumerator(str, 0));
+ }
+
+ public static TextElementEnumerator GetTextElementEnumerator(string str, int index)
+ {
+ //
+ // Validate parameters.
+ //
+ if (str == null)
+ {
+ throw new ArgumentNullException(nameof(str));
+ }
+
+ int len = str.Length;
+ if (index < 0 || (index > len))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ return (new TextElementEnumerator(str, index, len));
+ }
+
+ /*
+ * Returns the indices of each base character or properly formed surrogate pair
+ * within the str. It recognizes a base character plus one or more combining
+ * characters or a properly formed surrogate pair as a text element and returns
+ * the index of the base character or high surrogate. Each index is the
+ * beginning of a text element within a str. The length of each element is
+ * easily computed as the difference between successive indices. The length of
+ * the array will always be less than or equal to the length of the str. For
+ * example, given the str \u4f00\u302a\ud800\udc00\u4f01, this method would
+ * return the indices: 0, 2, 4.
+ */
+
+ public static int[] ParseCombiningCharacters(string str)
+ {
+ if (str == null)
+ {
+ throw new ArgumentNullException(nameof(str));
+ }
+
+ int len = str.Length;
+ int[] result = new int[len];
+ if (len == 0)
+ {
+ return (result);
+ }
+
+ int resultCount = 0;
+
+ int i = 0;
+ int currentCharLen;
+ UnicodeCategory currentCategory = CharUnicodeInfo.InternalGetUnicodeCategory(str, 0, out currentCharLen);
+
+ while (i < len)
+ {
+ result[resultCount++] = i;
+ i += GetCurrentTextElementLen(str, i, len, ref currentCategory, ref currentCharLen);
+ }
+
+ if (resultCount < len)
+ {
+ int[] returnArray = new int[resultCount];
+ Array.Copy(result, returnArray, resultCount);
+ return (returnArray);
+ }
+ return (result);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TaiwanCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/TaiwanCalendar.cs
new file mode 100644
index 0000000000..c5161138de
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TaiwanCalendar.cs
@@ -0,0 +1,281 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Globalization
+{
+ /*=================================TaiwanCalendar==========================
+ **
+ ** Taiwan calendar is based on the Gregorian calendar. And the year is an offset to Gregorian calendar.
+ ** That is,
+ ** Taiwan year = Gregorian year - 1911. So 1912/01/01 A.D. is Taiwan 1/01/01
+ **
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1912/01/01 9999/12/31
+ ** Taiwan 01/01/01 8088/12/31
+ ============================================================================*/
+
+ public class TaiwanCalendar : Calendar
+ {
+ //
+ // The era value for the current era.
+ //
+
+ // Since
+ // Gregorian Year = Era Year + yearOffset
+ // When Gregorian Year 1912 is year 1, so that
+ // 1912 = 1 + yearOffset
+ // So yearOffset = 1911
+ //m_EraInfo[0] = new EraInfo(1, new DateTime(1912, 1, 1).Ticks, 1911, 1, GregorianCalendar.MaxYear - 1911);
+
+ // Initialize our era info.
+ internal static EraInfo[] taiwanEraInfo = new EraInfo[] {
+ new EraInfo( 1, 1912, 1, 1, 1911, 1, GregorianCalendar.MaxYear - 1911) // era #, start year/month/day, yearOffset, minEraYear
+ };
+
+ internal static volatile Calendar s_defaultInstance;
+
+ internal GregorianCalendarHelper helper;
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of TaiwanCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ internal static Calendar GetDefaultInstance()
+ {
+ if (s_defaultInstance == null)
+ {
+ s_defaultInstance = new TaiwanCalendar();
+ }
+ return (s_defaultInstance);
+ }
+
+ internal static readonly DateTime calendarMinValue = new DateTime(1912, 1, 1);
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ // Return the type of the Taiwan calendar.
+ //
+
+ public TaiwanCalendar()
+ {
+ try
+ {
+ new CultureInfo("zh-TW");
+ }
+ catch (ArgumentException e)
+ {
+ throw new TypeInitializationException(this.GetType().ToString(), e);
+ }
+ helper = new GregorianCalendarHelper(this, taiwanEraInfo);
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return CalendarId.TAIWAN;
+ }
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ return (helper.AddMonths(time, months));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (helper.AddYears(time, years));
+ }
+
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ return (helper.GetDaysInMonth(year, month, era));
+ }
+
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ return (helper.GetDaysInYear(year, era));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (helper.GetDayOfMonth(time));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return (helper.GetDayOfWeek(time));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (helper.GetDayOfYear(time));
+ }
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ return (helper.GetMonthsInYear(year, era));
+ }
+
+
+ public override int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ return (helper.GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+
+ public override int GetEra(DateTime time)
+ {
+ return (helper.GetEra(time));
+ }
+
+ public override int GetMonth(DateTime time)
+ {
+ return (helper.GetMonth(time));
+ }
+
+
+ public override int GetYear(DateTime time)
+ {
+ return (helper.GetYear(time));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ return (helper.IsLeapDay(year, month, day, era));
+ }
+
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ return (helper.IsLeapYear(year, era));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ return (helper.GetLeapMonth(year, era));
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ return (helper.IsLeapMonth(year, month, era));
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ return (helper.ToDateTime(year, month, day, hour, minute, second, millisecond, era));
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (helper.Eras);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 99;
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ helper.MaxYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+ // For Taiwan calendar, four digit year is not used.
+ // Therefore, for any two digit number, we just return the original number.
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedPosNum);
+ }
+
+ if (year > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ helper.MaxYear));
+ }
+ return (year);
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TaiwanLunisolarCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/TaiwanLunisolarCalendar.cs
new file mode 100644
index 0000000000..60e84f7f33
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TaiwanLunisolarCalendar.cs
@@ -0,0 +1,322 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Globalization
+{
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1912/02/18 2051/02/10
+ ** TaiwanLunisolar 1912/01/01 2050/13/29
+ */
+
+ public class TaiwanLunisolarCalendar : EastAsianLunisolarCalendar
+ {
+ // Since
+ // Gregorian Year = Era Year + yearOffset
+ // When Gregorian Year 1912 is year 1, so that
+ // 1912 = 1 + yearOffset
+ // So yearOffset = 1911
+ //m_EraInfo[0] = new EraInfo(1, new DateTime(1912, 1, 1).Ticks, 1911, 1, GregorianCalendar.MaxYear - 1911);
+
+ // Initialize our era info.
+ internal static EraInfo[] taiwanLunisolarEraInfo = new EraInfo[] {
+ new EraInfo( 1, 1912, 1, 1, 1911, 1, GregorianCalendar.MaxYear - 1911) // era #, start year/month/day, yearOffset, minEraYear
+ };
+
+ internal GregorianCalendarHelper helper;
+
+ internal const int MIN_LUNISOLAR_YEAR = 1912;
+ internal const int MAX_LUNISOLAR_YEAR = 2050;
+
+ internal const int MIN_GREGORIAN_YEAR = 1912;
+ internal const int MIN_GREGORIAN_MONTH = 2;
+ internal const int MIN_GREGORIAN_DAY = 18;
+
+ internal const int MAX_GREGORIAN_YEAR = 2051;
+ internal const int MAX_GREGORIAN_MONTH = 2;
+ internal const int MAX_GREGORIAN_DAY = 10;
+
+ internal static DateTime minDate = new DateTime(MIN_GREGORIAN_YEAR, MIN_GREGORIAN_MONTH, MIN_GREGORIAN_DAY);
+ internal static DateTime maxDate = new DateTime((new DateTime(MAX_GREGORIAN_YEAR, MAX_GREGORIAN_MONTH, MAX_GREGORIAN_DAY, 23, 59, 59, 999)).Ticks + 9999);
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // 1911 from ChineseLunisolarCalendar
+ return 384;
+ }
+ }
+
+ private static readonly int[,] s_yinfo =
+ {
+ /*Y LM Lmon Lday DaysPerMonth D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 #Days
+ 1912 */
+ { 0 , 2 , 18 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1913 */{ 0 , 2 , 6 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1914 */{ 5 , 1 , 26 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1915 */{ 0 , 2 , 14 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1916 */{ 0 , 2 , 3 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 0 355
+1917 */{ 2 , 1 , 23 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1918 */{ 0 , 2 , 11 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1919 */{ 7 , 2 , 1 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1920 */{ 0 , 2 , 20 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1921 */{ 0 , 2 , 8 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1922 */{ 5 , 1 , 28 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1923 */{ 0 , 2 , 16 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1924 */{ 0 , 2 , 5 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1925 */{ 4 , 1 , 24 , 44456 },/* 30 29 30 29 30 30 29 30 30 29 30 29 30 385
+1926 */{ 0 , 2 , 13 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1927 */{ 0 , 2 , 2 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1928 */{ 2 , 1 , 23 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1929 */{ 0 , 2 , 10 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1930 */{ 6 , 1 , 30 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1931 */{ 0 , 2 , 17 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1932 */{ 0 , 2 , 6 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1933 */{ 5 , 1 , 26 , 27976 },/* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1934 */{ 0 , 2 , 14 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1935 */{ 0 , 2 , 4 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1936 */{ 3 , 1 , 24 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1937 */{ 0 , 2 , 11 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1938 */{ 7 , 1 , 31 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1939 */{ 0 , 2 , 19 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1940 */{ 0 , 2 , 8 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1941 */{ 6 , 1 , 27 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1942 */{ 0 , 2 , 15 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1943 */{ 0 , 2 , 5 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1944 */{ 4 , 1 , 25 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+1945 */{ 0 , 2 , 13 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1946 */{ 0 , 2 , 2 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+1947 */{ 2 , 1 , 22 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+1948 */{ 0 , 2 , 10 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1949 */{ 7 , 1 , 29 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+1950 */{ 0 , 2 , 17 , 27808 },/* 29 30 30 29 30 30 29 29 30 29 30 29 0 354
+1951 */{ 0 , 2 , 6 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1952 */{ 5 , 1 , 27 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+1953 */{ 0 , 2 , 14 , 19872 },/* 29 30 29 29 30 30 29 30 30 29 30 29 0 354
+1954 */{ 0 , 2 , 3 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+1955 */{ 3 , 1 , 24 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1956 */{ 0 , 2 , 12 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1957 */{ 8 , 1 , 31 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1958 */{ 0 , 2 , 18 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 0 355
+1959 */{ 0 , 2 , 8 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1960 */{ 6 , 1 , 28 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1961 */{ 0 , 2 , 15 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1962 */{ 0 , 2 , 5 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1963 */{ 4 , 1 , 25 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1964 */{ 0 , 2 , 13 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1965 */{ 0 , 2 , 2 , 21088 },/* 29 30 29 30 29 29 30 29 29 30 30 29 0 353
+1966 */{ 3 , 1 , 21 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1967 */{ 0 , 2 , 9 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1968 */{ 7 , 1 , 30 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1969 */{ 0 , 2 , 17 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1970 */{ 0 , 2 , 6 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1971 */{ 5 , 1 , 27 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1972 */{ 0 , 2 , 15 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+1973 */{ 0 , 2 , 3 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1974 */{ 4 , 1 , 23 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1975 */{ 0 , 2 , 11 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1976 */{ 8 , 1 , 31 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1977 */{ 0 , 2 , 18 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1978 */{ 0 , 2 , 7 , 46752 },/* 30 29 30 30 29 30 30 29 30 29 30 29 0 355
+1979 */{ 6 , 1 , 28 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1980 */{ 0 , 2 , 16 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1981 */{ 0 , 2 , 5 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1982 */{ 4 , 1 , 25 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1983 */{ 0 , 2 , 13 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1984 */{ 10 , 2 , 2 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1985 */{ 0 , 2 , 20 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1986 */{ 0 , 2 , 9 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1987 */{ 6 , 1 , 29 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 29 384
+1988 */{ 0 , 2 , 17 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1989 */{ 0 , 2 , 6 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1990 */{ 5 , 1 , 27 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1991 */{ 0 , 2 , 15 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1992 */{ 0 , 2 , 4 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1993 */{ 3 , 1 , 23 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1994 */{ 0 , 2 , 10 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1995 */{ 8 , 1 , 31 , 27432 },/* 29 30 30 29 30 29 30 30 29 29 30 29 30 384
+1996 */{ 0 , 2 , 19 , 23232 },/* 29 30 29 30 30 29 30 29 30 30 29 29 0 354
+1997 */{ 0 , 2 , 7 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1998 */{ 5 , 1 , 28 , 37736 },/* 30 29 29 30 29 29 30 30 29 30 30 29 30 384
+1999 */{ 0 , 2 , 16 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2000 */{ 0 , 2 , 5 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2001 */{ 4 , 1 , 24 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+2002 */{ 0 , 2 , 12 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2003 */{ 0 , 2 , 1 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2004 */{ 2 , 1 , 22 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2005 */{ 0 , 2 , 9 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+2006 */{ 7 , 1 , 29 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+2007 */{ 0 , 2 , 18 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+2008 */{ 0 , 2 , 7 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+2009 */{ 5 , 1 , 26 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+2010 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2011 */{ 0 , 2 , 3 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2012 */{ 4 , 1 , 23 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+2013 */{ 0 , 2 , 10 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2014 */{ 9 , 1 , 31 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2015 */{ 0 , 2 , 19 , 19360 },/* 29 30 29 29 30 29 30 30 30 29 30 29 0 354
+2016 */{ 0 , 2 , 8 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2017 */{ 6 , 1 , 28 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+2018 */{ 0 , 2 , 16 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2019 */{ 0 , 2 , 5 , 43312 },/* 30 29 30 29 30 29 29 30 29 29 30 30 0 354
+2020 */{ 4 , 1 , 25 , 29864 },/* 29 30 30 30 29 30 29 29 30 29 30 29 30 384
+2021 */{ 0 , 2 , 12 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2022 */{ 0 , 2 , 1 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2023 */{ 2 , 1 , 22 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+2024 */{ 0 , 2 , 10 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2025 */{ 6 , 1 , 29 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+2026 */{ 0 , 2 , 17 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+2027 */{ 0 , 2 , 6 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+2028 */{ 5 , 1 , 26 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+2029 */{ 0 , 2 , 13 , 54576 },/* 30 30 29 30 29 30 29 30 29 29 30 30 0 355
+2030 */{ 0 , 2 , 3 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+2031 */{ 3 , 1 , 23 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+2032 */{ 0 , 2 , 11 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2033 */{ 11 , 1 , 31 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+2034 */{ 0 , 2 , 19 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+2035 */{ 0 , 2 , 8 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2036 */{ 6 , 1 , 28 , 53848 },/* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+2037 */{ 0 , 2 , 15 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+2038 */{ 0 , 2 , 4 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2039 */{ 5 , 1 , 24 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 29 384
+2040 */{ 0 , 2 , 12 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 0 355
+2041 */{ 0 , 2 , 1 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+2042 */{ 2 , 1 , 22 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+2043 */{ 0 , 2 , 10 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+2044 */{ 7 , 1 , 30 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+2045 */{ 0 , 2 , 17 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+2046 */{ 0 , 2 , 6 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+2047 */{ 5 , 1 , 26 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+2048 */{ 0 , 2 , 14 , 27936 },/* 29 30 30 29 30 30 29 30 29 29 30 29 0 354
+2049 */{ 0 , 2 , 2 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+2050 */{ 3 , 1 , 23 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+ */};
+
+
+ internal override int MinCalendarYear
+ {
+ get
+ {
+ return (MIN_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override int MaxCalendarYear
+ {
+ get
+ {
+ return (MAX_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override DateTime MinDate
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ internal override DateTime MaxDate
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ internal override EraInfo[] CalEraInfo
+ {
+ get
+ {
+ return (taiwanLunisolarEraInfo);
+ }
+ }
+
+ internal override int GetYearInfo(int lunarYear, int index)
+ {
+ if ((lunarYear < MIN_LUNISOLAR_YEAR) || (lunarYear > MAX_LUNISOLAR_YEAR))
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MIN_LUNISOLAR_YEAR,
+ MAX_LUNISOLAR_YEAR));
+ }
+
+ return s_yinfo[lunarYear - MIN_LUNISOLAR_YEAR, index];
+ }
+
+ internal override int GetYear(int year, DateTime time)
+ {
+ return helper.GetYear(year, time);
+ }
+
+ internal override int GetGregorianYear(int year, int era)
+ {
+ return helper.GetGregorianYear(year, era);
+ }
+
+ public TaiwanLunisolarCalendar()
+ {
+ helper = new GregorianCalendarHelper(this, taiwanLunisolarEraInfo);
+ }
+
+ public override int GetEra(DateTime time)
+ {
+ return (helper.GetEra(time));
+ }
+
+ internal override CalendarId BaseCalendarID
+ {
+ get
+ {
+ return (CalendarId.TAIWAN);
+ }
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.TAIWANLUNISOLAR);
+ }
+ }
+
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (helper.Eras);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TextElementEnumerator.cs b/src/System.Private.CoreLib/shared/System/Globalization/TextElementEnumerator.cs
new file mode 100644
index 0000000000..8b0f102a77
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TextElementEnumerator.cs
@@ -0,0 +1,117 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose:
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Collections;
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ //
+ // This is public because GetTextElement() is public.
+ //
+
+ public class TextElementEnumerator : IEnumerator
+ {
+ private String _str;
+ private int _index;
+ private int _startIndex;
+
+ private int _strLen; // This is the length of the total string, counting from the beginning of string.
+
+ private int _currTextElementLen; // The current text element lenght after MoveNext() is called.
+
+ private UnicodeCategory _uc;
+
+ private int _charLen; // The next abstract char to look at after MoveNext() is called. It could be 1 or 2, depending on if it is a surrogate or not.
+
+ internal TextElementEnumerator(String str, int startIndex, int strLen)
+ {
+ Debug.Assert(str != null, "TextElementEnumerator(): str != null");
+ Debug.Assert(startIndex >= 0 && strLen >= 0, "TextElementEnumerator(): startIndex >= 0 && strLen >= 0");
+ Debug.Assert(strLen >= startIndex, "TextElementEnumerator(): strLen >= startIndex");
+ _str = str;
+ _startIndex = startIndex;
+ _strLen = strLen;
+ Reset();
+ }
+
+ public bool MoveNext()
+ {
+ if (_index >= _strLen)
+ {
+ // Make the _index to be greater than _strLen so that we can throw exception if GetTextElement() is called.
+ _index = _strLen + 1;
+ return (false);
+ }
+ _currTextElementLen = StringInfo.GetCurrentTextElementLen(_str, _index, _strLen, ref _uc, ref _charLen);
+ _index += _currTextElementLen;
+ return (true);
+ }
+
+ //
+ // Get the current text element.
+ //
+
+ public Object Current
+ {
+ get
+ {
+ return (GetTextElement());
+ }
+ }
+
+ //
+ // Get the current text element.
+ //
+
+ public String GetTextElement()
+ {
+ if (_index == _startIndex)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
+ }
+ if (_index > _strLen)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
+ }
+
+ return (_str.Substring(_index - _currTextElementLen, _currTextElementLen));
+ }
+
+ //
+ // Get the starting index of the current text element.
+ //
+
+ public int ElementIndex
+ {
+ get
+ {
+ if (_index == _startIndex)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
+ }
+ return (_index - _currTextElementLen);
+ }
+ }
+
+
+ public void Reset()
+ {
+ _index = _startIndex;
+ if (_index < _strLen)
+ {
+ // If we have more than 1 character, get the category of the current char.
+ _uc = CharUnicodeInfo.InternalGetUnicodeCategory(_str, _index, out _charLen);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs
new file mode 100644
index 0000000000..c431e462b5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+
+namespace System.Globalization
+{
+ public partial class TextInfo
+ {
+ private Tristate _needsTurkishCasing = Tristate.NotInitialized;
+
+ private void FinishInitialization() { }
+
+ // -----------------------------
+ // ---- PAL layer ends here ----
+ // -----------------------------
+
+ private bool NeedsTurkishCasing(string localeName)
+ {
+ Debug.Assert(localeName != null);
+
+ return CultureInfo.GetCultureInfo(localeName).CompareInfo.Compare("\u0131", "I", CompareOptions.IgnoreCase) == 0;
+ }
+
+ private bool IsInvariant { get { return _cultureName.Length == 0; } }
+
+ internal unsafe void ChangeCase(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper)
+ {
+ Debug.Assert(!_invariantMode);
+
+ if (IsInvariant)
+ {
+ Interop.Globalization.ChangeCaseInvariant(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
+ }
+ else
+ {
+ if (_needsTurkishCasing == Tristate.NotInitialized)
+ {
+ _needsTurkishCasing = NeedsTurkishCasing(_textInfoName) ? Tristate.True : Tristate.False;
+ }
+ if (_needsTurkishCasing == Tristate.True)
+ {
+ Interop.Globalization.ChangeCaseTurkish(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
+ }
+ else
+ {
+ Interop.Globalization.ChangeCase(src, srcLen, dstBuffer, dstBufferCapacity, bToUpper);
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Windows.cs
new file mode 100644
index 0000000000..6e5e321002
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Windows.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ public partial class TextInfo
+ {
+ private unsafe void FinishInitialization()
+ {
+ if (_invariantMode)
+ {
+ _sortHandle = IntPtr.Zero;
+ return;
+ }
+
+ const uint LCMAP_SORTHANDLE = 0x20000000;
+
+ IntPtr handle;
+ int ret = Interop.Kernel32.LCMapStringEx(_textInfoName, LCMAP_SORTHANDLE, null, 0, &handle, IntPtr.Size, null, null, IntPtr.Zero);
+ _sortHandle = ret > 0 ? handle : IntPtr.Zero;
+ }
+
+ private unsafe void ChangeCase(char* pSource, int pSourceLen, char* pResult, int pResultLen, bool toUpper)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(pSource != null);
+ Debug.Assert(pResult != null);
+ Debug.Assert(pSourceLen >= 0);
+ Debug.Assert(pResultLen >= 0);
+ Debug.Assert(pSourceLen <= pResultLen);
+
+ // Check for Invariant to avoid A/V in LCMapStringEx
+ uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING;
+
+ int ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName,
+ linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE),
+ pSource,
+ pSourceLen,
+ pResult,
+ pSourceLen,
+ null,
+ null,
+ _sortHandle);
+ if (ret == 0)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ }
+
+ Debug.Assert(ret == pSourceLen, "Expected getting the same length of the original string");
+ }
+
+ // PAL Ends here
+
+ private IntPtr _sortHandle;
+
+ private const uint LCMAP_LINGUISTIC_CASING = 0x01000000;
+ private const uint LCMAP_LOWERCASE = 0x00000100;
+ private const uint LCMAP_UPPERCASE = 0x00000200;
+
+ private static bool IsInvariantLocale(string localeName) => localeName == "";
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.cs
new file mode 100644
index 0000000000..183f02ccce
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.cs
@@ -0,0 +1,904 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This Class defines behaviors specific to a writing system.
+// A writing system is the collection of scripts and
+// orthographic rules required to represent a language as text.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace System.Globalization
+{
+ public partial class TextInfo : ICloneable, IDeserializationCallback
+ {
+ private enum Tristate : byte
+ {
+ NotInitialized,
+ True,
+ False,
+ }
+
+ private string _listSeparator;
+ private bool _isReadOnly = false;
+
+ /* _cultureName is the name of the creating culture.
+ _cultureData is the data that backs this class.
+ _textInfoName is the actual name of the textInfo (from cultureData.STEXTINFO)
+ In the desktop, when we call the sorting dll, it doesn't
+ know how to resolve custom locle names to sort ids so we have to have already resolved this.
+ */
+
+ private readonly string _cultureName; // Name of the culture that created this text info
+ private readonly CultureData _cultureData; // Data record for the culture that made us, not for this textinfo
+ private readonly string _textInfoName; // Name of the text info we're using (ie: _cultureData.STEXTINFO)
+
+ private Tristate _isAsciiCasingSameAsInvariant = Tristate.NotInitialized;
+
+ // _invariantMode is defined for the perf reason as accessing the instance field is faster than access the static property GlobalizationMode.Invariant
+ private readonly bool _invariantMode = GlobalizationMode.Invariant;
+
+ // Invariant text info
+ internal static TextInfo Invariant
+ {
+ get
+ {
+ if (s_Invariant == null)
+ s_Invariant = new TextInfo(CultureData.Invariant);
+ return s_Invariant;
+ }
+ }
+ internal volatile static TextInfo s_Invariant;
+
+ //////////////////////////////////////////////////////////////////////////
+ ////
+ //// TextInfo Constructors
+ ////
+ //// Implements CultureInfo.TextInfo.
+ ////
+ //////////////////////////////////////////////////////////////////////////
+ internal TextInfo(CultureData cultureData)
+ {
+ // This is our primary data source, we don't need most of the rest of this
+ _cultureData = cultureData;
+ _cultureName = _cultureData.CultureName;
+ _textInfoName = _cultureData.STEXTINFO;
+
+ FinishInitialization();
+ }
+
+ void IDeserializationCallback.OnDeserialization(Object sender)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ public virtual int ANSICodePage => _cultureData.IDEFAULTANSICODEPAGE;
+
+ public virtual int OEMCodePage => _cultureData.IDEFAULTOEMCODEPAGE;
+
+ public virtual int MacCodePage => _cultureData.IDEFAULTMACCODEPAGE;
+
+ public virtual int EBCDICCodePage => _cultureData.IDEFAULTEBCDICCODEPAGE;
+
+ // Just use the LCID from our text info name
+ public int LCID => CultureInfo.GetCultureInfo(_textInfoName).LCID;
+
+ public string CultureName => _textInfoName;
+
+ public bool IsReadOnly => _isReadOnly;
+
+ //////////////////////////////////////////////////////////////////////////
+ ////
+ //// Clone
+ ////
+ //// Is the implementation of ICloneable.
+ ////
+ //////////////////////////////////////////////////////////////////////////
+ public virtual object Clone()
+ {
+ object o = MemberwiseClone();
+ ((TextInfo)o).SetReadOnlyState(false);
+ return o;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ReadOnly
+ //
+ // Create a cloned readonly instance or return the input one if it is
+ // readonly.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public static TextInfo ReadOnly(TextInfo textInfo)
+ {
+ if (textInfo == null) { throw new ArgumentNullException(nameof(textInfo)); }
+ if (textInfo.IsReadOnly) { return textInfo; }
+
+ TextInfo clonedTextInfo = (TextInfo)(textInfo.MemberwiseClone());
+ clonedTextInfo.SetReadOnlyState(true);
+
+ return clonedTextInfo;
+ }
+
+ private void VerifyWritable()
+ {
+ if (_isReadOnly)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ }
+ }
+
+ internal void SetReadOnlyState(bool readOnly)
+ {
+ _isReadOnly = readOnly;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ListSeparator
+ //
+ // Returns the string used to separate items in a list.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual string ListSeparator
+ {
+ get
+ {
+ if (_listSeparator == null)
+ {
+ _listSeparator = _cultureData.SLIST;
+ }
+ return _listSeparator;
+ }
+
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
+ }
+ VerifyWritable();
+ _listSeparator = value;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToLower
+ //
+ // Converts the character or string to lower case. Certain locales
+ // have different casing semantics from the file systems in Win32.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public unsafe virtual char ToLower(char c)
+ {
+ if (_invariantMode || (IsAscii(c) && IsAsciiCasingSameAsInvariant))
+ {
+ return ToLowerAsciiInvariant(c);
+ }
+
+ return ChangeCase(c, toUpper: false);
+ }
+
+ public unsafe virtual string ToLower(string str)
+ {
+ if (str == null) { throw new ArgumentNullException(nameof(str)); }
+
+ if (_invariantMode)
+ {
+ return ToLowerAsciiInvariant(str);
+ }
+
+ return ChangeCase(str, toUpper: false);
+ }
+
+ private unsafe char ChangeCase(char c, bool toUpper)
+ {
+ Debug.Assert(!_invariantMode);
+
+ char dst = default;
+ ChangeCase(&c, 1, &dst, 1, toUpper);
+ return dst;
+ }
+
+ private unsafe string ChangeCase(string source, bool toUpper)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(source != null);
+
+ // If the string is empty, we're done.
+ if (source.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ int sourcePos = 0;
+ string result = null;
+
+ // If this culture's casing for ASCII is the same as invariant, try to take
+ // a fast path that'll work in managed code and ASCII rather than calling out
+ // to the OS for culture-aware casing.
+ if (IsAsciiCasingSameAsInvariant)
+ {
+ if (toUpper)
+ {
+ // Loop through each character.
+ for (sourcePos = 0; sourcePos < source.Length; sourcePos++)
+ {
+ // If the character is lower-case, we're going to need to allocate a string.
+ char c = source[sourcePos];
+ if ((uint)(c - 'a') <= 'z' - 'a')
+ {
+ // Allocate the result string.
+ result = string.FastAllocateString(source.Length);
+ fixed (char* pResult = result)
+ {
+ // Store all of characters examined thus far.
+ if (sourcePos > 0)
+ {
+ source.AsSpan(0, sourcePos).CopyTo(new Span<char>(pResult, sourcePos));
+ }
+
+ // And store the current character, upper-cased.
+ char* d = pResult + sourcePos;
+ *d++ = (char)(c & ~0x20);
+ sourcePos++;
+
+ // Then continue looping through the remainder of the characters. If we hit
+ // a non-ASCII character, bail to fall back to culture-aware casing.
+ for (; sourcePos < source.Length; sourcePos++)
+ {
+ c = source[sourcePos];
+ if ((uint)(c - 'a') <= 'z' - 'a')
+ {
+ *d++ = (char)(c & ~0x20);
+ }
+ else if (!IsAscii(c))
+ {
+ break;
+ }
+ else
+ {
+ *d++ = c;
+ }
+ }
+ }
+
+ break;
+ }
+ else if (!IsAscii(c))
+ {
+ // The character isn't ASCII; bail to fall back to a culture-aware casing.
+ break;
+ }
+ }
+ }
+ else // toUpper == false
+ {
+ // Loop through each character.
+ for (sourcePos = 0; sourcePos < source.Length; sourcePos++)
+ {
+ // If the character is upper-case, we're going to need to allocate a string.
+ char c = source[sourcePos];
+ if ((uint)(c - 'A') <= 'Z' - 'A')
+ {
+ // Allocate the result string.
+ result = string.FastAllocateString(source.Length);
+ fixed (char* pResult = result)
+ {
+ // Store all of characters examined thus far.
+ if (sourcePos > 0)
+ {
+ source.AsSpan(0, sourcePos).CopyTo(new Span<char>(pResult, sourcePos));
+ }
+
+ // And store the current character, upper-cased.
+ char* d = pResult + sourcePos;
+ *d++ = (char)(c | 0x20);
+ sourcePos++;
+
+ // Then continue looping through the remainder of the characters. If we hit
+ // a non-ASCII character, bail to fall back to culture-aware casing.
+ for (; sourcePos < source.Length; sourcePos++)
+ {
+ c = source[sourcePos];
+ if ((uint)(c - 'A') <= 'Z' - 'A')
+ {
+ *d++ = (char)(c | 0x20);
+ }
+ else if (!IsAscii(c))
+ {
+ break;
+ }
+ else
+ {
+ *d++ = c;
+ }
+ }
+ }
+
+ break;
+ }
+ else if (!IsAscii(c))
+ {
+ // The character isn't ASCII; bail to fall back to a culture-aware casing.
+ break;
+ }
+ }
+ }
+
+ // If we successfully iterated through all of the characters, we didn't need to fall back
+ // to culture-aware casing. In that case, if we allocated a result string, use it, otherwise
+ // just return the original string, as no modifications were necessary.
+ if (sourcePos == source.Length)
+ {
+ return result ?? source;
+ }
+ }
+
+ // Falling back to culture-aware casing. Make sure we have a result string to write into.
+ // If we need to allocate the result string, we'll also need to copy over to it any
+ // characters already examined.
+ if (result == null)
+ {
+ result = string.FastAllocateString(source.Length);
+ if (sourcePos > 0)
+ {
+ fixed (char* pResult = result)
+ {
+ source.AsSpan(0, sourcePos).CopyTo(new Span<char>(pResult, sourcePos));
+ }
+ }
+ }
+
+ // Do the casing operation on everything after what we already processed.
+ fixed (char* pSource = source)
+ {
+ fixed (char* pResult = result)
+ {
+ ChangeCase(pSource + sourcePos, source.Length - sourcePos, pResult + sourcePos, result.Length - sourcePos, toUpper);
+ }
+ }
+
+ return result;
+ }
+
+ internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(destination.Length >= source.Length);
+
+ if (source.IsEmpty)
+ {
+ return;
+ }
+
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pResult = &MemoryMarshal.GetReference(destination))
+ {
+ if (IsAsciiCasingSameAsInvariant)
+ {
+ int length = 0;
+ char* a = pSource, b = pResult;
+ if (toUpper)
+ {
+ while (length < source.Length && *a < 0x80)
+ {
+ *b++ = ToUpperAsciiInvariant(*a++);
+ length++;
+ }
+ }
+ else
+ {
+ while (length < source.Length && *a < 0x80)
+ {
+ *b++ = ToLowerAsciiInvariant(*a++);
+ length++;
+ }
+ }
+
+ if (length != source.Length)
+ {
+ ChangeCase(a, source.Length - length, b, destination.Length - length, toUpper);
+ }
+ }
+ else
+ {
+ ChangeCase(pSource, source.Length, pResult, destination.Length, toUpper);
+ }
+ }
+ }
+
+ private static unsafe string ToLowerAsciiInvariant(string s)
+ {
+ if (s.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ fixed (char* pSource = s)
+ {
+ int i = 0;
+ while (i < s.Length)
+ {
+ if ((uint)(pSource[i] - 'A') <= (uint)('Z' - 'A'))
+ {
+ break;
+ }
+ i++;
+ }
+
+ if (i >= s.Length)
+ {
+ return s;
+ }
+
+ string result = string.FastAllocateString(s.Length);
+ fixed (char* pResult = result)
+ {
+ for (int j = 0; j < i; j++)
+ {
+ pResult[j] = pSource[j];
+ }
+
+ pResult[i] = (char)(pSource[i] | 0x20);
+ i++;
+
+ while (i < s.Length)
+ {
+ pResult[i] = ToLowerAsciiInvariant(pSource[i]);
+ i++;
+ }
+ }
+
+ return result;
+ }
+ }
+
+ internal static void ToLowerAsciiInvariant(ReadOnlySpan<char> source, Span<char> destination)
+ {
+ Debug.Assert(destination.Length >= source.Length);
+
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = ToLowerAsciiInvariant(source[i]);
+ }
+ }
+
+ private static unsafe string ToUpperAsciiInvariant(string s)
+ {
+ if (s.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ fixed (char* pSource = s)
+ {
+ int i = 0;
+ while (i < s.Length)
+ {
+ if ((uint)(pSource[i] - 'a') <= (uint)('z' - 'a'))
+ {
+ break;
+ }
+ i++;
+ }
+
+ if (i >= s.Length)
+ {
+ return s;
+ }
+
+ string result = string.FastAllocateString(s.Length);
+ fixed (char* pResult = result)
+ {
+ for (int j = 0; j < i; j++)
+ {
+ pResult[j] = pSource[j];
+ }
+
+ pResult[i] = (char)(pSource[i] & ~0x20);
+ i++;
+
+ while (i < s.Length)
+ {
+ pResult[i] = ToUpperAsciiInvariant(pSource[i]);
+ i++;
+ }
+ }
+
+ return result;
+ }
+ }
+
+ internal static void ToUpperAsciiInvariant(ReadOnlySpan<char> source, Span<char> destination)
+ {
+ Debug.Assert(destination.Length >= source.Length);
+
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = ToUpperAsciiInvariant(source[i]);
+ }
+ }
+
+ private static char ToLowerAsciiInvariant(char c)
+ {
+ if ((uint)(c - 'A') <= (uint)('Z' - 'A'))
+ {
+ c = (char)(c | 0x20);
+ }
+ return c;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToUpper
+ //
+ // Converts the character or string to upper case. Certain locales
+ // have different casing semantics from the file systems in Win32.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public unsafe virtual char ToUpper(char c)
+ {
+ if (_invariantMode || (IsAscii(c) && IsAsciiCasingSameAsInvariant))
+ {
+ return ToUpperAsciiInvariant(c);
+ }
+
+ return ChangeCase(c, toUpper: true);
+ }
+
+ public unsafe virtual string ToUpper(string str)
+ {
+ if (str == null) { throw new ArgumentNullException(nameof(str)); }
+
+ if (_invariantMode)
+ {
+ return ToUpperAsciiInvariant(str);
+ }
+
+ return ChangeCase(str, toUpper: true);
+ }
+
+ internal static char ToUpperAsciiInvariant(char c)
+ {
+ if ((uint)(c - 'a') <= (uint)('z' - 'a'))
+ {
+ c = (char)(c & ~0x20);
+ }
+ return c;
+ }
+
+ private static bool IsAscii(char c)
+ {
+ return c < 0x80;
+ }
+
+ private bool IsAsciiCasingSameAsInvariant
+ {
+ get
+ {
+ if (_isAsciiCasingSameAsInvariant == Tristate.NotInitialized)
+ {
+ _isAsciiCasingSameAsInvariant = CultureInfo.GetCultureInfo(_textInfoName).CompareInfo.Compare("abcdefghijklmnopqrstuvwxyz",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ CompareOptions.IgnoreCase) == 0 ? Tristate.True : Tristate.False;
+ }
+ return _isAsciiCasingSameAsInvariant == Tristate.True;
+ }
+ }
+
+ // IsRightToLeft
+ //
+ // Returns true if the dominant direction of text and UI such as the relative position of buttons and scroll bars
+ //
+ public bool IsRightToLeft => _cultureData.IsRightToLeft;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same CultureInfo as the current instance.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override bool Equals(Object obj)
+ {
+ TextInfo that = obj as TextInfo;
+
+ if (that != null)
+ {
+ return CultureName.Equals(that.CultureName);
+ }
+
+ return false;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // CultureInfo. The hash code is guaranteed to be the same for CultureInfo A
+ // and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override int GetHashCode()
+ {
+ return CultureName.GetHashCode();
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns a string describing the
+ // TextInfo.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override string ToString()
+ {
+ return "TextInfo - " + _cultureData.CultureName;
+ }
+
+ //
+ // Titlecasing:
+ // -----------
+ // Titlecasing refers to a casing practice wherein the first letter of a word is an uppercase letter
+ // and the rest of the letters are lowercase. The choice of which words to titlecase in headings
+ // and titles is dependent on language and local conventions. For example, "The Merry Wives of Windor"
+ // is the appropriate titlecasing of that play's name in English, with the word "of" not titlecased.
+ // In German, however, the title is "Die lustigen Weiber von Windsor," and both "lustigen" and "von"
+ // are not titlecased. In French even fewer words are titlecased: "Les joyeuses commeres de Windsor."
+ //
+ // Moreover, the determination of what actually constitutes a word is language dependent, and this can
+ // influence which letter or letters of a "word" are uppercased when titlecasing strings. For example
+ // "l'arbre" is considered two words in French, whereas "can't" is considered one word in English.
+ //
+ public unsafe string ToTitleCase(string str)
+ {
+ if (str == null)
+ {
+ throw new ArgumentNullException(nameof(str));
+ }
+ if (str.Length == 0)
+ {
+ return str;
+ }
+
+ StringBuilder result = new StringBuilder();
+ string lowercaseData = null;
+ // Store if the current culture is Dutch (special case)
+ bool isDutchCulture = CultureName.StartsWith("nl-", StringComparison.OrdinalIgnoreCase);
+
+ for (int i = 0; i < str.Length; i++)
+ {
+ UnicodeCategory charType;
+ int charLen;
+
+ charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen);
+ if (char.CheckLetter(charType))
+ {
+ // Special case to check for Dutch specific titlecasing with "IJ" characters
+ // at the beginning of a word
+ if (isDutchCulture && i < str.Length - 1 && (str[i] == 'i' || str[i] == 'I') && (str[i+1] == 'j' || str[i+1] == 'J'))
+ {
+ result.Append("IJ");
+ i += 2;
+ }
+ else
+ {
+ // Do the titlecasing for the first character of the word.
+ i = AddTitlecaseLetter(ref result, ref str, i, charLen) + 1;
+ }
+
+ //
+ // Convert the characters until the end of the this word
+ // to lowercase.
+ //
+ int lowercaseStart = i;
+
+ //
+ // Use hasLowerCase flag to prevent from lowercasing acronyms (like "URT", "USA", etc)
+ // This is in line with Word 2000 behavior of titlecasing.
+ //
+ bool hasLowerCase = (charType == UnicodeCategory.LowercaseLetter);
+ // Use a loop to find all of the other letters following this letter.
+ while (i < str.Length)
+ {
+ charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen);
+ if (IsLetterCategory(charType))
+ {
+ if (charType == UnicodeCategory.LowercaseLetter)
+ {
+ hasLowerCase = true;
+ }
+ i += charLen;
+ }
+ else if (str[i] == '\'')
+ {
+ i++;
+ if (hasLowerCase)
+ {
+ if (lowercaseData == null)
+ {
+ lowercaseData = ToLower(str);
+ }
+ result.Append(lowercaseData, lowercaseStart, i - lowercaseStart);
+ }
+ else
+ {
+ result.Append(str, lowercaseStart, i - lowercaseStart);
+ }
+ lowercaseStart = i;
+ hasLowerCase = true;
+ }
+ else if (!IsWordSeparator(charType))
+ {
+ // This category is considered to be part of the word.
+ // This is any category that is marked as false in wordSeprator array.
+ i+= charLen;
+ }
+ else
+ {
+ // A word separator. Break out of the loop.
+ break;
+ }
+ }
+
+ int count = i - lowercaseStart;
+
+ if (count > 0)
+ {
+ if (hasLowerCase)
+ {
+ if (lowercaseData == null)
+ {
+ lowercaseData = ToLower(str);
+ }
+ result.Append(lowercaseData, lowercaseStart, count);
+ }
+ else
+ {
+ result.Append(str, lowercaseStart, count);
+ }
+ }
+
+ if (i < str.Length)
+ {
+ // not a letter, just append it
+ i = AddNonLetter(ref result, ref str, i, charLen);
+ }
+ }
+ else
+ {
+ // not a letter, just append it
+ i = AddNonLetter(ref result, ref str, i, charLen);
+ }
+ }
+ return result.ToString();
+ }
+
+ private static int AddNonLetter(ref StringBuilder result, ref string input, int inputIndex, int charLen)
+ {
+ Debug.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddNonLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!");
+ if (charLen == 2)
+ {
+ // Surrogate pair
+ result.Append(input[inputIndex++]);
+ result.Append(input[inputIndex]);
+ }
+ else
+ {
+ result.Append(input[inputIndex]);
+ }
+ return inputIndex;
+ }
+
+ private int AddTitlecaseLetter(ref StringBuilder result, ref string input, int inputIndex, int charLen)
+ {
+ Debug.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddTitlecaseLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!");
+
+ // for surrogate pairs do a simple ToUpper operation on the substring
+ if (charLen == 2)
+ {
+ // Surrogate pair
+ result.Append(ToUpper(input.Substring(inputIndex, charLen)));
+ inputIndex++;
+ }
+ else
+ {
+ switch (input[inputIndex])
+ {
+ //
+ // For AppCompat, the Titlecase Case Mapping data from NDP 2.0 is used below.
+ case (char) 0x01C4: // DZ with Caron -> Dz with Caron
+ case (char) 0x01C5: // Dz with Caron -> Dz with Caron
+ case (char) 0x01C6: // dz with Caron -> Dz with Caron
+ result.Append((char) 0x01C5);
+ break;
+ case (char) 0x01C7: // LJ -> Lj
+ case (char) 0x01C8: // Lj -> Lj
+ case (char) 0x01C9: // lj -> Lj
+ result.Append((char) 0x01C8);
+ break;
+ case (char) 0x01CA: // NJ -> Nj
+ case (char) 0x01CB: // Nj -> Nj
+ case (char) 0x01CC: // nj -> Nj
+ result.Append((char) 0x01CB);
+ break;
+ case (char) 0x01F1: // DZ -> Dz
+ case (char) 0x01F2: // Dz -> Dz
+ case (char) 0x01F3: // dz -> Dz
+ result.Append((char) 0x01F2);
+ break;
+ default:
+ result.Append(ToUpper(input[inputIndex]));
+ break;
+ }
+ }
+ return inputIndex;
+ }
+
+ //
+ // Used in ToTitleCase():
+ // When we find a starting letter, the following array decides if a category should be
+ // considered as word seprator or not.
+ //
+ private const int c_wordSeparatorMask =
+ /* false */ (0 << 0) | // UppercaseLetter = 0,
+ /* false */ (0 << 1) | // LowercaseLetter = 1,
+ /* false */ (0 << 2) | // TitlecaseLetter = 2,
+ /* false */ (0 << 3) | // ModifierLetter = 3,
+ /* false */ (0 << 4) | // OtherLetter = 4,
+ /* false */ (0 << 5) | // NonSpacingMark = 5,
+ /* false */ (0 << 6) | // SpacingCombiningMark = 6,
+ /* false */ (0 << 7) | // EnclosingMark = 7,
+ /* false */ (0 << 8) | // DecimalDigitNumber = 8,
+ /* false */ (0 << 9) | // LetterNumber = 9,
+ /* false */ (0 << 10) | // OtherNumber = 10,
+ /* true */ (1 << 11) | // SpaceSeparator = 11,
+ /* true */ (1 << 12) | // LineSeparator = 12,
+ /* true */ (1 << 13) | // ParagraphSeparator = 13,
+ /* true */ (1 << 14) | // Control = 14,
+ /* true */ (1 << 15) | // Format = 15,
+ /* false */ (0 << 16) | // Surrogate = 16,
+ /* false */ (0 << 17) | // PrivateUse = 17,
+ /* true */ (1 << 18) | // ConnectorPunctuation = 18,
+ /* true */ (1 << 19) | // DashPunctuation = 19,
+ /* true */ (1 << 20) | // OpenPunctuation = 20,
+ /* true */ (1 << 21) | // ClosePunctuation = 21,
+ /* true */ (1 << 22) | // InitialQuotePunctuation = 22,
+ /* true */ (1 << 23) | // FinalQuotePunctuation = 23,
+ /* true */ (1 << 24) | // OtherPunctuation = 24,
+ /* true */ (1 << 25) | // MathSymbol = 25,
+ /* true */ (1 << 26) | // CurrencySymbol = 26,
+ /* true */ (1 << 27) | // ModifierSymbol = 27,
+ /* true */ (1 << 28) | // OtherSymbol = 28,
+ /* false */ (0 << 29); // OtherNotAssigned = 29;
+
+ private static bool IsWordSeparator(UnicodeCategory category)
+ {
+ return (c_wordSeparatorMask & (1 << (int) category)) != 0;
+ }
+
+ private static bool IsLetterCategory(UnicodeCategory uc)
+ {
+ return (uc == UnicodeCategory.UppercaseLetter
+ || uc == UnicodeCategory.LowercaseLetter
+ || uc == UnicodeCategory.TitlecaseLetter
+ || uc == UnicodeCategory.ModifierLetter
+ || uc == UnicodeCategory.OtherLetter);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/ThaiBuddhistCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/ThaiBuddhistCalendar.cs
new file mode 100644
index 0000000000..4f8fd8f019
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/ThaiBuddhistCalendar.cs
@@ -0,0 +1,232 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Globalization
+{
+ /*=================================ThaiBuddhistCalendar==========================
+ **
+ ** ThaiBuddhistCalendar is based on Gregorian calendar. Its year value has
+ ** an offset to the Gregorain calendar.
+ **
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 0001/01/01 9999/12/31
+ ** Thai 0544/01/01 10542/12/31
+ ============================================================================*/
+
+ public class ThaiBuddhistCalendar : Calendar
+ {
+ // Initialize our era info.
+ internal static EraInfo[] thaiBuddhistEraInfo = new EraInfo[] {
+ new EraInfo( 1, 1, 1, 1, -543, 544, GregorianCalendar.MaxYear + 543) // era #, start year/month/day, yearOffset, minEraYear
+ };
+
+ //
+ // The era value for the current era.
+ //
+
+ public const int ThaiBuddhistEra = 1;
+
+ internal GregorianCalendarHelper helper;
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ public ThaiBuddhistCalendar()
+ {
+ helper = new GregorianCalendarHelper(this, thaiBuddhistEraInfo);
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.THAI);
+ }
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ return (helper.AddMonths(time, months));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (helper.AddYears(time, years));
+ }
+
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ return (helper.GetDaysInMonth(year, month, era));
+ }
+
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ return (helper.GetDaysInYear(year, era));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (helper.GetDayOfMonth(time));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return (helper.GetDayOfWeek(time));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (helper.GetDayOfYear(time));
+ }
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ return (helper.GetMonthsInYear(year, era));
+ }
+
+
+ public override int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ return (helper.GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+
+ public override int GetEra(DateTime time)
+ {
+ return (helper.GetEra(time));
+ }
+
+ public override int GetMonth(DateTime time)
+ {
+ return (helper.GetMonth(time));
+ }
+
+
+ public override int GetYear(DateTime time)
+ {
+ return (helper.GetYear(time));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ return (helper.IsLeapDay(year, month, day, era));
+ }
+
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ return (helper.IsLeapYear(year, era));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ return (helper.GetLeapMonth(year, era));
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ return (helper.IsLeapMonth(year, month, era));
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ return (helper.ToDateTime(year, month, day, hour, minute, second, millisecond, era));
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (helper.Eras);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 2572;
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ if (value < 99 || value > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ helper.MaxYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ return (helper.ToFourDigitYear(year, this.TwoDigitYearMax));
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanFormat.cs b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanFormat.cs
new file mode 100644
index 0000000000..a66e4600aa
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanFormat.cs
@@ -0,0 +1,537 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Globalization
+{
+ internal static class TimeSpanFormat
+ {
+ private static unsafe void AppendNonNegativeInt32(StringBuilder sb, int n, int digits)
+ {
+ Debug.Assert(n >= 0);
+ uint value = (uint)n;
+
+ const int MaxUInt32Digits = 10;
+ char* buffer = stackalloc char[MaxUInt32Digits];
+
+ int index = 0;
+ do
+ {
+ uint div = value / 10;
+ buffer[index++] = (char)(value - (div * 10) + '0');
+ value = div;
+ }
+ while (value != 0);
+ Debug.Assert(index <= MaxUInt32Digits);
+
+ for (int i = digits - index; i > 0; --i) sb.Append('0');
+ for (int i = index - 1; i >= 0; --i) sb.Append(buffer[i]);
+ }
+
+ internal static readonly FormatLiterals PositiveInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.InitInvariant(isNegative: false);
+ internal static readonly FormatLiterals NegativeInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.InitInvariant(isNegative: true);
+
+ internal enum Pattern
+ {
+ None = 0,
+ Minimum = 1,
+ Full = 2,
+ }
+
+ /// <summary>Main method called from TimeSpan.ToString.</summary>
+ internal static string Format(TimeSpan value, string format, IFormatProvider formatProvider) =>
+ StringBuilderCache.GetStringAndRelease(FormatToBuilder(value, format, formatProvider));
+
+ /// <summary>Main method called from TimeSpan.TryFormat.</summary>
+ internal static bool TryFormat(TimeSpan value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider formatProvider)
+ {
+ StringBuilder sb = FormatToBuilder(value, format, formatProvider);
+ if (sb.Length <= destination.Length)
+ {
+ charsWritten = sb.Length;
+ sb.CopyTo(0, destination, sb.Length);
+ StringBuilderCache.Release(sb);
+ return true;
+ }
+ else
+ {
+ StringBuilderCache.Release(sb);
+ charsWritten = 0;
+ return false;
+ }
+ }
+
+ private static StringBuilder FormatToBuilder(TimeSpan value, ReadOnlySpan<char> format, IFormatProvider formatProvider)
+ {
+ if (format.Length == 0)
+ {
+ format = "c";
+ }
+
+ // Standard formats
+ if (format.Length == 1)
+ {
+ char f = format[0];
+ switch (f)
+ {
+ case 'c':
+ case 't':
+ case 'T':
+ return FormatStandard(
+ value,
+ isInvariant: true,
+ format: format,
+ pattern: Pattern.Minimum);
+
+ case 'g':
+ case 'G':
+ DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(formatProvider);
+ return FormatStandard(
+ value,
+ isInvariant: false,
+ format: value.Ticks < 0 ? dtfi.FullTimeSpanNegativePattern : dtfi.FullTimeSpanPositivePattern,
+ pattern: f == 'g' ? Pattern.Minimum : Pattern.Full);
+
+ default:
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ }
+
+ // Custom formats
+ return FormatCustomized(value, format, DateTimeFormatInfo.GetInstance(formatProvider), result: null);
+ }
+
+ /// <summary>Format the TimeSpan instance using the specified format.</summary>
+ private static StringBuilder FormatStandard(TimeSpan value, bool isInvariant, ReadOnlySpan<char> format, Pattern pattern)
+ {
+ StringBuilder sb = StringBuilderCache.Acquire(InternalGlobalizationHelper.StringBuilderDefaultCapacity);
+ int day = (int)(value.Ticks / TimeSpan.TicksPerDay);
+ long time = value.Ticks % TimeSpan.TicksPerDay;
+
+ if (value.Ticks < 0)
+ {
+ day = -day;
+ time = -time;
+ }
+ int hours = (int)(time / TimeSpan.TicksPerHour % 24);
+ int minutes = (int)(time / TimeSpan.TicksPerMinute % 60);
+ int seconds = (int)(time / TimeSpan.TicksPerSecond % 60);
+ int fraction = (int)(time % TimeSpan.TicksPerSecond);
+
+ FormatLiterals literal;
+ if (isInvariant)
+ {
+ literal = value.Ticks < 0 ?
+ NegativeInvariantFormatLiterals :
+ PositiveInvariantFormatLiterals;
+ }
+ else
+ {
+ literal = new FormatLiterals();
+ literal.Init(format, pattern == Pattern.Full);
+ }
+
+ if (fraction != 0)
+ {
+ // truncate the partial second to the specified length
+ fraction = (int)(fraction / TimeSpanParse.Pow10(DateTimeFormat.MaxSecondsFractionDigits - literal.ff));
+ }
+
+ // Pattern.Full: [-]dd.hh:mm:ss.fffffff
+ // Pattern.Minimum: [-][d.]hh:mm:ss[.fffffff]
+
+ sb.Append(literal.Start); // [-]
+ if (pattern == Pattern.Full || day != 0)
+ {
+ sb.Append(day); // [dd]
+ sb.Append(literal.DayHourSep); // [.]
+ } //
+ AppendNonNegativeInt32(sb, hours, literal.hh); // hh
+ sb.Append(literal.HourMinuteSep); // :
+ AppendNonNegativeInt32(sb, minutes, literal.mm); // mm
+ sb.Append(literal.MinuteSecondSep); // :
+ AppendNonNegativeInt32(sb, seconds, literal.ss); // ss
+ if (!isInvariant && pattern == Pattern.Minimum)
+ {
+ int effectiveDigits = literal.ff;
+ while (effectiveDigits > 0)
+ {
+ if (fraction % 10 == 0)
+ {
+ fraction = fraction / 10;
+ effectiveDigits--;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (effectiveDigits > 0)
+ {
+ sb.Append(literal.SecondFractionSep); // [.FFFFFFF]
+ sb.Append((fraction).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
+ }
+ }
+ else if (pattern == Pattern.Full || fraction != 0)
+ {
+ sb.Append(literal.SecondFractionSep); // [.]
+ AppendNonNegativeInt32(sb, fraction, literal.ff); // [fffffff]
+ }
+ sb.Append(literal.End);
+
+ return sb;
+ }
+
+ /// <summary>Format the TimeSpan instance using the specified format.</summary>
+ private static StringBuilder FormatCustomized(TimeSpan value, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, StringBuilder result)
+ {
+ Debug.Assert(dtfi != null);
+
+ bool resultBuilderIsPooled = false;
+ if (result == null)
+ {
+ result = StringBuilderCache.Acquire(InternalGlobalizationHelper.StringBuilderDefaultCapacity);
+ resultBuilderIsPooled = true;
+ }
+
+ int day = (int)(value.Ticks / TimeSpan.TicksPerDay);
+ long time = value.Ticks % TimeSpan.TicksPerDay;
+
+ if (value.Ticks < 0)
+ {
+ day = -day;
+ time = -time;
+ }
+ int hours = (int)(time / TimeSpan.TicksPerHour % 24);
+ int minutes = (int)(time / TimeSpan.TicksPerMinute % 60);
+ int seconds = (int)(time / TimeSpan.TicksPerSecond % 60);
+ int fraction = (int)(time % TimeSpan.TicksPerSecond);
+
+ long tmp = 0;
+ int i = 0;
+ int tokenLen;
+
+ while (i < format.Length)
+ {
+ char ch = format[i];
+ int nextChar;
+ switch (ch)
+ {
+ case 'h':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2)
+ {
+ goto default; // to release the builder and throw
+ }
+ DateTimeFormat.FormatDigits(result, hours, tokenLen);
+ break;
+ case 'm':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2)
+ {
+ goto default; // to release the builder and throw
+ }
+ DateTimeFormat.FormatDigits(result, minutes, tokenLen);
+ break;
+ case 's':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2)
+ {
+ goto default; // to release the builder and throw
+ }
+ DateTimeFormat.FormatDigits(result, seconds, tokenLen);
+ break;
+ case 'f':
+ //
+ // The fraction of a second in single-digit precision. The remaining digits are truncated.
+ //
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits)
+ {
+ goto default; // to release the builder and throw
+ }
+
+ tmp = fraction;
+ tmp /= TimeSpanParse.Pow10(DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
+ result.Append((tmp).ToString(DateTimeFormat.fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture));
+ break;
+ case 'F':
+ //
+ // Displays the most significant digit of the seconds fraction. Nothing is displayed if the digit is zero.
+ //
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits)
+ {
+ goto default; // to release the builder and throw
+ }
+
+ tmp = fraction;
+ tmp /= TimeSpanParse.Pow10(DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
+ int effectiveDigits = tokenLen;
+ while (effectiveDigits > 0)
+ {
+ if (tmp % 10 == 0)
+ {
+ tmp = tmp / 10;
+ effectiveDigits--;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (effectiveDigits > 0)
+ {
+ result.Append((tmp).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
+ }
+ break;
+ case 'd':
+ //
+ // tokenLen == 1 : Day as digits with no leading zero.
+ // tokenLen == 2+: Day as digits with leading zero for single-digit days.
+ //
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 8)
+ {
+ goto default; // to release the builder and throw
+ }
+
+ DateTimeFormat.FormatDigits(result, day, tokenLen, true);
+ break;
+ case '\'':
+ case '\"':
+ tokenLen = DateTimeFormat.ParseQuoteString(format, i, result);
+ break;
+ case '%':
+ // Optional format character.
+ // For example, format string "%d" will print day
+ // Most of the cases, "%" can be ignored.
+ nextChar = DateTimeFormat.ParseNextChar(format, i);
+ // nextChar will be -1 if we already reach the end of the format string.
+ // Besides, we will not allow "%%" appear in the pattern.
+ if (nextChar >= 0 && nextChar != (int)'%')
+ {
+ char nextCharChar = (char)nextChar;
+ StringBuilder origStringBuilder = FormatCustomized(value, MemoryMarshal.CreateReadOnlySpan<char>(ref nextCharChar, 1), dtfi, result);
+ Debug.Assert(ReferenceEquals(origStringBuilder, result));
+ tokenLen = 2;
+ }
+ else
+ {
+ //
+ // This means that '%' is at the end of the format string or
+ // "%%" appears in the format string.
+ //
+ goto default; // to release the builder and throw
+ }
+ break;
+ case '\\':
+ // Escaped character. Can be used to insert character into the format string.
+ // For example, "\d" will insert the character 'd' into the string.
+ //
+ nextChar = DateTimeFormat.ParseNextChar(format, i);
+ if (nextChar >= 0)
+ {
+ result.Append(((char)nextChar));
+ tokenLen = 2;
+ }
+ else
+ {
+ //
+ // This means that '\' is at the end of the formatting string.
+ //
+ goto default; // to release the builder and throw
+ }
+ break;
+ default:
+ // Invalid format string
+ if (resultBuilderIsPooled)
+ {
+ StringBuilderCache.Release(result);
+ }
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ i += tokenLen;
+ }
+ return result;
+ }
+
+ internal struct FormatLiterals
+ {
+ internal string AppCompatLiteral;
+ internal int dd;
+ internal int hh;
+ internal int mm;
+ internal int ss;
+ internal int ff;
+
+ private string[] _literals;
+
+ internal string Start => _literals[0];
+ internal string DayHourSep => _literals[1];
+ internal string HourMinuteSep => _literals[2];
+ internal string MinuteSecondSep => _literals[3];
+ internal string SecondFractionSep => _literals[4];
+ internal string End => _literals[5];
+
+ /* factory method for static invariant FormatLiterals */
+ internal static FormatLiterals InitInvariant(bool isNegative)
+ {
+ FormatLiterals x = new FormatLiterals();
+ x._literals = new string[6];
+ x._literals[0] = isNegative ? "-" : string.Empty;
+ x._literals[1] = ".";
+ x._literals[2] = ":";
+ x._literals[3] = ":";
+ x._literals[4] = ".";
+ x._literals[5] = string.Empty;
+ x.AppCompatLiteral = ":."; // MinuteSecondSep+SecondFractionSep;
+ x.dd = 2;
+ x.hh = 2;
+ x.mm = 2;
+ x.ss = 2;
+ x.ff = DateTimeFormat.MaxSecondsFractionDigits;
+ return x;
+ }
+
+ // For the "v1" TimeSpan localized patterns, the data is simply literal field separators with
+ // the constants guaranteed to include DHMSF ordered greatest to least significant.
+ // Once the data becomes more complex than this we will need to write a proper tokenizer for
+ // parsing and formatting
+ internal void Init(ReadOnlySpan<char> format, bool useInvariantFieldLengths)
+ {
+ dd = hh = mm = ss = ff = 0;
+ _literals = new string[6];
+ for (int i = 0; i < _literals.Length; i++)
+ {
+ _literals[i] = string.Empty;
+ }
+
+ StringBuilder sb = StringBuilderCache.Acquire(InternalGlobalizationHelper.StringBuilderDefaultCapacity);
+ bool inQuote = false;
+ char quote = '\'';
+ int field = 0;
+
+ for (int i = 0; i < format.Length; i++)
+ {
+ switch (format[i])
+ {
+ case '\'':
+ case '\"':
+ if (inQuote && (quote == format[i]))
+ {
+ /* we were in a quote and found a matching exit quote, so we are outside a quote now */
+ if (field >= 0 && field <= 5)
+ {
+ _literals[field] = sb.ToString();
+ sb.Length = 0;
+ inQuote = false;
+ }
+ else
+ {
+ Debug.Fail($"Unexpected field value: {field}");
+ return; // how did we get here?
+ }
+ }
+ else if (!inQuote)
+ {
+ /* we are at the start of a new quote block */
+ quote = format[i];
+ inQuote = true;
+ }
+ else
+ {
+ /* we were in a quote and saw the other type of quote character, so we are still in a quote */
+ }
+ break;
+ case '%':
+ Debug.Fail("Unexpected special token '%', Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ goto default;
+ case '\\':
+ if (!inQuote)
+ {
+ i++; /* skip next character that is escaped by this backslash or percent sign */
+ break;
+ }
+ goto default;
+ case 'd':
+ if (!inQuote)
+ {
+ Debug.Assert((field == 0 && sb.Length == 0) || field == 1, "field == 0 || field == 1, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 1; // DayHourSep
+ dd++;
+ }
+ break;
+ case 'h':
+ if (!inQuote)
+ {
+ Debug.Assert((field == 1 && sb.Length == 0) || field == 2, "field == 1 || field == 2, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 2; // HourMinuteSep
+ hh++;
+ }
+ break;
+ case 'm':
+ if (!inQuote)
+ {
+ Debug.Assert((field == 2 && sb.Length == 0) || field == 3, "field == 2 || field == 3, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 3; // MinuteSecondSep
+ mm++;
+ }
+ break;
+ case 's':
+ if (!inQuote)
+ {
+ Debug.Assert((field == 3 && sb.Length == 0) || field == 4, "field == 3 || field == 4, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 4; // SecondFractionSep
+ ss++;
+ }
+ break;
+ case 'f':
+ case 'F':
+ if (!inQuote)
+ {
+ Debug.Assert((field == 4 && sb.Length == 0) || field == 5, "field == 4 || field == 5, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 5; // End
+ ff++;
+ }
+ break;
+ default:
+ sb.Append(format[i]);
+ break;
+ }
+ }
+
+ Debug.Assert(field == 5);
+ AppCompatLiteral = MinuteSecondSep + SecondFractionSep;
+
+ Debug.Assert(0 < dd && dd < 3, "0 < dd && dd < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ Debug.Assert(0 < hh && hh < 3, "0 < hh && hh < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ Debug.Assert(0 < mm && mm < 3, "0 < mm && mm < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ Debug.Assert(0 < ss && ss < 3, "0 < ss && ss < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ Debug.Assert(0 < ff && ff < 8, "0 < ff && ff < 8, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+
+ if (useInvariantFieldLengths)
+ {
+ dd = 2;
+ hh = 2;
+ mm = 2;
+ ss = 2;
+ ff = DateTimeFormat.MaxSecondsFractionDigits;
+ }
+ else
+ {
+ if (dd < 1 || dd > 2) dd = 2; // The DTFI property has a problem. let's try to make the best of the situation.
+ if (hh < 1 || hh > 2) hh = 2;
+ if (mm < 1 || mm > 2) mm = 2;
+ if (ss < 1 || ss > 2) ss = 2;
+ if (ff < 1 || ff > 7) ff = 7;
+ }
+ StringBuilderCache.Release(sb);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs
new file mode 100644
index 0000000000..b1e104550e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs
@@ -0,0 +1,1704 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Purpose: Used by TimeSpan to parse a time interval string.
+//
+// Standard Format:
+// -=-=-=-=-=-=-=-
+// "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
+// Not culture sensitive. Default format (and null/empty format string) map to this format.
+//
+// "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
+// Only print what's needed. Localized (if you want Invariant, pass in Invariant).
+// The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
+//
+// "G": General format, long: [-]d':'hh':'mm':'ss'.'fffffff
+// Always print days and 7 fractional digits. Localized (if you want Invariant, pass in Invariant).
+// The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
+//
+// * "TryParseTimeSpan" is the main method for Parse/TryParse
+//
+// - TimeSpanTokenizer.GetNextToken() is used to split the input string into number and literal tokens.
+// - TimeSpanRawInfo.ProcessToken() adds the next token into the parsing intermediary state structure
+// - ProcessTerminalState() uses the fully initialized TimeSpanRawInfo to find a legal parse match.
+// The terminal states are attempted as follows:
+// foreach (+InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern) try
+// 1 number => d
+// 2 numbers => h:m
+// 3 numbers => h:m:s | d.h:m | h:m:.f
+// 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
+// 5 numbers => d.h:m:s.f
+//
+// Custom Format:
+// -=-=-=-=-=-=-=
+//
+// * "TryParseExactTimeSpan" is the main method for ParseExact/TryParseExact methods
+// * "TryParseExactMultipleTimeSpan" is the main method for ParseExact/TryparseExact
+// methods that take a string[] of formats
+//
+// - For single-letter formats "TryParseTimeSpan" is called (see above)
+// - For multi-letter formats "TryParseByFormat" is called
+// - TryParseByFormat uses helper methods (ParseExactLiteral, ParseExactDigits, etc)
+// which drive the underlying TimeSpanTokenizer. However, unlike standard formatting which
+// operates on whole-tokens, ParseExact operates at the character-level. As such,
+// TimeSpanTokenizer.NextChar and TimeSpanTokenizer.BackOne() are called directly.
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.Globalization
+{
+ internal static class TimeSpanParse
+ {
+ private const int MaxFractionDigits = 7;
+ private const int MaxDays = 10675199;
+ private const int MaxHours = 23;
+ private const int MaxMinutes = 59;
+ private const int MaxSeconds = 59;
+ private const int MaxFraction = 9999999;
+
+ [Flags]
+ private enum TimeSpanStandardStyles : byte
+ {
+ // Standard Format Styles
+ None = 0x00000000,
+ Invariant = 0x00000001, //Allow Invariant Culture
+ Localized = 0x00000002, //Allow Localized Culture
+ RequireFull = 0x00000004, //Require the input to be in DHMSF format
+ Any = Invariant | Localized,
+ }
+
+ // TimeSpan Token Types
+ private enum TTT : byte
+ {
+ None = 0, // None of the TimeSpanToken fields are set
+ End = 1, // '\0'
+ Num = 2, // Number
+ Sep = 3, // literal
+ NumOverflow = 4, // Number that overflowed
+ }
+
+ private ref struct TimeSpanToken
+ {
+ internal TTT _ttt;
+ internal int _num; // Store the number that we are parsing (if any)
+ internal int _zeroes; // Store the number of leading zeroes (if any)
+ internal ReadOnlySpan<char> _sep; // Store the literal that we are parsing (if any)
+
+ public TimeSpanToken(TTT type) : this(type, 0, 0, default(ReadOnlySpan<char>)) { }
+
+ public TimeSpanToken(int number) : this(TTT.Num, number, 0, default(ReadOnlySpan<char>)) { }
+
+ public TimeSpanToken(int number, int leadingZeroes) : this(TTT.Num, number, leadingZeroes, default(ReadOnlySpan<char>)) { }
+
+ public TimeSpanToken(TTT type, int number, int leadingZeroes, ReadOnlySpan<char> separator)
+ {
+ _ttt = type;
+ _num = number;
+ _zeroes = leadingZeroes;
+ _sep = separator;
+ }
+
+ public bool IsInvalidFraction()
+ {
+ Debug.Assert(_ttt == TTT.Num);
+ Debug.Assert(_num > -1);
+
+ if (_num > MaxFraction || _zeroes > MaxFractionDigits)
+ return true;
+
+ if (_num == 0 || _zeroes == 0)
+ return false;
+
+ // num > 0 && zeroes > 0 && num <= maxValue && zeroes <= maxPrecision
+ return _num >= MaxFraction / Pow10(_zeroes - 1);
+ }
+ }
+
+ private ref struct TimeSpanTokenizer
+ {
+ private ReadOnlySpan<char> _value;
+ private int _pos;
+
+ internal TimeSpanTokenizer(ReadOnlySpan<char> input) : this(input, 0) { }
+
+ internal TimeSpanTokenizer(ReadOnlySpan<char> input, int startPosition)
+ {
+ _value = input;
+ _pos = startPosition;
+ }
+
+ /// <summary>Returns the next token in the input string</summary>
+ /// <remarks>Used by the parsing routines that operate on standard-formats.</remarks>
+ internal TimeSpanToken GetNextToken()
+ {
+ // Get the position of the next character to be processed. If there is no
+ // next character, we're at the end.
+ int pos = _pos;
+ Debug.Assert(pos > -1);
+ if (pos >= _value.Length)
+ {
+ return new TimeSpanToken(TTT.End);
+ }
+
+ // Now retrieve that character. If it's a digit, we're processing a number.
+ int num = _value[pos] - '0';
+ if ((uint)num <= 9)
+ {
+ int zeroes = 0;
+ if (num == 0)
+ {
+ // Read all leading zeroes.
+ zeroes = 1;
+ while (true)
+ {
+ int digit;
+ if (++_pos >= _value.Length || (uint)(digit = _value[_pos] - '0') > 9)
+ {
+ return new TimeSpanToken(TTT.Num, 0, zeroes, default(ReadOnlySpan<char>));
+ }
+
+ if (digit == 0)
+ {
+ zeroes++;
+ continue;
+ }
+
+ num = digit;
+ break;
+ }
+ }
+
+ // Continue to read as long as we're reading digits.
+ while (++_pos < _value.Length)
+ {
+ int digit = _value[_pos] - '0';
+ if ((uint)digit > 9)
+ {
+ break;
+ }
+
+ num = num * 10 + digit;
+ if ((num & 0xF0000000) != 0)
+ {
+ return new TimeSpanToken(TTT.NumOverflow);
+ }
+ }
+
+ return new TimeSpanToken(TTT.Num, num, zeroes, default(ReadOnlySpan<char>));
+ }
+
+ // Otherwise, we're processing a separator, and we've already processed the first
+ // character of it. Continue processing characters as long as they're not digits.
+ int length = 1;
+ while (true)
+ {
+ if (++_pos >= _value.Length || (uint)(_value[_pos] - '0') <= 9)
+ {
+ break;
+ }
+ length++;
+ }
+
+ // Return the separator.
+ return new TimeSpanToken(TTT.Sep, 0, 0, _value.Slice(pos, length));
+ }
+
+ internal bool EOL => _pos >= (_value.Length - 1);
+
+ internal void BackOne()
+ {
+ if (_pos > 0) --_pos;
+ }
+
+ internal char NextChar
+ {
+ get
+ {
+ int pos = ++_pos;
+ return (uint)pos < (uint)_value.Length ?
+ _value[pos] :
+ (char)0;
+ }
+ }
+ }
+
+ /// <summary>Stores intermediary parsing state for the standard formats.</summary>
+ private ref struct TimeSpanRawInfo
+ {
+ internal TimeSpanFormat.FormatLiterals PositiveInvariant => TimeSpanFormat.PositiveInvariantFormatLiterals;
+ internal TimeSpanFormat.FormatLiterals NegativeInvariant => TimeSpanFormat.NegativeInvariantFormatLiterals;
+
+ internal TimeSpanFormat.FormatLiterals PositiveLocalized
+ {
+ get
+ {
+ if (!_posLocInit)
+ {
+ _posLoc = new TimeSpanFormat.FormatLiterals();
+ _posLoc.Init(_fullPosPattern, false);
+ _posLocInit = true;
+ }
+ return _posLoc;
+ }
+ }
+
+ internal TimeSpanFormat.FormatLiterals NegativeLocalized
+ {
+ get
+ {
+ if (!_negLocInit)
+ {
+ _negLoc = new TimeSpanFormat.FormatLiterals();
+ _negLoc.Init(_fullNegPattern, false);
+ _negLocInit = true;
+ }
+ return _negLoc;
+ }
+ }
+
+ internal bool FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == 5
+ && _numCount == 4
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.DayHourSep)
+ && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
+ && _literals3.EqualsOrdinal(pattern.AppCompatLiteral)
+ && _literals4.EqualsOrdinal(pattern.End);
+
+ internal bool PartialAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == 4
+ && _numCount == 3
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
+ && _literals2.EqualsOrdinal(pattern.AppCompatLiteral)
+ && _literals3.EqualsOrdinal(pattern.End);
+
+ /// <summary>DHMSF (all values matched)</summary>
+ internal bool FullMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == MaxLiteralTokens
+ && _numCount == MaxNumericTokens
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.DayHourSep)
+ && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
+ && _literals3.EqualsOrdinal(pattern.MinuteSecondSep)
+ && _literals4.EqualsOrdinal(pattern.SecondFractionSep)
+ && _literals5.EqualsOrdinal(pattern.End);
+
+ /// <summary>D (no hours, minutes, seconds, or fractions)</summary>
+ internal bool FullDMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == 2
+ && _numCount == 1
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.End);
+
+ /// <summary>HM (no days, seconds, or fractions)</summary>
+ internal bool FullHMMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == 3
+ && _numCount == 2
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
+ && _literals2.EqualsOrdinal(pattern.End);
+
+ /// <summary>DHM (no seconds or fraction)</summary>
+ internal bool FullDHMMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == 4
+ && _numCount == 3
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.DayHourSep)
+ && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
+ && _literals3.EqualsOrdinal(pattern.End);
+
+ /// <summary>HMS (no days or fraction)</summary>
+ internal bool FullHMSMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == 4
+ && _numCount == 3
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
+ && _literals2.EqualsOrdinal(pattern.MinuteSecondSep)
+ && _literals3.EqualsOrdinal(pattern.End);
+
+ /// <summary>DHMS (no fraction)</summary>
+ internal bool FullDHMSMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == 5
+ && _numCount == 4
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.DayHourSep)
+ && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
+ && _literals3.EqualsOrdinal(pattern.MinuteSecondSep)
+ && _literals4.EqualsOrdinal(pattern.End);
+
+ /// <summary>HMSF (no days)</summary>
+ internal bool FullHMSFMatch(TimeSpanFormat.FormatLiterals pattern) =>
+ _sepCount == 5
+ && _numCount == 4
+ && _literals0.EqualsOrdinal(pattern.Start)
+ && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
+ && _literals2.EqualsOrdinal(pattern.MinuteSecondSep)
+ && _literals3.EqualsOrdinal(pattern.SecondFractionSep)
+ && _literals4.EqualsOrdinal(pattern.End);
+
+ internal TTT _lastSeenTTT;
+ internal int _tokenCount;
+ internal int _sepCount;
+ internal int _numCount;
+
+ private TimeSpanFormat.FormatLiterals _posLoc;
+ private TimeSpanFormat.FormatLiterals _negLoc;
+ private bool _posLocInit;
+ private bool _negLocInit;
+ private string _fullPosPattern;
+ private string _fullNegPattern;
+
+ private const int MaxTokens = 11;
+ private const int MaxLiteralTokens = 6;
+ private const int MaxNumericTokens = 5;
+
+ internal TimeSpanToken _numbers0, _numbers1, _numbers2, _numbers3, _numbers4; // MaxNumbericTokens = 5
+ internal ReadOnlySpan<char> _literals0, _literals1, _literals2, _literals3, _literals4, _literals5; // MaxLiteralTokens=6
+
+ internal void Init(DateTimeFormatInfo dtfi)
+ {
+ Debug.Assert(dtfi != null);
+
+ _lastSeenTTT = TTT.None;
+ _tokenCount = 0;
+ _sepCount = 0;
+ _numCount = 0;
+
+ _fullPosPattern = dtfi.FullTimeSpanPositivePattern;
+ _fullNegPattern = dtfi.FullTimeSpanNegativePattern;
+ _posLocInit = false;
+ _negLocInit = false;
+ }
+
+ internal bool ProcessToken(ref TimeSpanToken tok, ref TimeSpanResult result)
+ {
+ switch (tok._ttt)
+ {
+ case TTT.Num:
+ if ((_tokenCount == 0 && !AddSep(default(ReadOnlySpan<char>), ref result)) || !AddNum(tok, ref result))
+ {
+ return false;
+ }
+ break;
+
+ case TTT.Sep:
+ if (!AddSep(tok._sep, ref result))
+ {
+ return false;
+ }
+ break;
+
+ case TTT.NumOverflow:
+ return result.SetOverflowFailure();
+
+ default:
+ // Some unknown token or a repeat token type in the input
+ return result.SetBadTimeSpanFailure();
+ }
+
+ _lastSeenTTT = tok._ttt;
+ Debug.Assert(_tokenCount == (_sepCount + _numCount), "tokenCount == (SepCount + NumCount)");
+ return true;
+ }
+
+ private bool AddSep(ReadOnlySpan<char> sep, ref TimeSpanResult result)
+ {
+ if (_sepCount >= MaxLiteralTokens || _tokenCount >= MaxTokens)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+
+ switch (_sepCount++)
+ {
+ case 0: _literals0 = sep; break;
+ case 1: _literals1 = sep; break;
+ case 2: _literals2 = sep; break;
+ case 3: _literals3 = sep; break;
+ case 4: _literals4 = sep; break;
+ default: _literals5 = sep; break;
+ }
+
+ _tokenCount++;
+ return true;
+ }
+ private bool AddNum(TimeSpanToken num, ref TimeSpanResult result)
+ {
+ if (_numCount >= MaxNumericTokens || _tokenCount >= MaxTokens)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+
+ switch (_numCount++)
+ {
+ case 0: _numbers0 = num; break;
+ case 1: _numbers1 = num; break;
+ case 2: _numbers2 = num; break;
+ case 3: _numbers3 = num; break;
+ default: _numbers4 = num; break;
+ }
+
+ _tokenCount++;
+ return true;
+ }
+ }
+
+ /// <summary>Store the result of the parsing.</summary>
+ private ref struct TimeSpanResult
+ {
+ internal TimeSpan parsedTimeSpan;
+ private readonly bool _throwOnFailure;
+ private readonly ReadOnlySpan<char> _originalTimeSpanString;
+
+ internal TimeSpanResult(bool throwOnFailure, ReadOnlySpan<char> originalTimeSpanString)
+ {
+ parsedTimeSpan = default(TimeSpan);
+ _throwOnFailure = throwOnFailure;
+ _originalTimeSpanString = originalTimeSpanString;
+ }
+
+ internal bool SetNoFormatSpecifierFailure()
+ {
+ if (!_throwOnFailure)
+ {
+ return false;
+ }
+
+ throw new FormatException(SR.Format_NoFormatSpecifier);
+ }
+
+ internal bool SetBadQuoteFailure(char failingCharacter)
+ {
+ if (!_throwOnFailure)
+ {
+ return false;
+ }
+
+ throw new FormatException(SR.Format(SR.Format_BadQuote, failingCharacter));
+ }
+
+ internal bool SetInvalidStringFailure()
+ {
+ if (!_throwOnFailure)
+ {
+ return false;
+ }
+
+ throw new FormatException(SR.Format_InvalidString);
+ }
+
+ internal bool SetArgumentNullFailure(string argumentName)
+ {
+ if (!_throwOnFailure)
+ {
+ return false;
+ }
+
+ Debug.Assert(argumentName != null);
+ throw new ArgumentNullException(argumentName, SR.ArgumentNull_String);
+ }
+
+ internal bool SetOverflowFailure()
+ {
+ if (!_throwOnFailure)
+ {
+ return false;
+ }
+
+ throw new OverflowException(SR.Format(SR.Overflow_TimeSpanElementTooLarge, new string(_originalTimeSpanString)));
+ }
+
+ internal bool SetBadTimeSpanFailure()
+ {
+ if (!_throwOnFailure)
+ {
+ return false;
+ }
+
+ throw new FormatException(SR.Format(SR.Format_BadTimeSpan, new string(_originalTimeSpanString)));
+ }
+
+ internal bool SetBadFormatSpecifierFailure(char? formatSpecifierCharacter = null)
+ {
+ if (!_throwOnFailure)
+ {
+ return false;
+ }
+
+ throw new FormatException(SR.Format(SR.Format_BadFormatSpecifier, formatSpecifierCharacter));
+ }
+ }
+
+ internal static long Pow10(int pow)
+ {
+ switch (pow)
+ {
+ case 0: return 1;
+ case 1: return 10;
+ case 2: return 100;
+ case 3: return 1000;
+ case 4: return 10000;
+ case 5: return 100000;
+ case 6: return 1000000;
+ case 7: return 10000000;
+ default: return (long)Math.Pow(10, pow);
+ }
+ }
+
+ private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result)
+ {
+ if (days._num > MaxDays ||
+ hours._num > MaxHours ||
+ minutes._num > MaxMinutes ||
+ seconds._num > MaxSeconds ||
+ fraction.IsInvalidFraction())
+ {
+ result = 0;
+ return false;
+ }
+
+ long ticks = ((long)days._num * 3600 * 24 + (long)hours._num * 3600 + (long)minutes._num * 60 + seconds._num) * 1000;
+ if (ticks > InternalGlobalizationHelper.MaxMilliSeconds || ticks < InternalGlobalizationHelper.MinMilliSeconds)
+ {
+ result = 0;
+ return false;
+ }
+
+ // Normalize the fraction component
+ //
+ // string representation => (zeroes,num) => resultant fraction ticks
+ // --------------------- ------------ ------------------------
+ // ".9999999" => (0,9999999) => 9,999,999 ticks (same as constant maxFraction)
+ // ".1" => (0,1) => 1,000,000 ticks
+ // ".01" => (1,1) => 100,000 ticks
+ // ".001" => (2,1) => 10,000 ticks
+ long f = fraction._num;
+ if (f != 0)
+ {
+ long lowerLimit = InternalGlobalizationHelper.TicksPerTenthSecond;
+ if (fraction._zeroes > 0)
+ {
+ long divisor = Pow10(fraction._zeroes);
+ lowerLimit = lowerLimit / divisor;
+ }
+
+ while (f < lowerLimit)
+ {
+ f *= 10;
+ }
+ }
+
+ result = ticks * TimeSpan.TicksPerMillisecond + f;
+ if (positive && result < 0)
+ {
+ result = 0;
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static TimeSpan Parse(ReadOnlySpan<char> input, IFormatProvider formatProvider)
+ {
+ var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
+ bool success = TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult);
+ Debug.Assert(success, "Should have thrown on failure");
+ return parseResult.parsedTimeSpan;
+ }
+
+ internal static bool TryParse(ReadOnlySpan<char> input, IFormatProvider formatProvider, out TimeSpan result)
+ {
+ var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
+
+ if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult))
+ {
+ result = parseResult.parsedTimeSpan;
+ return true;
+ }
+
+ result = default(TimeSpan);
+ return false;
+ }
+
+ internal static TimeSpan ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles)
+ {
+ var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
+ bool success = TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult);
+ Debug.Assert(success, "Should have thrown on failure");
+ return parseResult.parsedTimeSpan;
+ }
+
+ internal static bool TryParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
+ {
+ var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
+
+ if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult))
+ {
+ result = parseResult.parsedTimeSpan;
+ return true;
+ }
+
+ result = default(TimeSpan);
+ return false;
+ }
+
+ internal static TimeSpan ParseExactMultiple(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles)
+ {
+ var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
+ bool success = TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult);
+ Debug.Assert(success, "Should have thrown on failure");
+ return parseResult.parsedTimeSpan;
+ }
+
+ internal static bool TryParseExactMultiple(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
+ {
+ var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
+
+ if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult))
+ {
+ result = parseResult.parsedTimeSpan;
+ return true;
+ }
+
+ result = default(TimeSpan);
+ return false;
+ }
+
+ /// <summary>Common private Parse method called by both Parse and TryParse.</summary>
+ private static bool TryParseTimeSpan(ReadOnlySpan<char> input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result)
+ {
+ input = input.Trim();
+ if (input.IsEmpty)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+
+ var tokenizer = new TimeSpanTokenizer(input);
+
+ var raw = new TimeSpanRawInfo();
+ raw.Init(DateTimeFormatInfo.GetInstance(formatProvider));
+
+ TimeSpanToken tok = tokenizer.GetNextToken();
+
+ // The following loop will break out when we reach the end of the str or
+ // when we can determine that the input is invalid.
+ while (tok._ttt != TTT.End)
+ {
+ if (!raw.ProcessToken(ref tok, ref result))
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+ tok = tokenizer.GetNextToken();
+ }
+ Debug.Assert(tokenizer.EOL);
+
+ if (!ProcessTerminalState(ref raw, style, ref result))
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Validate the terminal state of a standard format parse.
+ /// Sets result.parsedTimeSpan on success.
+ /// Calculates the resultant TimeSpan from the TimeSpanRawInfo.
+ /// </summary>
+ /// <remarks>
+ /// try => +InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern
+ /// 1) Verify Start matches
+ /// 2) Verify End matches
+ /// 3) 1 number => d
+ /// 2 numbers => h:m
+ /// 3 numbers => h:m:s | d.h:m | h:m:.f
+ /// 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
+ /// 5 numbers => d.h:m:s.f
+ /// </remarks>
+ private static bool ProcessTerminalState(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
+ {
+ if (raw._lastSeenTTT == TTT.Num)
+ {
+ TimeSpanToken tok = new TimeSpanToken();
+ tok._ttt = TTT.Sep;
+ if (!raw.ProcessToken(ref tok, ref result))
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+ }
+
+ switch (raw._numCount)
+ {
+ case 1: return ProcessTerminal_D(ref raw, style, ref result);
+ case 2: return ProcessTerminal_HM(ref raw, style, ref result);
+ case 3: return ProcessTerminal_HM_S_D(ref raw, style, ref result);
+ case 4: return ProcessTerminal_HMS_F_D(ref raw, style, ref result);
+ case 5: return ProcessTerminal_DHMSF(ref raw, style, ref result);
+ default: return result.SetBadTimeSpanFailure();
+ }
+ }
+
+ /// <summary>Validate the 5-number "Days.Hours:Minutes:Seconds.Fraction" terminal case.</summary>
+ private static bool ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
+ {
+ if (raw._sepCount != 6)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+ Debug.Assert(raw._numCount == 5);
+
+ bool inv = (style & TimeSpanStandardStyles.Invariant) != 0;
+ bool loc = (style & TimeSpanStandardStyles.Localized) != 0;
+ bool positive = false;
+ bool match = false;
+
+ if (inv)
+ {
+ if (raw.FullMatch(raw.PositiveInvariant))
+ {
+ match = true;
+ positive = true;
+ }
+ if (!match && raw.FullMatch(raw.NegativeInvariant))
+ {
+ match = true;
+ positive = false;
+ }
+ }
+
+ if (loc)
+ {
+ if (!match && raw.FullMatch(raw.PositiveLocalized))
+ {
+ match = true;
+ positive = true;
+ }
+ if (!match && raw.FullMatch(raw.NegativeLocalized))
+ {
+ match = true;
+ positive = false;
+ }
+ }
+
+ if (match)
+ {
+ long ticks;
+
+ if (!TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, raw._numbers4, out ticks))
+ {
+ return result.SetOverflowFailure();
+ }
+
+ if (!positive)
+ {
+ ticks = -ticks;
+ if (ticks > 0)
+ {
+ return result.SetOverflowFailure();
+ }
+ }
+
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ return result.SetBadTimeSpanFailure();
+ }
+
+
+ /// <summary>
+ /// Validate the ambiguous 4-number "Hours:Minutes:Seconds.Fraction", "Days.Hours:Minutes:Seconds",
+ /// or "Days.Hours:Minutes:.Fraction" terminal case.
+ /// </summary>
+ private static bool ProcessTerminal_HMS_F_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
+ {
+ if (raw._sepCount != 5 || (style & TimeSpanStandardStyles.RequireFull) != 0)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+ Debug.Assert(raw._numCount == 4);
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ long ticks = 0;
+ bool positive = false, match = false, overflow = false;
+ var zero = new TimeSpanToken(0);
+
+ if (inv)
+ {
+ if (raw.FullHMSFMatch(raw.PositiveInvariant))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullDHMSMatch(raw.PositiveInvariant))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullAppCompatMatch(raw.PositiveInvariant))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullHMSFMatch(raw.NegativeInvariant))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullDHMSMatch(raw.NegativeInvariant))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullAppCompatMatch(raw.NegativeInvariant))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
+ overflow = overflow || !match;
+ }
+ }
+
+ if (loc)
+ {
+ if (!match && raw.FullHMSFMatch(raw.PositiveLocalized))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullDHMSMatch(raw.PositiveLocalized))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullAppCompatMatch(raw.PositiveLocalized))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullHMSFMatch(raw.NegativeLocalized))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullDHMSMatch(raw.NegativeLocalized))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullAppCompatMatch(raw.NegativeLocalized))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
+ overflow = overflow || !match;
+ }
+ }
+
+ if (match)
+ {
+ if (!positive)
+ {
+ ticks = -ticks;
+ if (ticks > 0)
+ {
+ return result.SetOverflowFailure();
+ }
+ }
+
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ return overflow ?
+ result.SetOverflowFailure() : // we found at least one literal pattern match but the numbers just didn't fit
+ result.SetBadTimeSpanFailure(); // we couldn't find a thing
+ }
+
+ /// <summary>Validate the ambiguous 3-number "Hours:Minutes:Seconds", "Days.Hours:Minutes", or "Hours:Minutes:.Fraction" terminal case.</summary>
+ private static bool ProcessTerminal_HM_S_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
+ {
+ if (raw._sepCount != 4 || (style & TimeSpanStandardStyles.RequireFull) != 0)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+ Debug.Assert(raw._numCount == 3);
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ bool positive = false, match = false, overflow = false;
+ var zero = new TimeSpanToken(0);
+ long ticks = 0;
+
+ if (inv)
+ {
+ if (raw.FullHMSMatch(raw.PositiveInvariant))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullDHMMatch(raw.PositiveInvariant))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.PartialAppCompatMatch(raw.PositiveInvariant))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullHMSMatch(raw.NegativeInvariant))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullDHMMatch(raw.NegativeInvariant))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.PartialAppCompatMatch(raw.NegativeInvariant))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
+ overflow = overflow || !match;
+ }
+ }
+
+ if (loc)
+ {
+ if (!match && raw.FullHMSMatch(raw.PositiveLocalized))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullDHMMatch(raw.PositiveLocalized))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.PartialAppCompatMatch(raw.PositiveLocalized))
+ {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullHMSMatch(raw.NegativeLocalized))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.FullDHMMatch(raw.NegativeLocalized))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
+ overflow = overflow || !match;
+ }
+
+ if (!match && raw.PartialAppCompatMatch(raw.NegativeLocalized))
+ {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
+ overflow = overflow || !match;
+ }
+ }
+
+ if (match)
+ {
+ if (!positive)
+ {
+ ticks = -ticks;
+ if (ticks > 0)
+ {
+ return result.SetOverflowFailure();
+ }
+ }
+
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ return overflow ?
+ result.SetOverflowFailure() : // we found at least one literal pattern match but the numbers just didn't fit
+ result.SetBadTimeSpanFailure(); // we couldn't find a thing
+ }
+
+ /// <summary>Validate the 2-number "Hours:Minutes" terminal case.</summary>
+ private static bool ProcessTerminal_HM(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
+ {
+ if (raw._sepCount != 3 || (style & TimeSpanStandardStyles.RequireFull) != 0)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+ Debug.Assert(raw._numCount == 2);
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ bool positive = false, match = false;
+
+ if (inv)
+ {
+ if (raw.FullHMMatch(raw.PositiveInvariant))
+ {
+ match = true;
+ positive = true;
+ }
+
+ if (!match && raw.FullHMMatch(raw.NegativeInvariant))
+ {
+ match = true;
+ positive = false;
+ }
+ }
+
+ if (loc)
+ {
+ if (!match && raw.FullHMMatch(raw.PositiveLocalized))
+ {
+ match = true;
+ positive = true;
+ }
+
+ if (!match && raw.FullHMMatch(raw.NegativeLocalized))
+ {
+ match = true;
+ positive = false;
+ }
+ }
+
+ if (match)
+ {
+ long ticks = 0;
+ var zero = new TimeSpanToken(0);
+
+ if (!TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, zero, out ticks))
+ {
+ return result.SetOverflowFailure();
+ }
+
+ if (!positive)
+ {
+ ticks = -ticks;
+ if (ticks > 0)
+ {
+ return result.SetOverflowFailure();
+ }
+ }
+
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ return result.SetBadTimeSpanFailure();
+ }
+
+ /// <summary>Validate the 1-number "Days" terminal case.</summary>
+ private static bool ProcessTerminal_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
+ {
+ if (raw._sepCount != 2 || (style & TimeSpanStandardStyles.RequireFull) != 0)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+ Debug.Assert(raw._numCount == 1);
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ bool positive = false, match = false;
+
+ if (inv)
+ {
+ if (raw.FullDMatch(raw.PositiveInvariant))
+ {
+ match = true;
+ positive = true;
+ }
+
+ if (!match && raw.FullDMatch(raw.NegativeInvariant))
+ {
+ match = true;
+ positive = false;
+ }
+ }
+
+ if (loc)
+ {
+ if (!match && raw.FullDMatch(raw.PositiveLocalized))
+ {
+ match = true;
+ positive = true;
+ }
+
+ if (!match && raw.FullDMatch(raw.NegativeLocalized))
+ {
+ match = true;
+ positive = false;
+ }
+ }
+
+ if (match)
+ {
+ long ticks = 0;
+ var zero = new TimeSpanToken(0);
+
+ if (!TryTimeToTicks(positive, raw._numbers0, zero, zero, zero, zero, out ticks))
+ {
+ return result.SetOverflowFailure();
+ }
+
+ if (!positive)
+ {
+ ticks = -ticks;
+ if (ticks > 0)
+ {
+ return result.SetOverflowFailure();
+ }
+ }
+
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ return result.SetBadTimeSpanFailure();
+ }
+
+ /// <summary>Common private ParseExact method called by both ParseExact and TryParseExact.</summary>
+ private static bool TryParseExactTimeSpan(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result)
+ {
+ if (format.Length == 0)
+ {
+ return result.SetBadFormatSpecifierFailure();
+ }
+
+ if (format.Length == 1)
+ {
+ switch (format[0])
+ {
+ case 'c':
+ case 't':
+ case 'T':
+ return TryParseTimeSpanConstant(input, ref result); // fast path for legacy style TimeSpan formats.
+
+ case 'g':
+ return TryParseTimeSpan(input, TimeSpanStandardStyles.Localized, formatProvider, ref result);
+
+ case 'G':
+ return TryParseTimeSpan(input, TimeSpanStandardStyles.Localized | TimeSpanStandardStyles.RequireFull, formatProvider, ref result);
+
+ default:
+ return result.SetBadFormatSpecifierFailure(format[0]);
+ }
+ }
+
+ return TryParseByFormat(input, format, styles, ref result);
+ }
+
+ /// <summary>Parse the TimeSpan instance using the specified format. Used by TryParseExactTimeSpan.</summary>
+ private static bool TryParseByFormat(ReadOnlySpan<char> input, ReadOnlySpan<char> format, TimeSpanStyles styles, ref TimeSpanResult result)
+ {
+ bool seenDD = false; // already processed days?
+ bool seenHH = false; // already processed hours?
+ bool seenMM = false; // already processed minutes?
+ bool seenSS = false; // already processed seconds?
+ bool seenFF = false; // already processed fraction?
+
+ int dd = 0; // parsed days
+ int hh = 0; // parsed hours
+ int mm = 0; // parsed minutes
+ int ss = 0; // parsed seconds
+ int leadingZeroes = 0; // number of leading zeroes in the parsed fraction
+ int ff = 0; // parsed fraction
+ int i = 0; // format string position
+ int tokenLen = 0; // length of current format token, used to update index 'i'
+
+ var tokenizer = new TimeSpanTokenizer(input, -1);
+
+ while (i < format.Length)
+ {
+ char ch = format[i];
+ int nextFormatChar;
+ switch (ch)
+ {
+ case 'h':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2 || seenHH || !ParseExactDigits(ref tokenizer, tokenLen, out hh))
+ {
+ return result.SetInvalidStringFailure();
+ }
+ seenHH = true;
+ break;
+
+ case 'm':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2 || seenMM || !ParseExactDigits(ref tokenizer, tokenLen, out mm))
+ {
+ return result.SetInvalidStringFailure();
+ }
+ seenMM = true;
+ break;
+
+ case 's':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2 || seenSS || !ParseExactDigits(ref tokenizer, tokenLen, out ss))
+ {
+ return result.SetInvalidStringFailure();
+ }
+ seenSS = true;
+ break;
+
+ case 'f':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF || !ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff))
+ {
+ return result.SetInvalidStringFailure();
+ }
+ seenFF = true;
+ break;
+
+ case 'F':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF)
+ {
+ return result.SetInvalidStringFailure();
+ }
+ ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff);
+ seenFF = true;
+ break;
+
+ case 'd':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ int tmp = 0;
+ if (tokenLen > 8 || seenDD || !ParseExactDigits(ref tokenizer, (tokenLen < 2) ? 1 : tokenLen, (tokenLen < 2) ? 8 : tokenLen, out tmp, out dd))
+ {
+ return result.SetInvalidStringFailure();
+ }
+ seenDD = true;
+ break;
+
+ case '\'':
+ case '\"':
+ StringBuilder enquotedString = StringBuilderCache.Acquire();
+ if (!DateTimeParse.TryParseQuoteString(format, i, enquotedString, out tokenLen))
+ {
+ StringBuilderCache.Release(enquotedString);
+ return result.SetBadQuoteFailure(ch);
+ }
+ if (!ParseExactLiteral(ref tokenizer, enquotedString))
+ {
+ StringBuilderCache.Release(enquotedString);
+ return result.SetInvalidStringFailure();
+ }
+ StringBuilderCache.Release(enquotedString);
+ break;
+
+ case '%':
+ // Optional format character.
+ // For example, format string "%d" will print day
+ // Most of the cases, "%" can be ignored.
+ nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
+
+ // nextFormatChar will be -1 if we already reach the end of the format string.
+ // Besides, we will not allow "%%" appear in the pattern.
+ if (nextFormatChar >= 0 && nextFormatChar != '%')
+ {
+ tokenLen = 1; // skip the '%' and process the format character
+ break;
+ }
+ else
+ {
+ // This means that '%' is at the end of the format string or
+ // "%%" appears in the format string.
+ return result.SetInvalidStringFailure();
+ }
+
+ case '\\':
+ // Escaped character. Can be used to insert character into the format string.
+ // For example, "\d" will insert the character 'd' into the string.
+ //
+ nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
+ if (nextFormatChar >= 0 && tokenizer.NextChar == (char)nextFormatChar)
+ {
+ tokenLen = 2;
+ }
+ else
+ {
+ // This means that '\' is at the end of the format string or the literal match failed.
+ return result.SetInvalidStringFailure();
+ }
+ break;
+
+ default:
+ return result.SetInvalidStringFailure();
+ }
+
+ i += tokenLen;
+ }
+
+
+ if (!tokenizer.EOL)
+ {
+ // the custom format didn't consume the entire input
+ return result.SetBadTimeSpanFailure();
+ }
+
+ bool positive = (styles & TimeSpanStyles.AssumeNegative) == 0;
+ if (TryTimeToTicks(positive, new TimeSpanToken(dd),
+ new TimeSpanToken(hh),
+ new TimeSpanToken(mm),
+ new TimeSpanToken(ss),
+ new TimeSpanToken(ff, leadingZeroes),
+ out long ticks))
+ {
+ if (!positive)
+ {
+ ticks = -ticks;
+ }
+
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+ else
+ {
+ return result.SetOverflowFailure();
+ }
+ }
+
+ private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, out int result)
+ {
+ result = 0;
+ int zeroes = 0;
+ int maxDigitLength = (minDigitLength == 1) ? 2 : minDigitLength;
+ return ParseExactDigits(ref tokenizer, minDigitLength, maxDigitLength, out zeroes, out result);
+ }
+
+ private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, int maxDigitLength, out int zeroes, out int result)
+ {
+ int tmpResult = 0, tmpZeroes = 0;
+
+ int tokenLength = 0;
+ while (tokenLength < maxDigitLength)
+ {
+ char ch = tokenizer.NextChar;
+ if (ch < '0' || ch > '9')
+ {
+ tokenizer.BackOne();
+ break;
+ }
+
+ tmpResult = tmpResult * 10 + (ch - '0');
+ if (tmpResult == 0) tmpZeroes++;
+ tokenLength++;
+ }
+
+ zeroes = tmpZeroes;
+ result = tmpResult;
+ return tokenLength >= minDigitLength;
+ }
+
+ private static bool ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBuilder enquotedString)
+ {
+ for (int i = 0; i < enquotedString.Length; i++)
+ {
+ if (enquotedString[i] != tokenizer.NextChar)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Parses the "c" (constant) format. This code is 100% identical to the non-globalized v1.0-v3.5 TimeSpan.Parse() routine
+ /// and exists for performance/appcompat with legacy callers who cannot move onto the globalized Parse overloads.
+ /// </summary>
+ private static bool TryParseTimeSpanConstant(ReadOnlySpan<char> input, ref TimeSpanResult result) =>
+ new StringParser().TryParse(input, ref result);
+
+ private ref struct StringParser
+ {
+ private ReadOnlySpan<char> _str;
+ private char _ch;
+ private int _pos;
+ private int _len;
+
+ internal void NextChar()
+ {
+ if (_pos < _len)
+ {
+ _pos++;
+ }
+
+ _ch = _pos < _len ?
+ _str[_pos] :
+ (char)0;
+ }
+
+ internal char NextNonDigit()
+ {
+ int i = _pos;
+ while (i < _len)
+ {
+ char ch = _str[i];
+ if (ch < '0' || ch > '9') return ch;
+ i++;
+ }
+
+ return (char)0;
+ }
+
+ internal bool TryParse(ReadOnlySpan<char> input, ref TimeSpanResult result)
+ {
+ result.parsedTimeSpan._ticks = 0;
+
+ _str = input;
+ _len = input.Length;
+ _pos = -1;
+ NextChar();
+ SkipBlanks();
+
+ bool negative = false;
+ if (_ch == '-')
+ {
+ negative = true;
+ NextChar();
+ }
+
+ long time;
+ if (NextNonDigit() == ':')
+ {
+ if (!ParseTime(out time, ref result))
+ {
+ return false;
+ };
+ }
+ else
+ {
+ int days;
+ if (!ParseInt((int)(0x7FFFFFFFFFFFFFFFL / TimeSpan.TicksPerDay), out days, ref result))
+ {
+ return false;
+ }
+
+ time = days * TimeSpan.TicksPerDay;
+
+ if (_ch == '.')
+ {
+ NextChar();
+ long remainingTime;
+ if (!ParseTime(out remainingTime, ref result))
+ {
+ return false;
+ };
+ time += remainingTime;
+ }
+ }
+
+ if (negative)
+ {
+ time = -time;
+ // Allow -0 as well
+ if (time > 0)
+ {
+ return result.SetOverflowFailure();
+ }
+ }
+ else
+ {
+ if (time < 0)
+ {
+ return result.SetOverflowFailure();
+ }
+ }
+
+ SkipBlanks();
+
+ if (_pos < _len)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+
+ result.parsedTimeSpan._ticks = time;
+ return true;
+ }
+
+ internal bool ParseInt(int max, out int i, ref TimeSpanResult result)
+ {
+ i = 0;
+ int p = _pos;
+ while (_ch >= '0' && _ch <= '9')
+ {
+ if ((i & 0xF0000000) != 0)
+ {
+ return result.SetOverflowFailure();
+ }
+
+ i = i * 10 + _ch - '0';
+ if (i < 0)
+ {
+ return result.SetOverflowFailure();
+ }
+
+ NextChar();
+ }
+
+ if (p == _pos)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+
+ if (i > max)
+ {
+ return result.SetOverflowFailure();
+ }
+
+ return true;
+ }
+
+ internal bool ParseTime(out long time, ref TimeSpanResult result)
+ {
+ time = 0;
+ int unit;
+
+ if (!ParseInt(23, out unit, ref result))
+ {
+ return false;
+ }
+
+ time = unit * TimeSpan.TicksPerHour;
+ if (_ch != ':')
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+
+ NextChar();
+
+ if (!ParseInt(59, out unit, ref result))
+ {
+ return false;
+ }
+
+ time += unit * TimeSpan.TicksPerMinute;
+
+ if (_ch == ':')
+ {
+ NextChar();
+
+ // allow seconds with the leading zero
+ if (_ch != '.')
+ {
+ if (!ParseInt(59, out unit, ref result))
+ {
+ return false;
+ }
+ time += unit * TimeSpan.TicksPerSecond;
+ }
+
+ if (_ch == '.')
+ {
+ NextChar();
+ int f = (int)TimeSpan.TicksPerSecond;
+ while (f > 1 && _ch >= '0' && _ch <= '9')
+ {
+ f /= 10;
+ time += (_ch - '0') * f;
+ NextChar();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ internal void SkipBlanks()
+ {
+ while (_ch == ' ' || _ch == '\t') NextChar();
+ }
+ }
+
+ /// <summary>Common private ParseExactMultiple method called by both ParseExactMultiple and TryParseExactMultiple.</summary>
+ private static bool TryParseExactMultipleTimeSpan(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result)
+ {
+ if (formats == null)
+ {
+ return result.SetArgumentNullFailure(nameof(formats));
+ }
+
+ if (input.Length == 0)
+ {
+ return result.SetBadTimeSpanFailure();
+ }
+
+ if (formats.Length == 0)
+ {
+ return result.SetNoFormatSpecifierFailure();
+ }
+
+ // Do a loop through the provided formats and see if we can parse succesfully in
+ // one of the formats.
+ for (int i = 0; i < formats.Length; i++)
+ {
+ if (formats[i] == null || formats[i].Length == 0)
+ {
+ return result.SetBadFormatSpecifierFailure();
+ }
+
+ // Create a new non-throwing result each time to ensure the runs are independent.
+ TimeSpanResult innerResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
+
+ if (TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult))
+ {
+ result.parsedTimeSpan = innerResult.parsedTimeSpan;
+ return true;
+ }
+ }
+
+ return result.SetBadTimeSpanFailure();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanStyles.cs b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanStyles.cs
new file mode 100644
index 0000000000..68a47bcbe6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanStyles.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ [Flags]
+ public enum TimeSpanStyles
+ {
+ None = 0x00000000,
+ AssumeNegative = 0x00000001,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/UmAlQuraCalendar.cs b/src/System.Private.CoreLib/shared/System/Globalization/UmAlQuraCalendar.cs
new file mode 100644
index 0000000000..21a938f8f1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/UmAlQuraCalendar.cs
@@ -0,0 +1,858 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1900/04/30 2077/05/13
+ ** UmAlQura 1318/01/01 1500/12/30
+ */
+
+ public partial class UmAlQuraCalendar : Calendar
+ {
+ internal const int MinCalendarYear = 1318;
+ internal const int MaxCalendarYear = 1500;
+
+ internal struct DateMapping
+ {
+ internal DateMapping(int MonthsLengthFlags, int GYear, int GMonth, int GDay)
+ {
+ HijriMonthsLengthFlags = MonthsLengthFlags;
+ GregorianDate = new DateTime(GYear, GMonth, GDay);
+ }
+ internal int HijriMonthsLengthFlags;
+ internal DateTime GregorianDate;
+ }
+
+ private static readonly DateMapping[] s_hijriYearInfo = InitDateMapping();
+
+ private static DateMapping[] InitDateMapping()
+ {
+ short[] rawData = new short[]
+ {
+ //These data is taken from Tables/Excel/UmAlQura.xls please make sure that the two places are in sync
+ /* DaysPerM GY GM GD D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12
+ 1318*/0x02EA, 1900, 4, 30,/* 0 1 0 1 0 1 1 1 0 1 0 0 4/30/1900
+ 1319*/0x06E9, 1901, 4, 19,/* 1 0 0 1 0 1 1 1 0 1 1 0 4/19/1901
+ 1320*/0x0ED2, 1902, 4, 9,/* 0 1 0 0 1 0 1 1 0 1 1 1 4/9/1902
+ 1321*/0x0EA4, 1903, 3, 30,/* 0 0 1 0 0 1 0 1 0 1 1 1 3/30/1903
+ 1322*/0x0D4A, 1904, 3, 18,/* 0 1 0 1 0 0 1 0 1 0 1 1 3/18/1904
+ 1323*/0x0A96, 1905, 3, 7,/* 0 1 1 0 1 0 0 1 0 1 0 1 3/7/1905
+ 1324*/0x0536, 1906, 2, 24,/* 0 1 1 0 1 1 0 0 1 0 1 0 2/24/1906
+ 1325*/0x0AB5, 1907, 2, 13,/* 1 0 1 0 1 1 0 1 0 1 0 1 2/13/1907
+ 1326*/0x0DAA, 1908, 2, 3,/* 0 1 0 1 0 1 0 1 1 0 1 1 2/3/1908
+ 1327*/0x0BA4, 1909, 1, 23,/* 0 0 1 0 0 1 0 1 1 1 0 1 1/23/1909
+ 1328*/0x0B49, 1910, 1, 12,/* 1 0 0 1 0 0 1 0 1 1 0 1 1/12/1910
+ 1329*/0x0A93, 1911, 1, 1,/* 1 1 0 0 1 0 0 1 0 1 0 1 1/1/1911
+ 1330*/0x052B, 1911, 12, 21,/* 1 1 0 1 0 1 0 0 1 0 1 0 12/21/1911
+ 1331*/0x0A57, 1912, 12, 9,/* 1 1 1 0 1 0 1 0 0 1 0 1 12/9/1912
+ 1332*/0x04B6, 1913, 11, 29,/* 0 1 1 0 1 1 0 1 0 0 1 0 11/29/1913
+ 1333*/0x0AB5, 1914, 11, 18,/* 1 0 1 0 1 1 0 1 0 1 0 1 11/18/1914
+ 1334*/0x05AA, 1915, 11, 8,/* 0 1 0 1 0 1 0 1 1 0 1 0 11/8/1915
+ 1335*/0x0D55, 1916, 10, 27,/* 1 0 1 0 1 0 1 0 1 0 1 1 10/27/1916
+ 1336*/0x0D2A, 1917, 10, 17,/* 0 1 0 1 0 1 0 0 1 0 1 1 10/17/1917
+ 1337*/0x0A56, 1918, 10, 6,/* 0 1 1 0 1 0 1 0 0 1 0 1 10/6/1918
+ 1338*/0x04AE, 1919, 9, 25,/* 0 1 1 1 0 1 0 1 0 0 1 0 9/25/1919
+ 1339*/0x095D, 1920, 9, 13,/* 1 0 1 1 1 0 1 0 1 0 0 1 9/13/1920
+ 1340*/0x02EC, 1921, 9, 3,/* 0 0 1 1 0 1 1 1 0 1 0 0 9/3/1921
+ 1341*/0x06D5, 1922, 8, 23,/* 1 0 1 0 1 0 1 1 0 1 1 0 8/23/1922
+ 1342*/0x06AA, 1923, 8, 13,/* 0 1 0 1 0 1 0 1 0 1 1 0 8/13/1923
+ 1343*/0x0555, 1924, 8, 1,/* 1 0 1 0 1 0 1 0 1 0 1 0 8/1/1924
+ 1344*/0x04AB, 1925, 7, 21,/* 1 1 0 1 0 1 0 1 0 0 1 0 7/21/1925
+ 1345*/0x095B, 1926, 7, 10,/* 1 1 0 1 1 0 1 0 1 0 0 1 7/10/1926
+ 1346*/0x02BA, 1927, 6, 30,/* 0 1 0 1 1 1 0 1 0 1 0 0 6/30/1927
+ 1347*/0x0575, 1928, 6, 18,/* 1 0 1 0 1 1 1 0 1 0 1 0 6/18/1928
+ 1348*/0x0BB2, 1929, 6, 8,/* 0 1 0 0 1 1 0 1 1 1 0 1 6/8/1929
+ 1349*/0x0764, 1930, 5, 29,/* 0 0 1 0 0 1 1 0 1 1 1 0 5/29/1930
+ 1350*/0x0749, 1931, 5, 18,/* 1 0 0 1 0 0 1 0 1 1 1 0 5/18/1931
+ 1351*/0x0655, 1932, 5, 6,/* 1 0 1 0 1 0 1 0 0 1 1 0 5/6/1932
+ 1352*/0x02AB, 1933, 4, 25,/* 1 1 0 1 0 1 0 1 0 1 0 0 4/25/1933
+ 1353*/0x055B, 1934, 4, 14,/* 1 1 0 1 1 0 1 0 1 0 1 0 4/14/1934
+ 1354*/0x0ADA, 1935, 4, 4,/* 0 1 0 1 1 0 1 1 0 1 0 1 4/4/1935
+ 1355*/0x06D4, 1936, 3, 24,/* 0 0 1 0 1 0 1 1 0 1 1 0 3/24/1936
+ 1356*/0x0EC9, 1937, 3, 13,/* 1 0 0 1 0 0 1 1 0 1 1 1 3/13/1937
+ 1357*/0x0D92, 1938, 3, 3,/* 0 1 0 0 1 0 0 1 1 0 1 1 3/3/1938
+ 1358*/0x0D25, 1939, 2, 20,/* 1 0 1 0 0 1 0 0 1 0 1 1 2/20/1939
+ 1359*/0x0A4D, 1940, 2, 9,/* 1 0 1 1 0 0 1 0 0 1 0 1 2/9/1940
+ 1360*/0x02AD, 1941, 1, 28,/* 1 0 1 1 0 1 0 1 0 1 0 0 1/28/1941
+ 1361*/0x056D, 1942, 1, 17,/* 1 0 1 1 0 1 1 0 1 0 1 0 1/17/1942
+ 1362*/0x0B6A, 1943, 1, 7,/* 0 1 0 1 0 1 1 0 1 1 0 1 1/7/1943
+ 1363*/0x0B52, 1943, 12, 28,/* 0 1 0 0 1 0 1 0 1 1 0 1 12/28/1943
+ 1364*/0x0AA5, 1944, 12, 16,/* 1 0 1 0 0 1 0 1 0 1 0 1 12/16/1944
+ 1365*/0x0A4B, 1945, 12, 5,/* 1 1 0 1 0 0 1 0 0 1 0 1 12/5/1945
+ 1366*/0x0497, 1946, 11, 24,/* 1 1 1 0 1 0 0 1 0 0 1 0 11/24/1946
+ 1367*/0x0937, 1947, 11, 13,/* 1 1 1 0 1 1 0 0 1 0 0 1 11/13/1947
+ 1368*/0x02B6, 1948, 11, 2,/* 0 1 1 0 1 1 0 1 0 1 0 0 11/2/1948
+ 1369*/0x0575, 1949, 10, 22,/* 1 0 1 0 1 1 1 0 1 0 1 0 10/22/1949
+ 1370*/0x0D6A, 1950, 10, 12,/* 0 1 0 1 0 1 1 0 1 0 1 1 10/12/1950
+ 1371*/0x0D52, 1951, 10, 2,/* 0 1 0 0 1 0 1 0 1 0 1 1 10/2/1951
+ 1372*/0x0A96, 1952, 9, 20,/* 0 1 1 0 1 0 0 1 0 1 0 1 9/20/1952
+ 1373*/0x092D, 1953, 9, 9,/* 1 0 1 1 0 1 0 0 1 0 0 1 9/9/1953
+ 1374*/0x025D, 1954, 8, 29,/* 1 0 1 1 1 0 1 0 0 1 0 0 8/29/1954
+ 1375*/0x04DD, 1955, 8, 18,/* 1 0 1 1 1 0 1 1 0 0 1 0 8/18/1955
+ 1376*/0x0ADA, 1956, 8, 7,/* 0 1 0 1 1 0 1 1 0 1 0 1 8/7/1956
+ 1377*/0x05D4, 1957, 7, 28,/* 0 0 1 0 1 0 1 1 1 0 1 0 7/28/1957
+ 1378*/0x0DA9, 1958, 7, 17,/* 1 0 0 1 0 1 0 1 1 0 1 1 7/17/1958
+ 1379*/0x0D52, 1959, 7, 7,/* 0 1 0 0 1 0 1 0 1 0 1 1 7/7/1959
+ 1380*/0x0AAA, 1960, 6, 25,/* 0 1 0 1 0 1 0 1 0 1 0 1 6/25/1960
+ 1381*/0x04D6, 1961, 6, 14,/* 0 1 1 0 1 0 1 1 0 0 1 0 6/14/1961
+ 1382*/0x09B6, 1962, 6, 3,/* 0 1 1 0 1 1 0 1 1 0 0 1 6/3/1962
+ 1383*/0x0374, 1963, 5, 24,/* 0 0 1 0 1 1 1 0 1 1 0 0 5/24/1963
+ 1384*/0x0769, 1964, 5, 12,/* 1 0 0 1 0 1 1 0 1 1 1 0 5/12/1964
+ 1385*/0x0752, 1965, 5, 2,/* 0 1 0 0 1 0 1 0 1 1 1 0 5/2/1965
+ 1386*/0x06A5, 1966, 4, 21,/* 1 0 1 0 0 1 0 1 0 1 1 0 4/21/1966
+ 1387*/0x054B, 1967, 4, 10,/* 1 1 0 1 0 0 1 0 1 0 1 0 4/10/1967
+ 1388*/0x0AAB, 1968, 3, 29,/* 1 1 0 1 0 1 0 1 0 1 0 1 3/29/1968
+ 1389*/0x055A, 1969, 3, 19,/* 0 1 0 1 1 0 1 0 1 0 1 0 3/19/1969
+ 1390*/0x0AD5, 1970, 3, 8,/* 1 0 1 0 1 0 1 1 0 1 0 1 3/8/1970
+ 1391*/0x0DD2, 1971, 2, 26,/* 0 1 0 0 1 0 1 1 1 0 1 1 2/26/1971
+ 1392*/0x0DA4, 1972, 2, 16,/* 0 0 1 0 0 1 0 1 1 0 1 1 2/16/1972
+ 1393*/0x0D49, 1973, 2, 4,/* 1 0 0 1 0 0 1 0 1 0 1 1 2/4/1973
+ 1394*/0x0A95, 1974, 1, 24,/* 1 0 1 0 1 0 0 1 0 1 0 1 1/24/1974
+ 1395*/0x052D, 1975, 1, 13,/* 1 0 1 1 0 1 0 0 1 0 1 0 1/13/1975
+ 1396*/0x0A5D, 1976, 1, 2,/* 1 0 1 1 1 0 1 0 0 1 0 1 1/2/1976
+ 1397*/0x055A, 1976, 12, 22,/* 0 1 0 1 1 0 1 0 1 0 1 0 12/22/1976
+ 1398*/0x0AD5, 1977, 12, 11,/* 1 0 1 0 1 0 1 1 0 1 0 1 12/11/1977
+ 1399*/0x06AA, 1978, 12, 1,/* 0 1 0 1 0 1 0 1 0 1 1 0 12/1/1978
+ 1400*/0x0695, 1979, 11, 20,/* 1 0 1 0 1 0 0 1 0 1 1 0 11/20/1979
+ 1401*/0x052B, 1980, 11, 8,/* 1 1 0 1 0 1 0 0 1 0 1 0 11/8/1980
+ 1402*/0x0A57, 1981, 10, 28,/* 1 1 1 0 1 0 1 0 0 1 0 1 10/28/1981
+ 1403*/0x04AE, 1982, 10, 18,/* 0 1 1 1 0 1 0 1 0 0 1 0 10/18/1982
+ 1404*/0x0976, 1983, 10, 7,/* 0 1 1 0 1 1 1 0 1 0 0 1 10/7/1983
+ 1405*/0x056C, 1984, 9, 26,/* 0 0 1 1 0 1 1 0 1 0 1 0 9/26/1984
+ 1406*/0x0B55, 1985, 9, 15,/* 1 0 1 0 1 0 1 0 1 1 0 1 9/15/1985
+ 1407*/0x0AAA, 1986, 9, 5,/* 0 1 0 1 0 1 0 1 0 1 0 1 9/5/1986
+ 1408*/0x0A55, 1987, 8, 25,/* 1 0 1 0 1 0 1 0 0 1 0 1 8/25/1987
+ 1409*/0x04AD, 1988, 8, 13,/* 1 0 1 1 0 1 0 1 0 0 1 0 8/13/1988
+ 1410*/0x095D, 1989, 8, 2,/* 1 0 1 1 1 0 1 0 1 0 0 1 8/2/1989
+ 1411*/0x02DA, 1990, 7, 23,/* 0 1 0 1 1 0 1 1 0 1 0 0 7/23/1990
+ 1412*/0x05D9, 1991, 7, 12,/* 1 0 0 1 1 0 1 1 1 0 1 0 7/12/1991
+ 1413*/0x0DB2, 1992, 7, 1,/* 0 1 0 0 1 1 0 1 1 0 1 1 7/1/1992
+ 1414*/0x0BA4, 1993, 6, 21,/* 0 0 1 0 0 1 0 1 1 1 0 1 6/21/1993
+ 1415*/0x0B4A, 1994, 6, 10,/* 0 1 0 1 0 0 1 0 1 1 0 1 6/10/1994
+ 1416*/0x0A55, 1995, 5, 30,/* 1 0 1 0 1 0 1 0 0 1 0 1 5/30/1995
+ 1417*/0x02B5, 1996, 5, 18,/* 1 0 1 0 1 1 0 1 0 1 0 0 5/18/1996
+ 1418*/0x0575, 1997, 5, 7,/* 1 0 1 0 1 1 1 0 1 0 1 0 5/7/1997
+ 1419*/0x0B6A, 1998, 4, 27,/* 0 1 0 1 0 1 1 0 1 1 0 1 4/27/1998
+ 1420*/0x0BD2, 1999, 4, 17,/* 0 1 0 0 1 0 1 1 1 1 0 1 4/17/1999
+ 1421*/0x0BC4, 2000, 4, 6,/* 0 0 1 0 0 0 1 1 1 1 0 1 4/6/2000
+ 1422*/0x0B89, 2001, 3, 26,/* 1 0 0 1 0 0 0 1 1 1 0 1 3/26/2001
+ 1423*/0x0A95, 2002, 3, 15,/* 1 0 1 0 1 0 0 1 0 1 0 1 3/15/2002
+ 1424*/0x052D, 2003, 3, 4,/* 1 0 1 1 0 1 0 0 1 0 1 0 3/4/2003
+ 1425*/0x05AD, 2004, 2, 21,/* 1 0 1 1 0 1 0 1 1 0 1 0 2/21/2004
+ 1426*/0x0B6A, 2005, 2, 10,/* 0 1 0 1 0 1 1 0 1 1 0 1 2/10/2005
+ 1427*/0x06D4, 2006, 1, 31,/* 0 0 1 0 1 0 1 1 0 1 1 0 1/31/2006
+ 1428*/0x0DC9, 2007, 1, 20,/* 1 0 0 1 0 0 1 1 1 0 1 1 1/20/2007
+ 1429*/0x0D92, 2008, 1, 10,/* 0 1 0 0 1 0 0 1 1 0 1 1 1/10/2008
+ 1430*/0x0AA6, 2008, 12, 29,/* 0 1 1 0 0 1 0 1 0 1 0 1 12/29/2008
+ 1431*/0x0956, 2009, 12, 18,/* 0 1 1 0 1 0 1 0 1 0 0 1 12/18/2009
+ 1432*/0x02AE, 2010, 12, 7,/* 0 1 1 1 0 1 0 1 0 1 0 0 12/7/2010
+ 1433*/0x056D, 2011, 11, 26,/* 1 0 1 1 0 1 1 0 1 0 1 0 11/26/2011
+ 1434*/0x036A, 2012, 11, 15,/* 0 1 0 1 0 1 1 0 1 1 0 0 11/15/2012
+ 1435*/0x0B55, 2013, 11, 4,/* 1 0 1 0 1 0 1 0 1 1 0 1 11/4/2013
+ 1436*/0x0AAA, 2014, 10, 25,/* 0 1 0 1 0 1 0 1 0 1 0 1 10/25/2014
+ 1437*/0x094D, 2015, 10, 14,/* 1 0 1 1 0 0 1 0 1 0 0 1 10/14/2015
+ 1438*/0x049D, 2016, 10, 2,/* 1 0 1 1 1 0 0 1 0 0 1 0 10/2/2016
+ 1439*/0x095D, 2017, 9, 21,/* 1 0 1 1 1 0 1 0 1 0 0 1 9/21/2017
+ 1440*/0x02BA, 2018, 9, 11,/* 0 1 0 1 1 1 0 1 0 1 0 0 9/11/2018
+ 1441*/0x05B5, 2019, 8, 31,/* 1 0 1 0 1 1 0 1 1 0 1 0 8/31/2019
+ 1442*/0x05AA, 2020, 8, 20,/* 0 1 0 1 0 1 0 1 1 0 1 0 8/20/2020
+ 1443*/0x0D55, 2021, 8, 9,/* 1 0 1 0 1 0 1 0 1 0 1 1 8/9/2021
+ 1444*/0x0A9A, 2022, 7, 30,/* 0 1 0 1 1 0 0 1 0 1 0 1 7/30/2022
+ 1445*/0x092E, 2023, 7, 19,/* 0 1 1 1 0 1 0 0 1 0 0 1 7/19/2023
+ 1446*/0x026E, 2024, 7, 7,/* 0 1 1 1 0 1 1 0 0 1 0 0 7/7/2024
+ 1447*/0x055D, 2025, 6, 26,/* 1 0 1 1 1 0 1 0 1 0 1 0 6/26/2025
+ 1448*/0x0ADA, 2026, 6, 16,/* 0 1 0 1 1 0 1 1 0 1 0 1 6/16/2026
+ 1449*/0x06D4, 2027, 6, 6,/* 0 0 1 0 1 0 1 1 0 1 1 0 6/6/2027
+ 1450*/0x06A5, 2028, 5, 25,/* 1 0 1 0 0 1 0 1 0 1 1 0 5/25/2028
+ 1451*/0x054B, 2029, 5, 14,/* 1 1 0 1 0 0 1 0 1 0 1 0 5/14/2029
+ 1452*/0x0A97, 2030, 5, 3,/* 1 1 1 0 1 0 0 1 0 1 0 1 5/3/2030
+ 1453*/0x054E, 2031, 4, 23,/* 0 1 1 1 0 0 1 0 1 0 1 0 4/23/2031
+ 1454*/0x0AAE, 2032, 4, 11,/* 0 1 1 1 0 1 0 1 0 1 0 1 4/11/2032
+ 1455*/0x05AC, 2033, 4, 1,/* 0 0 1 1 0 1 0 1 1 0 1 0 4/1/2033
+ 1456*/0x0BA9, 2034, 3, 21,/* 1 0 0 1 0 1 0 1 1 1 0 1 3/21/2034
+ 1457*/0x0D92, 2035, 3, 11,/* 0 1 0 0 1 0 0 1 1 0 1 1 3/11/2035
+ 1458*/0x0B25, 2036, 2, 28,/* 1 0 1 0 0 1 0 0 1 1 0 1 2/28/2036
+ 1459*/0x064B, 2037, 2, 16,/* 1 1 0 1 0 0 1 0 0 1 1 0 2/16/2037
+ 1460*/0x0CAB, 2038, 2, 5,/* 1 1 0 1 0 1 0 1 0 0 1 1 2/5/2038
+ 1461*/0x055A, 2039, 1, 26,/* 0 1 0 1 1 0 1 0 1 0 1 0 1/26/2039
+ 1462*/0x0B55, 2040, 1, 15,/* 1 0 1 0 1 0 1 0 1 1 0 1 1/15/2040
+ 1463*/0x06D2, 2041, 1, 4,/* 0 1 0 0 1 0 1 1 0 1 1 0 1/4/2041
+ 1464*/0x0EA5, 2041, 12, 24,/* 1 0 1 0 0 1 0 1 0 1 1 1 12/24/2041
+ 1465*/0x0E4A, 2042, 12, 14,/* 0 1 0 1 0 0 1 0 0 1 1 1 12/14/2042
+ 1466*/0x0A95, 2043, 12, 3,/* 1 0 1 0 1 0 0 1 0 1 0 1 12/3/2043
+ 1467*/0x052D, 2044, 11, 21,/* 1 0 1 1 0 1 0 0 1 0 1 0 11/21/2044
+ 1468*/0x0AAD, 2045, 11, 10,/* 1 0 1 1 0 1 0 1 0 1 0 1 11/10/2045
+ 1469*/0x036C, 2046, 10, 31,/* 0 0 1 1 0 1 1 0 1 1 0 0 10/31/2046
+ 1470*/0x0759, 2047, 10, 20,/* 1 0 0 1 1 0 1 0 1 1 1 0 10/20/2047
+ 1471*/0x06D2, 2048, 10, 9,/* 0 1 0 0 1 0 1 1 0 1 1 0 10/9/2048
+ 1472*/0x0695, 2049, 9, 28,/* 1 0 1 0 1 0 0 1 0 1 1 0 9/28/2049
+ 1473*/0x052D, 2050, 9, 17,/* 1 0 1 1 0 1 0 0 1 0 1 0 9/17/2050
+ 1474*/0x0A5B, 2051, 9, 6,/* 1 1 0 1 1 0 1 0 0 1 0 1 9/6/2051
+ 1475*/0x04BA, 2052, 8, 26,/* 0 1 0 1 1 1 0 1 0 0 1 0 8/26/2052
+ 1476*/0x09BA, 2053, 8, 15,/* 0 1 0 1 1 1 0 1 1 0 0 1 8/15/2053
+ 1477*/0x03B4, 2054, 8, 5,/* 0 0 1 0 1 1 0 1 1 1 0 0 8/5/2054
+ 1478*/0x0B69, 2055, 7, 25,/* 1 0 0 1 0 1 1 0 1 1 0 1 7/25/2055
+ 1479*/0x0B52, 2056, 7, 14,/* 0 1 0 0 1 0 1 0 1 1 0 1 7/14/2056
+ 1480*/0x0AA6, 2057, 7, 3,/* 0 1 1 0 0 1 0 1 0 1 0 1 7/3/2057
+ 1481*/0x04B6, 2058, 6, 22,/* 0 1 1 0 1 1 0 1 0 0 1 0 6/22/2058
+ 1482*/0x096D, 2059, 6, 11,/* 1 0 1 1 0 1 1 0 1 0 0 1 6/11/2059
+ 1483*/0x02EC, 2060, 5, 31,/* 0 0 1 1 0 1 1 1 0 1 0 0 5/31/2060
+ 1484*/0x06D9, 2061, 5, 20,/* 1 0 0 1 1 0 1 1 0 1 1 0 5/20/2061
+ 1485*/0x0EB2, 2062, 5, 10,/* 0 1 0 0 1 1 0 1 0 1 1 1 5/10/2062
+ 1486*/0x0D54, 2063, 4, 30,/* 0 0 1 0 1 0 1 0 1 0 1 1 4/30/2063
+ 1487*/0x0D2A, 2064, 4, 18,/* 0 1 0 1 0 1 0 0 1 0 1 1 4/18/2064
+ 1488*/0x0A56, 2065, 4, 7,/* 0 1 1 0 1 0 1 0 0 1 0 1 4/7/2065
+ 1489*/0x04AE, 2066, 3, 27,/* 0 1 1 1 0 1 0 1 0 0 1 0 3/27/2066
+ 1490*/0x096D, 2067, 3, 16,/* 1 0 1 1 0 1 1 0 1 0 0 1 3/16/2067
+ 1491*/0x0D6A, 2068, 3, 5,/* 0 1 0 1 0 1 1 0 1 0 1 1 3/5/2068
+ 1492*/0x0B54, 2069, 2, 23,/* 0 0 1 0 1 0 1 0 1 1 0 1 2/23/2069
+ 1493*/0x0B29, 2070, 2, 12,/* 1 0 0 1 0 1 0 0 1 1 0 1 2/12/2070
+ 1494*/0x0A93, 2071, 2, 1,/* 1 1 0 0 1 0 0 1 0 1 0 1 2/1/2071
+ 1495*/0x052B, 2072, 1, 21,/* 1 1 0 1 0 1 0 0 1 0 1 0 1/21/2072
+ 1496*/0x0A57, 2073, 1, 9,/* 1 1 1 0 1 0 1 0 0 1 0 1 1/9/2073
+ 1497*/0x0536, 2073, 12, 30,/* 0 1 1 0 1 1 0 0 1 0 1 0 12/30/2073
+ 1498*/0x0AB5, 2074, 12, 19,/* 1 0 1 0 1 1 0 1 0 1 0 1 12/19/2074
+ 1499*/0x06AA, 2075, 12, 9,/* 0 1 0 1 0 1 0 1 0 1 1 0 12/9/2075
+ 1500*/0x0E93, 2076, 11, 27,/* 1 1 0 0 1 0 0 1 0 1 1 1 11/27/2076
+ 1501*/ 0, 2077, 11, 17,/* 0 0 0 0 0 0 0 0 0 0 0 0 11/17/2077
+ */ };
+ // Direct inline initialization of DateMapping array would produce a lot of code bloat.
+
+ // We take advantage of C# compiler compiles inline initialization of primitive type array into very compact code.
+ // So we start with raw data stored in primitive type array, and initialize the DateMapping out of it
+
+ DateMapping[] mapping = new DateMapping[rawData.Length / 4];
+ for (int i = 0; i < mapping.Length; i++)
+ mapping[i] = new DateMapping(rawData[i * 4], rawData[i * 4 + 1], rawData[i * 4 + 2], rawData[i * 4 + 3]);
+ return mapping;
+ }
+
+ public const int UmAlQuraEra = 1;
+
+ internal const int DateCycle = 30;
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+
+ // This is the minimal Gregorian date that we support in the UmAlQuraCalendar.
+ internal static DateTime minDate = new DateTime(1900, 4, 30);
+ internal static DateTime maxDate = new DateTime((new DateTime(2077, 11, 16, 23, 59, 59, 999)).Ticks + 9999);
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.LunarCalendar;
+ }
+ }
+
+ public UmAlQuraCalendar()
+ {
+ }
+
+ internal override CalendarId BaseCalendarID
+ {
+ get
+ {
+ return (CalendarId.HIJRI);
+ }
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.UMALQURA);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // HijriCalendar has same number of days as UmAlQuraCalendar for any given year
+ // HijriCalendar says year 1317 has 355 days.
+ return 355;
+ }
+ }
+
+ /*==========================ConvertHijriToGregorian==========================
+ ** Purpose: convert Hdate(year,month,day) to Gdate(year,month,day)
+ ** Arguments:
+ ** Input/Ouput: Hijrah date: year:yh, month:mh, day:dh
+ ** Output: Gregorian date: year:yg, month:mg, day:dg , day of week:dayweek
+ ** and returns flag found:1 not found:0
+ =========================ConvertHijriToGregorian============================*/
+ private static void ConvertHijriToGregorian(int HijriYear, int HijriMonth, int HijriDay, ref int yg, ref int mg, ref int dg)
+ {
+ Debug.Assert((HijriYear >= MinCalendarYear) && (HijriYear <= MaxCalendarYear), "Hijri year is out of range.");
+ Debug.Assert(HijriMonth >= 1, "Hijri month is out of range.");
+ Debug.Assert(HijriDay >= 1, "Hijri day is out of range.");
+ int index, b, nDays = HijriDay - 1;
+ DateTime dt;
+
+
+ index = HijriYear - MinCalendarYear;
+ dt = s_hijriYearInfo[index].GregorianDate;
+
+
+ b = s_hijriYearInfo[index].HijriMonthsLengthFlags;
+
+ for (int m = 1; m < HijriMonth; m++)
+ {
+ nDays = nDays + 29 + (b & 1); /* Add the months lengths before mh */
+ b = b >> 1;
+ }
+
+ dt = dt.AddDays(nDays);
+ dt.GetDatePart(out yg, out mg, out dg);
+ }
+
+ /*=================================GetAbsoluteDateUmAlQura==========================
+ **Action: Gets the Absolute date for the given UmAlQura date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ private static long GetAbsoluteDateUmAlQura(int year, int month, int day)
+ {
+ //Caller should check the validaty of year, month and day.
+
+ int yg = 0, mg = 0, dg = 0;
+ ConvertHijriToGregorian(year, month, day, ref yg, ref mg, ref dg);
+ return GregorianCalendar.GetAbsoluteDate(yg, mg, dg);
+ }
+
+ internal static void CheckTicksRange(long ticks)
+ {
+ if (ticks < minDate.Ticks || ticks > maxDate.Ticks)
+ {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ SR.ArgumentOutOfRange_CalendarRange,
+ minDate,
+ maxDate));
+ }
+ }
+
+ internal static void CheckEraRange(int era)
+ {
+ if (era != CurrentEra && era != UmAlQuraEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ internal static void CheckYearRange(int year, int era)
+ {
+ CheckEraRange(era);
+ if (year < MinCalendarYear || year > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MinCalendarYear,
+ MaxCalendarYear));
+ }
+ }
+
+ internal static void CheckYearMonthRange(int year, int month, int era)
+ {
+ CheckYearRange(year, era);
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+ }
+
+ /*========================ConvertGregorianToHijri============================
+ ** Purpose: convert DateTime to Hdate(year,month,day)
+ ** Arguments:
+ ** Input: DateTime
+ ** Output: Hijrah date: year:yh, month:mh, day:dh
+ ============================================================================*/
+ private static void ConvertGregorianToHijri(DateTime time, ref int HijriYear, ref int HijriMonth, ref int HijriDay)
+ {
+ int index, b, DaysPerThisMonth;
+ double nDays;
+ TimeSpan ts;
+ int yh1 = 0, mh1 = 0, dh1 = 0;
+
+ Debug.Assert((time.Ticks >= minDate.Ticks) && (time.Ticks <= maxDate.Ticks), "Gregorian date is out of range.");
+
+ // Find the index where we should start our search by quessing the Hijri year that we will be in HijriYearInfo.
+ // A Hijri year is 354 or 355 days. Use 355 days so that we will search from a lower index.
+
+ index = (int)((time.Ticks - minDate.Ticks) / Calendar.TicksPerDay) / 355;
+ do
+ {
+ } while (time.CompareTo(s_hijriYearInfo[++index].GregorianDate) > 0); //while greater
+
+ if (time.CompareTo(s_hijriYearInfo[index].GregorianDate) != 0)
+ {
+ index--;
+ }
+
+ ts = time.Subtract(s_hijriYearInfo[index].GregorianDate);
+ yh1 = index + MinCalendarYear;
+
+ mh1 = 1;
+ dh1 = 1;
+ nDays = ts.TotalDays;
+ b = s_hijriYearInfo[index].HijriMonthsLengthFlags;
+ DaysPerThisMonth = 29 + (b & 1);
+
+ while (nDays >= DaysPerThisMonth)
+ {
+ nDays -= DaysPerThisMonth;
+ b = b >> 1;
+ DaysPerThisMonth = 29 + (b & 1);
+ mh1++;
+ }
+ dh1 += (int)nDays;
+
+ HijriDay = dh1;
+ HijriMonth = mh1;
+ HijriYear = yh1;
+ }
+
+ /*=================================GetDatePart==========================
+ **Action: Returns a given date part of this <i>DateTime</i>. This method is used
+ ** to compute the year, day-of-year, month, or day part.
+ **Returns:
+ **Arguments:
+ **Exceptions: ArgumentException if part is incorrect.
+ **Notes:
+ ** First, we get the absolute date (the number of days from January 1st, 1 A.C) for the given ticks.
+ ** Use the formula (((AbsoluteDate - 226894) * 33) / (33 * 365 + 8)) + 1, we can a rough value for the UmAlQura year.
+ ** In order to get the exact UmAlQura year, we compare the exact absolute date for UmAlQuraYear and (UmAlQuraYear + 1).
+ ** From here, we can get the correct UmAlQura year.
+ ============================================================================*/
+
+ internal virtual int GetDatePart(DateTime time, int part)
+ {
+ int UmAlQuraYear = 0; // UmAlQura year
+ int UmAlQuraMonth = 0; // UmAlQura month
+ int UmAlQuraDay = 0; // UmAlQura day
+ long ticks = time.Ticks;
+ CheckTicksRange(ticks);
+
+ ConvertGregorianToHijri(time, ref UmAlQuraYear, ref UmAlQuraMonth, ref UmAlQuraDay);
+
+ if (part == DatePartYear)
+ return (UmAlQuraYear);
+
+ if (part == DatePartMonth)
+ return (UmAlQuraMonth);
+
+ if (part == DatePartDay)
+ return (UmAlQuraDay);
+
+ if (part == DatePartDayOfYear)
+ return (int)(GetAbsoluteDateUmAlQura(UmAlQuraYear, UmAlQuraMonth, UmAlQuraDay) - GetAbsoluteDateUmAlQura(UmAlQuraYear, 1, 1) + 1);
+
+ // Incorrect part value.
+ throw new InvalidOperationException(SR.InvalidOperation_DateTimeParsing);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ -120000,
+ 120000));
+ }
+ // Get the date in UmAlQura calendar.
+ int y = GetDatePart(time, DatePartYear);
+ int m = GetDatePart(time, DatePartMonth);
+ int d = GetDatePart(time, DatePartDay);
+ int i = m - 1 + months;
+
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+
+ if (d > 29)
+ {
+ int days = GetDaysInMonth(y, m);
+ if (d > days)
+ {
+ d = days;
+ }
+ }
+ CheckYearRange(y, UmAlQuraEra);
+ DateTime dt = new DateTime(GetAbsoluteDateUmAlQura(y, m, d) * TicksPerDay + time.Ticks % TicksPerDay);
+ Calendar.CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (dt);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (GetDatePart(time, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 354 or 355.
+ //
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (GetDatePart(time, DatePartDayOfYear));
+ }
+
+ /*
+ internal bool CouldBeLeapYear(int year)
+ {
+ return ((((year * 11) + 14) % 30) < 11);
+ }
+ */
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+
+ public override int GetDaysInMonth(int year, int month, int era)
+ {
+ CheckYearMonthRange(year, month, era);
+
+ if ((s_hijriYearInfo[year - MinCalendarYear].HijriMonthsLengthFlags & (1 << month - 1)) == 0)
+ return 29;
+ else
+ return 30;
+ }
+
+ internal static int RealGetDaysInYear(int year)
+ {
+ int days = 0, b;
+
+ Debug.Assert((year >= MinCalendarYear) && (year <= MaxCalendarYear), "Hijri year is out of range.");
+
+ b = s_hijriYearInfo[year - MinCalendarYear].HijriMonthsLengthFlags;
+
+ for (int m = 1; m <= 12; m++)
+ {
+ days = days + 29 + (b & 1); /* Add the months lengths before mh */
+ b = b >> 1;
+ }
+ Debug.Assert((days == 354) || (days == 355), "Hijri year has to be 354 or 355 days.");
+ return days;
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (RealGetDaysInYear(year));
+ }
+
+ // Returns the era for the specified DateTime value.
+
+
+ public override int GetEra(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return (UmAlQuraEra);
+ }
+
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { UmAlQuraEra });
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+
+ public override int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between MinCalendarYear and MaxCalendarYear.
+ //
+
+
+ public override int GetYear(DateTime time)
+ {
+ return (GetDatePart(time, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ if (day >= 1 && day <= 29)
+ {
+ CheckYearMonthRange(year, month, era);
+ return (false);
+ }
+
+ // The year/month/era value checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Day,
+ daysInMonth,
+ month));
+ }
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ CheckYearMonthRange(year, month, era);
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ if (RealGetDaysInYear(year) == 355)
+ return true;
+ else
+ return false;
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ if (day >= 1 && day <= 29)
+ {
+ CheckYearMonthRange(year, month, era);
+ goto DayInRang;
+ }
+
+ // The year/month/era value checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+
+ if (day < 1 || day > daysInMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Day,
+ daysInMonth,
+ month));
+ }
+ DayInRang:
+ long lDate = GetAbsoluteDateUmAlQura(year, month, day);
+
+ if (lDate >= 0)
+ {
+ return (new DateTime(lDate * GregorianCalendar.TicksPerDay + TimeToTicks(hour, minute, second, millisecond)));
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 1451;
+
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1)
+ {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ if (value != 99 && (value < MinCalendarYear || value > MaxCalendarYear))
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(value),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MinCalendarYear,
+ MaxCalendarYear));
+ }
+ VerifyWritable();
+ // We allow year 99 to be set so that one can make ToFourDigitYearMax a no-op by setting TwoDigitYearMax to 99.
+ twoDigitYearMax = value;
+ }
+ }
+
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (year < 100)
+ {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if ((year < MinCalendarYear) || (year > MaxCalendarYear))
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MinCalendarYear,
+ MaxCalendarYear));
+ }
+ return (year);
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/UnicodeCategory.cs b/src/System.Private.CoreLib/shared/System/Globalization/UnicodeCategory.cs
new file mode 100644
index 0000000000..f0ae1fdfd9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Globalization/UnicodeCategory.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ public enum UnicodeCategory
+ {
+ UppercaseLetter = 0,
+ LowercaseLetter = 1,
+ TitlecaseLetter = 2,
+ ModifierLetter = 3,
+ OtherLetter = 4,
+ NonSpacingMark = 5,
+ SpacingCombiningMark = 6,
+ EnclosingMark = 7,
+ DecimalDigitNumber = 8,
+ LetterNumber = 9,
+ OtherNumber = 10,
+ SpaceSeparator = 11,
+ LineSeparator = 12,
+ ParagraphSeparator = 13,
+ Control = 14,
+ Format = 15,
+ Surrogate = 16,
+ PrivateUse = 17,
+ ConnectorPunctuation = 18,
+ DashPunctuation = 19,
+ OpenPunctuation = 20,
+ ClosePunctuation = 21,
+ InitialQuotePunctuation = 22,
+ FinalQuotePunctuation = 23,
+ OtherPunctuation = 24,
+ MathSymbol = 25,
+ CurrencySymbol = 26,
+ ModifierSymbol = 27,
+ OtherSymbol = 28,
+ OtherNotAssigned = 29,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Guid.Unix.cs b/src/System.Private.CoreLib/shared/System/Guid.Unix.cs
new file mode 100644
index 0000000000..442e7f8837
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Guid.Unix.cs
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ partial struct Guid
+ {
+ // This will create a new random guid based on the https://www.ietf.org/rfc/rfc4122.txt
+ public static unsafe Guid NewGuid()
+ {
+ Guid g;
+ Interop.GetRandomBytes((byte*)&g, sizeof(Guid));
+
+ const ushort VersionMask = 0xF000;
+ const ushort RandomGuidVersion = 0x4000;
+
+ const byte ClockSeqHiAndReservedMask = 0xC0;
+ const byte ClockSeqHiAndReservedValue = 0x80;
+
+ // Modify bits indicating the type of the GUID
+
+ unchecked
+ {
+ // time_hi_and_version
+ g._c = (short)((g._c & ~VersionMask) | RandomGuidVersion);
+ // clock_seq_hi_and_reserved
+ g._d = (byte)((g._d & ~ClockSeqHiAndReservedMask) | ClockSeqHiAndReservedValue);
+ }
+
+ return g;
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Guid.Windows.cs b/src/System.Private.CoreLib/shared/System/Guid.Windows.cs
new file mode 100644
index 0000000000..f00fbe45b3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Guid.Windows.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ partial struct Guid
+ {
+ public static Guid NewGuid()
+ {
+ // CoCreateGuid should never return Guid.Empty, since it attempts to maintain some
+ // uniqueness guarantees.
+
+ Guid g;
+ int hr = Interop.Ole32.CoCreateGuid(out g);
+ // We don't expect that this will ever throw an error, none are even documented, and so we don't want to pull
+ // in the HR to ComException mappings into the core library just for this so we will try a generic exception if
+ // we ever hit this condition.
+ if (hr != 0)
+ {
+ Exception ex = new Exception();
+ ex.SetErrorCode(hr);
+ throw ex;
+ }
+ return g;
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Guid.cs b/src/System.Private.CoreLib/shared/System/Guid.cs
new file mode 100644
index 0000000000..37a8dce94c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Guid.cs
@@ -0,0 +1,1436 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ // Represents a Globally Unique Identifier.
+ [StructLayout(LayoutKind.Sequential)]
+ [Serializable]
+ [Runtime.Versioning.NonVersionable] // This only applies to field layout
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public partial struct Guid : IFormattable, IComparable, IComparable<Guid>, IEquatable<Guid>, ISpanFormattable
+ {
+ public static readonly Guid Empty = new Guid();
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Member variables
+ ////////////////////////////////////////////////////////////////////////////////
+ private int _a; // Do not rename (binary serialization)
+ private short _b; // Do not rename (binary serialization)
+ private short _c; // Do not rename (binary serialization)
+ private byte _d; // Do not rename (binary serialization)
+ private byte _e; // Do not rename (binary serialization)
+ private byte _f; // Do not rename (binary serialization)
+ private byte _g; // Do not rename (binary serialization)
+ private byte _h; // Do not rename (binary serialization)
+ private byte _i; // Do not rename (binary serialization)
+ private byte _j; // Do not rename (binary serialization)
+ private byte _k; // Do not rename (binary serialization)
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Constructors
+ ////////////////////////////////////////////////////////////////////////////////
+
+ // Creates a new guid from an array of bytes.
+ public Guid(byte[] b) :
+ this(new ReadOnlySpan<byte>(b ?? throw new ArgumentNullException(nameof(b))))
+ {
+ }
+
+ // Creates a new guid from a read-only span.
+ public Guid(ReadOnlySpan<byte> b)
+ {
+ if (b.Length != 16)
+ throw new ArgumentException(SR.Format(SR.Arg_GuidArrayCtor, "16"), nameof(b));
+
+ _a = b[3] << 24 | b[2] << 16 | b[1] << 8 | b[0];
+ _b = (short)(b[5] << 8 | b[4]);
+ _c = (short)(b[7] << 8 | b[6]);
+ _d = b[8];
+ _e = b[9];
+ _f = b[10];
+ _g = b[11];
+ _h = b[12];
+ _i = b[13];
+ _j = b[14];
+ _k = b[15];
+ }
+
+ [CLSCompliant(false)]
+ public Guid(uint a, ushort b, ushort c, byte d, byte e, byte f, byte g, byte h, byte i, byte j, byte k)
+ {
+ _a = (int)a;
+ _b = (short)b;
+ _c = (short)c;
+ _d = d;
+ _e = e;
+ _f = f;
+ _g = g;
+ _h = h;
+ _i = i;
+ _j = j;
+ _k = k;
+ }
+
+ // Creates a new GUID initialized to the value represented by the arguments.
+ //
+ public Guid(int a, short b, short c, byte[] d)
+ {
+ if (d == null)
+ throw new ArgumentNullException(nameof(d));
+ // Check that array is not too big
+ if (d.Length != 8)
+ throw new ArgumentException(SR.Format(SR.Arg_GuidArrayCtor, "8"), nameof(d));
+
+ _a = a;
+ _b = b;
+ _c = c;
+ _d = d[0];
+ _e = d[1];
+ _f = d[2];
+ _g = d[3];
+ _h = d[4];
+ _i = d[5];
+ _j = d[6];
+ _k = d[7];
+ }
+
+ // Creates a new GUID initialized to the value represented by the
+ // arguments. The bytes are specified like this to avoid endianness issues.
+ //
+ public Guid(int a, short b, short c, byte d, byte e, byte f, byte g, byte h, byte i, byte j, byte k)
+ {
+ _a = a;
+ _b = b;
+ _c = c;
+ _d = d;
+ _e = e;
+ _f = f;
+ _g = g;
+ _h = h;
+ _i = i;
+ _j = j;
+ _k = k;
+ }
+
+ [Flags]
+ private enum GuidStyles
+ {
+ None = 0x00000000,
+ AllowParenthesis = 0x00000001, //Allow the guid to be enclosed in parens
+ AllowBraces = 0x00000002, //Allow the guid to be enclosed in braces
+ AllowDashes = 0x00000004, //Allow the guid to contain dash group separators
+ AllowHexPrefix = 0x00000008, //Allow the guid to contain {0xdd,0xdd}
+ RequireParenthesis = 0x00000010, //Require the guid to be enclosed in parens
+ RequireBraces = 0x00000020, //Require the guid to be enclosed in braces
+ RequireDashes = 0x00000040, //Require the guid to contain dash group separators
+ RequireHexPrefix = 0x00000080, //Require the guid to contain {0xdd,0xdd}
+
+ HexFormat = RequireBraces | RequireHexPrefix, /* X */
+ NumberFormat = None, /* N */
+ DigitFormat = RequireDashes, /* D */
+ BraceFormat = RequireBraces | RequireDashes, /* B */
+ ParenthesisFormat = RequireParenthesis | RequireDashes, /* P */
+
+ Any = AllowParenthesis | AllowBraces | AllowDashes | AllowHexPrefix,
+ }
+ private enum GuidParseThrowStyle
+ {
+ None = 0,
+ All = 1,
+ AllButOverflow = 2
+ }
+ private enum ParseFailureKind
+ {
+ None = 0,
+ ArgumentNull = 1,
+ Format = 2,
+ FormatWithParameter = 3,
+ NativeException = 4,
+ FormatWithInnerException = 5
+ }
+
+ // This will store the result of the parsing. And it will eventually be used to construct a Guid instance.
+ private struct GuidResult
+ {
+ internal Guid _parsedGuid;
+ internal GuidParseThrowStyle _throwStyle;
+
+ private ParseFailureKind _failure;
+ private string _failureMessageID;
+ private object _failureMessageFormatArgument;
+ private string _failureArgumentName;
+ private Exception _innerException;
+
+ internal void Init(GuidParseThrowStyle canThrow)
+ {
+ _throwStyle = canThrow;
+ }
+
+ internal void SetFailure(Exception nativeException)
+ {
+ _failure = ParseFailureKind.NativeException;
+ _innerException = nativeException;
+ }
+
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID)
+ {
+ SetFailure(failure, failureMessageID, null, null, null);
+ }
+
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument)
+ {
+ SetFailure(failure, failureMessageID, failureMessageFormatArgument, null, null);
+ }
+
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument,
+ string failureArgumentName, Exception innerException)
+ {
+ Debug.Assert(failure != ParseFailureKind.NativeException, "ParseFailureKind.NativeException should not be used with this overload");
+ _failure = failure;
+ _failureMessageID = failureMessageID;
+ _failureMessageFormatArgument = failureMessageFormatArgument;
+ _failureArgumentName = failureArgumentName;
+ _innerException = innerException;
+ if (_throwStyle != GuidParseThrowStyle.None)
+ {
+ throw GetGuidParseException();
+ }
+ }
+
+ internal Exception GetGuidParseException()
+ {
+ switch (_failure)
+ {
+ case ParseFailureKind.ArgumentNull:
+ return new ArgumentNullException(_failureArgumentName, SR.GetResourceString(_failureMessageID));
+
+ case ParseFailureKind.FormatWithInnerException:
+ return new FormatException(SR.GetResourceString(_failureMessageID), _innerException);
+
+ case ParseFailureKind.FormatWithParameter:
+ return new FormatException(SR.Format(SR.GetResourceString(_failureMessageID), _failureMessageFormatArgument));
+
+ case ParseFailureKind.Format:
+ return new FormatException(SR.GetResourceString(_failureMessageID));
+
+ case ParseFailureKind.NativeException:
+ return _innerException;
+
+ default:
+ Debug.Fail("Unknown GuidParseFailure: " + _failure);
+ return new FormatException(SR.Format_GuidUnrecognized);
+ }
+ }
+ }
+
+ // Creates a new guid based on the value in the string. The value is made up
+ // of hex digits speared by the dash ("-"). The string may begin and end with
+ // brackets ("{", "}").
+ //
+ // The string must be of the form dddddddd-dddd-dddd-dddd-dddddddddddd. where
+ // d is a hex digit. (That is 8 hex digits, followed by 4, then 4, then 4,
+ // then 12) such as: "CA761232-ED42-11CE-BACD-00AA0057B223"
+ //
+ public Guid(string g)
+ {
+ if (g == null)
+ {
+ throw new ArgumentNullException(nameof(g));
+ }
+
+ GuidResult result = new GuidResult();
+ result.Init(GuidParseThrowStyle.All);
+ if (TryParseGuid(g, GuidStyles.Any, ref result))
+ {
+ this = result._parsedGuid;
+ }
+ else
+ {
+ throw result.GetGuidParseException();
+ }
+ }
+
+ public static Guid Parse(string input) =>
+ Parse(input != null ? (ReadOnlySpan<char>)input : throw new ArgumentNullException(nameof(input)));
+
+ public static Guid Parse(ReadOnlySpan<char> input)
+ {
+ GuidResult result = new GuidResult();
+ result.Init(GuidParseThrowStyle.AllButOverflow);
+ if (TryParseGuid(input, GuidStyles.Any, ref result))
+ {
+ return result._parsedGuid;
+ }
+ else
+ {
+ throw result.GetGuidParseException();
+ }
+ }
+
+ public static bool TryParse(string input, out Guid result)
+ {
+ if (input == null)
+ {
+ result = default(Guid);
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)input, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> input, out Guid result)
+ {
+ GuidResult parseResult = new GuidResult();
+ parseResult.Init(GuidParseThrowStyle.None);
+ if (TryParseGuid(input, GuidStyles.Any, ref parseResult))
+ {
+ result = parseResult._parsedGuid;
+ return true;
+ }
+ else
+ {
+ result = default(Guid);
+ return false;
+ }
+ }
+
+ public static Guid ParseExact(string input, string format) =>
+ ParseExact(
+ input != null ? (ReadOnlySpan<char>)input : throw new ArgumentNullException(nameof(input)),
+ format != null ? (ReadOnlySpan<char>)format : throw new ArgumentNullException(nameof(format)));
+
+ public static Guid ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format)
+ {
+ if (format.Length != 1)
+ {
+ // all acceptable format strings are of length 1
+ throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+ }
+
+ GuidStyles style;
+ switch (format[0])
+ {
+ case 'D':
+ case 'd':
+ style = GuidStyles.DigitFormat;
+ break;
+ case 'N':
+ case 'n':
+ style = GuidStyles.NumberFormat;
+ break;
+ case 'B':
+ case 'b':
+ style = GuidStyles.BraceFormat;
+ break;
+ case 'P':
+ case 'p':
+ style = GuidStyles.ParenthesisFormat;
+ break;
+ case 'X':
+ case 'x':
+ style = GuidStyles.HexFormat;
+ break;
+ default:
+ throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+ }
+
+ GuidResult result = new GuidResult();
+ result.Init(GuidParseThrowStyle.AllButOverflow);
+ if (TryParseGuid(input, style, ref result))
+ {
+ return result._parsedGuid;
+ }
+ else
+ {
+ throw result.GetGuidParseException();
+ }
+ }
+
+ public static bool TryParseExact(string input, string format, out Guid result)
+ {
+ if (input == null)
+ {
+ result = default(Guid);
+ return false;
+ }
+
+ return TryParseExact((ReadOnlySpan<char>)input, format, out result);
+ }
+
+ public static bool TryParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, out Guid result)
+ {
+ if (format.Length != 1)
+ {
+ result = default(Guid);
+ return false;
+ }
+
+ GuidStyles style;
+ switch (format[0])
+ {
+ case 'D':
+ case 'd':
+ style = GuidStyles.DigitFormat;
+ break;
+ case 'N':
+ case 'n':
+ style = GuidStyles.NumberFormat;
+ break;
+ case 'B':
+ case 'b':
+ style = GuidStyles.BraceFormat;
+ break;
+ case 'P':
+ case 'p':
+ style = GuidStyles.ParenthesisFormat;
+ break;
+ case 'X':
+ case 'x':
+ style = GuidStyles.HexFormat;
+ break;
+ default:
+ // invalid guid format specification
+ result = default(Guid);
+ return false;
+ }
+
+ GuidResult parseResult = new GuidResult();
+ parseResult.Init(GuidParseThrowStyle.None);
+ if (TryParseGuid(input, style, ref parseResult))
+ {
+ result = parseResult._parsedGuid;
+ return true;
+ }
+ else
+ {
+ result = default(Guid);
+ return false;
+ }
+ }
+
+ private static bool TryParseGuid(ReadOnlySpan<char> guidString, GuidStyles flags, ref GuidResult result)
+ {
+ guidString = guidString.Trim(); // Remove whitespace from beginning and end
+
+ if (guidString.Length == 0)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidUnrecognized));
+ return false;
+ }
+
+ // Check for dashes
+ bool dashesExistInString = guidString.IndexOf('-') >= 0;
+
+ if (dashesExistInString)
+ {
+ if ((flags & (GuidStyles.AllowDashes | GuidStyles.RequireDashes)) == 0)
+ {
+ // dashes are not allowed
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidUnrecognized));
+ return false;
+ }
+ }
+ else
+ {
+ if ((flags & GuidStyles.RequireDashes) != 0)
+ {
+ // dashes are required
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidUnrecognized));
+ return false;
+ }
+ }
+
+ // Check for braces
+ bool bracesExistInString = (guidString.IndexOf('{') >= 0);
+
+ if (bracesExistInString)
+ {
+ if ((flags & (GuidStyles.AllowBraces | GuidStyles.RequireBraces)) == 0)
+ {
+ // braces are not allowed
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidUnrecognized));
+ return false;
+ }
+ }
+ else
+ {
+ if ((flags & GuidStyles.RequireBraces) != 0)
+ {
+ // braces are required
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidUnrecognized));
+ return false;
+ }
+ }
+
+ // Check for parenthesis
+ bool parenthesisExistInString = (guidString.IndexOf('(') >= 0);
+
+ if (parenthesisExistInString)
+ {
+ if ((flags & (GuidStyles.AllowParenthesis | GuidStyles.RequireParenthesis)) == 0)
+ {
+ // parenthesis are not allowed
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidUnrecognized));
+ return false;
+ }
+ }
+ else
+ {
+ if ((flags & GuidStyles.RequireParenthesis) != 0)
+ {
+ // parenthesis are required
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidUnrecognized));
+ return false;
+ }
+ }
+
+ try
+ {
+ // let's get on with the parsing
+ if (dashesExistInString)
+ {
+ // Check if it's of the form [{|(]dddddddd-dddd-dddd-dddd-dddddddddddd[}|)]
+ return TryParseGuidWithDashes(guidString, ref result);
+ }
+ else if (bracesExistInString)
+ {
+ // Check if it's of the form {0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}
+ return TryParseGuidWithHexPrefix(guidString, ref result);
+ }
+ else
+ {
+ // Check if it's of the form dddddddddddddddddddddddddddddddd
+ return TryParseGuidWithNoStyle(guidString, ref result);
+ }
+ }
+ catch (IndexOutOfRangeException ex)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithInnerException, nameof(SR.Format_GuidUnrecognized), null, null, ex);
+ return false;
+ }
+ catch (ArgumentException ex)
+ {
+ result.SetFailure(ParseFailureKind.FormatWithInnerException, nameof(SR.Format_GuidUnrecognized), null, null, ex);
+ return false;
+ }
+ }
+
+ // Check if it's of the form {0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}
+ private static bool TryParseGuidWithHexPrefix(ReadOnlySpan<char> guidString, ref GuidResult result)
+ {
+ int numStart = 0;
+ int numLen = 0;
+
+ // Eat all of the whitespace
+ guidString = EatAllWhitespace(guidString);
+
+ // Check for leading '{'
+ if (guidString.Length == 0 || guidString[0] != '{')
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidBrace));
+ return false;
+ }
+
+ // Check for '0x'
+ if (!IsHexPrefix(guidString, 1))
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidHexPrefix), "{0xdddddddd, etc}");
+ return false;
+ }
+
+ // Find the end of this hex number (since it is not fixed length)
+ numStart = 3;
+ numLen = guidString.Slice(numStart).IndexOf(',');
+ if (numLen <= 0)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidComma));
+ return false;
+ }
+
+ if (!StringToInt(guidString.Slice(numStart, numLen) /*first DWORD*/, -1, ParseNumbers.IsTight, out result._parsedGuid._a, ref result))
+ return false;
+
+ // Check for '0x'
+ if (!IsHexPrefix(guidString, numStart + numLen + 1))
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidHexPrefix), "{0xdddddddd, 0xdddd, etc}");
+ return false;
+ }
+ // +3 to get by ',0x'
+ numStart = numStart + numLen + 3;
+ numLen = guidString.Slice(numStart).IndexOf(',');
+ if (numLen <= 0)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidComma));
+ return false;
+ }
+
+ // Read in the number
+ if (!StringToShort(guidString.Slice(numStart, numLen) /*first DWORD*/, -1, ParseNumbers.IsTight, out result._parsedGuid._b, ref result))
+ return false;
+ // Check for '0x'
+ if (!IsHexPrefix(guidString, numStart + numLen + 1))
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidHexPrefix), "{0xdddddddd, 0xdddd, 0xdddd, etc}");
+ return false;
+ }
+ // +3 to get by ',0x'
+ numStart = numStart + numLen + 3;
+ numLen = guidString.Slice(numStart).IndexOf(',');
+ if (numLen <= 0)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidComma));
+ return false;
+ }
+
+ // Read in the number
+ if (!StringToShort(guidString.Slice(numStart, numLen) /*first DWORD*/, -1, ParseNumbers.IsTight, out result._parsedGuid._c, ref result))
+ return false;
+
+ // Check for '{'
+ if (guidString.Length <= numStart + numLen + 1 || guidString[numStart + numLen + 1] != '{')
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidBrace));
+ return false;
+ }
+
+ // Prepare for loop
+ numLen++;
+ Span<byte> bytes = stackalloc byte[8];
+
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ // Check for '0x'
+ if (!IsHexPrefix(guidString, numStart + numLen + 1))
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidHexPrefix), "{... { ... 0xdd, ...}}");
+ return false;
+ }
+
+ // +3 to get by ',0x' or '{0x' for first case
+ numStart = numStart + numLen + 3;
+
+ // Calculate number length
+ if (i < 7) // first 7 cases
+ {
+ numLen = guidString.Slice(numStart).IndexOf(',');
+ if (numLen <= 0)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidComma));
+ return false;
+ }
+ }
+ else // last case ends with '}', not ','
+ {
+ numLen = guidString.Slice(numStart).IndexOf('}');
+ if (numLen <= 0)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidBraceAfterLastNumber));
+ return false;
+ }
+ }
+
+ // Read in the number
+ int signedNumber;
+ if (!StringToInt(guidString.Slice(numStart, numLen), -1, ParseNumbers.IsTight, out signedNumber, ref result))
+ {
+ return false;
+ }
+ uint number = (uint)signedNumber;
+
+ // check for overflow
+ if (number > 255)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Overflow_Byte));
+ return false;
+ }
+ bytes[i] = (byte)number;
+ }
+
+ result._parsedGuid._d = bytes[0];
+ result._parsedGuid._e = bytes[1];
+ result._parsedGuid._f = bytes[2];
+ result._parsedGuid._g = bytes[3];
+ result._parsedGuid._h = bytes[4];
+ result._parsedGuid._i = bytes[5];
+ result._parsedGuid._j = bytes[6];
+ result._parsedGuid._k = bytes[7];
+
+ // Check for last '}'
+ if (numStart + numLen + 1 >= guidString.Length || guidString[numStart + numLen + 1] != '}')
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidEndBrace));
+ return false;
+ }
+
+ // Check if we have extra characters at the end
+ if (numStart + numLen + 1 != guidString.Length - 1)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_ExtraJunkAtEnd));
+ return false;
+ }
+
+ return true;
+ }
+
+ // Check if it's of the form dddddddddddddddddddddddddddddddd
+ private static bool TryParseGuidWithNoStyle(ReadOnlySpan<char> guidString, ref GuidResult result)
+ {
+ int startPos = 0;
+ int temp;
+ long templ;
+ int currentPos = 0;
+
+ if (guidString.Length != 32)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidInvLen));
+ return false;
+ }
+
+ for (int i = 0; i < guidString.Length; i++)
+ {
+ char ch = guidString[i];
+ if (ch >= '0' && ch <= '9')
+ {
+ continue;
+ }
+ else
+ {
+ char upperCaseCh = char.ToUpperInvariant(ch);
+ if (upperCaseCh >= 'A' && upperCaseCh <= 'F')
+ {
+ continue;
+ }
+ }
+
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidInvalidChar));
+ return false;
+ }
+
+ if (!StringToInt(guidString.Slice(startPos, 8) /*first DWORD*/, -1, ParseNumbers.IsTight, out result._parsedGuid._a, ref result))
+ return false;
+
+ startPos += 8;
+ if (!StringToShort(guidString.Slice(startPos, 4), -1, ParseNumbers.IsTight, out result._parsedGuid._b, ref result))
+ return false;
+
+ startPos += 4;
+ if (!StringToShort(guidString.Slice(startPos, 4), -1, ParseNumbers.IsTight, out result._parsedGuid._c, ref result))
+ return false;
+
+ startPos += 4;
+ if (!StringToInt(guidString.Slice(startPos, 4), -1, ParseNumbers.IsTight, out temp, ref result))
+ return false;
+
+ startPos += 4;
+ currentPos = startPos;
+
+ if (!StringToLong(guidString, ref currentPos, ParseNumbers.NoSpace, out templ, ref result))
+ return false;
+
+ if (currentPos - startPos != 12)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidInvLen));
+ return false;
+ }
+
+ result._parsedGuid._d = (byte)(temp >> 8);
+ result._parsedGuid._e = (byte)(temp);
+ temp = (int)(templ >> 32);
+ result._parsedGuid._f = (byte)(temp >> 8);
+ result._parsedGuid._g = (byte)(temp);
+ temp = (int)(templ);
+ result._parsedGuid._h = (byte)(temp >> 24);
+ result._parsedGuid._i = (byte)(temp >> 16);
+ result._parsedGuid._j = (byte)(temp >> 8);
+ result._parsedGuid._k = (byte)(temp);
+
+ return true;
+ }
+
+ // Check if it's of the form [{|(]dddddddd-dddd-dddd-dddd-dddddddddddd[}|)]
+ private static bool TryParseGuidWithDashes(ReadOnlySpan<char> guidString, ref GuidResult result)
+ {
+ int startPos = 0;
+ int temp;
+ long templ;
+ int currentPos = 0;
+
+ // check to see that it's the proper length
+ if (guidString[0] == '{')
+ {
+ if (guidString.Length != 38 || guidString[37] != '}')
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidInvLen));
+ return false;
+ }
+ startPos = 1;
+ }
+ else if (guidString[0] == '(')
+ {
+ if (guidString.Length != 38 || guidString[37] != ')')
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidInvLen));
+ return false;
+ }
+ startPos = 1;
+ }
+ else if (guidString.Length != 36)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidInvLen));
+ return false;
+ }
+
+ if (guidString[8 + startPos] != '-' ||
+ guidString[13 + startPos] != '-' ||
+ guidString[18 + startPos] != '-' ||
+ guidString[23 + startPos] != '-')
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidDashes));
+ return false;
+ }
+
+ currentPos = startPos;
+ if (!StringToInt(guidString, ref currentPos, 8, ParseNumbers.NoSpace, out temp, ref result))
+ return false;
+ result._parsedGuid._a = temp;
+ ++currentPos; //Increment past the '-';
+
+ if (!StringToInt(guidString, ref currentPos, 4, ParseNumbers.NoSpace, out temp, ref result))
+ return false;
+ result._parsedGuid._b = (short)temp;
+ ++currentPos; //Increment past the '-';
+
+ if (!StringToInt(guidString, ref currentPos, 4, ParseNumbers.NoSpace, out temp, ref result))
+ return false;
+ result._parsedGuid._c = (short)temp;
+ ++currentPos; //Increment past the '-';
+
+ if (!StringToInt(guidString, ref currentPos, 4, ParseNumbers.NoSpace, out temp, ref result))
+ return false;
+ ++currentPos; //Increment past the '-';
+ startPos = currentPos;
+
+ if (!StringToLong(guidString, ref currentPos, ParseNumbers.NoSpace, out templ, ref result))
+ return false;
+
+ if (currentPos - startPos != 12)
+ {
+ result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidInvLen));
+ return false;
+ }
+ result._parsedGuid._d = (byte)(temp >> 8);
+ result._parsedGuid._e = (byte)(temp);
+ temp = (int)(templ >> 32);
+ result._parsedGuid._f = (byte)(temp >> 8);
+ result._parsedGuid._g = (byte)(temp);
+ temp = (int)(templ);
+ result._parsedGuid._h = (byte)(temp >> 24);
+ result._parsedGuid._i = (byte)(temp >> 16);
+ result._parsedGuid._j = (byte)(temp >> 8);
+ result._parsedGuid._k = (byte)(temp);
+
+ return true;
+ }
+
+ private static bool StringToShort(ReadOnlySpan<char> str, int requiredLength, int flags, out short result, ref GuidResult parseResult)
+ {
+ int parsePos = 0;
+ return StringToShort(str, ref parsePos, requiredLength, flags, out result, ref parseResult);
+ }
+
+ private static bool StringToShort(ReadOnlySpan<char> str, ref int parsePos, int requiredLength, int flags, out short result, ref GuidResult parseResult)
+ {
+ result = 0;
+ int x;
+ bool retValue = StringToInt(str, ref parsePos, requiredLength, flags, out x, ref parseResult);
+ result = (short)x;
+ return retValue;
+ }
+
+ private static bool StringToInt(ReadOnlySpan<char> str, int requiredLength, int flags, out int result, ref GuidResult parseResult)
+ {
+ int parsePos = 0;
+ return StringToInt(str, ref parsePos, requiredLength, flags, out result, ref parseResult);
+ }
+
+ private static bool StringToInt(ReadOnlySpan<char> str, ref int parsePos, int requiredLength, int flags, out int result, ref GuidResult parseResult)
+ {
+ result = 0;
+
+ int currStart = parsePos;
+ try
+ {
+ result = ParseNumbers.StringToInt(str, 16, flags, ref parsePos);
+ }
+ catch (OverflowException ex)
+ {
+ if (parseResult._throwStyle == GuidParseThrowStyle.All)
+ {
+ throw;
+ }
+ else if (parseResult._throwStyle == GuidParseThrowStyle.AllButOverflow)
+ {
+ throw new FormatException(SR.Format_GuidUnrecognized, ex);
+ }
+ else
+ {
+ parseResult.SetFailure(ex);
+ return false;
+ }
+ }
+ catch (Exception ex)
+ {
+ if (parseResult._throwStyle == GuidParseThrowStyle.None)
+ {
+ parseResult.SetFailure(ex);
+ return false;
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ //If we didn't parse enough characters, there's clearly an error.
+ if (requiredLength != -1 && parsePos - currStart != requiredLength)
+ {
+ parseResult.SetFailure(ParseFailureKind.Format, nameof(SR.Format_GuidInvalidChar));
+ return false;
+ }
+ return true;
+ }
+
+ private static unsafe bool StringToLong(ReadOnlySpan<char> str, ref int parsePos, int flags, out long result, ref GuidResult parseResult)
+ {
+ result = 0;
+
+ try
+ {
+ result = ParseNumbers.StringToLong(str, 16, flags, ref parsePos);
+ }
+ catch (OverflowException ex)
+ {
+ if (parseResult._throwStyle == GuidParseThrowStyle.All)
+ {
+ throw;
+ }
+ else if (parseResult._throwStyle == GuidParseThrowStyle.AllButOverflow)
+ {
+ throw new FormatException(SR.Format_GuidUnrecognized, ex);
+ }
+ else
+ {
+ parseResult.SetFailure(ex);
+ return false;
+ }
+ }
+ catch (Exception ex)
+ {
+ if (parseResult._throwStyle == GuidParseThrowStyle.None)
+ {
+ parseResult.SetFailure(ex);
+ return false;
+ }
+ else
+ {
+ throw;
+ }
+ }
+ return true;
+ }
+
+ private static ReadOnlySpan<char> EatAllWhitespace(ReadOnlySpan<char> str)
+ {
+ // Find the first whitespace character. If there is none, just return the input.
+ int i;
+ for (i = 0; i < str.Length && !char.IsWhiteSpace(str[i]); i++) ;
+ if (i == str.Length)
+ {
+ return str;
+ }
+
+ // There was at least one whitespace. Copy over everything prior to it to a new array.
+ var chArr = new char[str.Length];
+ int newLength = 0;
+ if (i > 0)
+ {
+ newLength = i;
+ str.Slice(0, i).CopyTo(chArr);
+ }
+
+ // Loop through the remaining chars, copying over non-whitespace.
+ for (; i < str.Length; i++)
+ {
+ char c = str[i];
+ if (!char.IsWhiteSpace(c))
+ {
+ chArr[newLength++] = c;
+ }
+ }
+
+ // Return the string with the whitespace removed.
+ return new ReadOnlySpan<char>(chArr, 0, newLength);
+ }
+
+ private static bool IsHexPrefix(ReadOnlySpan<char> str, int i) =>
+ i + 1 < str.Length &&
+ str[i] == '0' &&
+ (str[i + 1] == 'x' || char.ToLowerInvariant(str[i + 1]) == 'x');
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void WriteByteHelper(Span<byte> destination)
+ {
+ destination[0] = (byte)(_a);
+ destination[1] = (byte)(_a >> 8);
+ destination[2] = (byte)(_a >> 16);
+ destination[3] = (byte)(_a >> 24);
+ destination[4] = (byte)(_b);
+ destination[5] = (byte)(_b >> 8);
+ destination[6] = (byte)(_c);
+ destination[7] = (byte)(_c >> 8);
+ destination[8] = _d;
+ destination[9] = _e;
+ destination[10] = _f;
+ destination[11] = _g;
+ destination[12] = _h;
+ destination[13] = _i;
+ destination[14] = _j;
+ destination[15] = _k;
+ }
+
+ // Returns an unsigned byte array containing the GUID.
+ public byte[] ToByteArray()
+ {
+ var g = new byte[16];
+ WriteByteHelper(g);
+ return g;
+ }
+
+ // Returns whether bytes are sucessfully written to given span.
+ public bool TryWriteBytes(Span<byte> destination)
+ {
+ if (destination.Length < 16)
+ return false;
+
+ WriteByteHelper(destination);
+ return true;
+ }
+
+ // Returns the guid in "registry" format.
+ public override string ToString()
+ {
+ return ToString("D", null);
+ }
+
+ public override int GetHashCode()
+ {
+ // Simply XOR all the bits of the GUID 32 bits at a time.
+ return _a ^ Unsafe.Add(ref _a, 1) ^ Unsafe.Add(ref _a, 2) ^ Unsafe.Add(ref _a, 3);
+ }
+
+ // Returns true if and only if the guid represented
+ // by o is the same as this instance.
+ public override bool Equals(object o)
+ {
+ Guid g;
+ // Check that o is a Guid first
+ if (o == null || !(o is Guid))
+ return false;
+ else g = (Guid)o;
+
+ // Now compare each of the elements
+ return g._a == _a &&
+ Unsafe.Add(ref g._a, 1) == Unsafe.Add(ref _a, 1) &&
+ Unsafe.Add(ref g._a, 2) == Unsafe.Add(ref _a, 2) &&
+ Unsafe.Add(ref g._a, 3) == Unsafe.Add(ref _a, 3);
+ }
+
+ public bool Equals(Guid g)
+ {
+ // Now compare each of the elements
+ return g._a == _a &&
+ Unsafe.Add(ref g._a, 1) == Unsafe.Add(ref _a, 1) &&
+ Unsafe.Add(ref g._a, 2) == Unsafe.Add(ref _a, 2) &&
+ Unsafe.Add(ref g._a, 3) == Unsafe.Add(ref _a, 3);
+ }
+
+ private int GetResult(uint me, uint them)
+ {
+ if (me < them)
+ {
+ return -1;
+ }
+ return 1;
+ }
+
+ public int CompareTo(object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (!(value is Guid))
+ {
+ throw new ArgumentException(SR.Arg_MustBeGuid, nameof(value));
+ }
+ Guid g = (Guid)value;
+
+ if (g._a != _a)
+ {
+ return GetResult((uint)_a, (uint)g._a);
+ }
+
+ if (g._b != _b)
+ {
+ return GetResult((uint)_b, (uint)g._b);
+ }
+
+ if (g._c != _c)
+ {
+ return GetResult((uint)_c, (uint)g._c);
+ }
+
+ if (g._d != _d)
+ {
+ return GetResult(_d, g._d);
+ }
+
+ if (g._e != _e)
+ {
+ return GetResult(_e, g._e);
+ }
+
+ if (g._f != _f)
+ {
+ return GetResult(_f, g._f);
+ }
+
+ if (g._g != _g)
+ {
+ return GetResult(_g, g._g);
+ }
+
+ if (g._h != _h)
+ {
+ return GetResult(_h, g._h);
+ }
+
+ if (g._i != _i)
+ {
+ return GetResult(_i, g._i);
+ }
+
+ if (g._j != _j)
+ {
+ return GetResult(_j, g._j);
+ }
+
+ if (g._k != _k)
+ {
+ return GetResult(_k, g._k);
+ }
+
+ return 0;
+ }
+
+ public int CompareTo(Guid value)
+ {
+ if (value._a != _a)
+ {
+ return GetResult((uint)_a, (uint)value._a);
+ }
+
+ if (value._b != _b)
+ {
+ return GetResult((uint)_b, (uint)value._b);
+ }
+
+ if (value._c != _c)
+ {
+ return GetResult((uint)_c, (uint)value._c);
+ }
+
+ if (value._d != _d)
+ {
+ return GetResult(_d, value._d);
+ }
+
+ if (value._e != _e)
+ {
+ return GetResult(_e, value._e);
+ }
+
+ if (value._f != _f)
+ {
+ return GetResult(_f, value._f);
+ }
+
+ if (value._g != _g)
+ {
+ return GetResult(_g, value._g);
+ }
+
+ if (value._h != _h)
+ {
+ return GetResult(_h, value._h);
+ }
+
+ if (value._i != _i)
+ {
+ return GetResult(_i, value._i);
+ }
+
+ if (value._j != _j)
+ {
+ return GetResult(_j, value._j);
+ }
+
+ if (value._k != _k)
+ {
+ return GetResult(_k, value._k);
+ }
+
+ return 0;
+ }
+
+ public static bool operator ==(Guid a, Guid b)
+ {
+ // Now compare each of the elements
+ return a._a == b._a &&
+ Unsafe.Add(ref a._a, 1) == Unsafe.Add(ref b._a, 1) &&
+ Unsafe.Add(ref a._a, 2) == Unsafe.Add(ref b._a, 2) &&
+ Unsafe.Add(ref a._a, 3) == Unsafe.Add(ref b._a, 3);
+ }
+
+ public static bool operator !=(Guid a, Guid b)
+ {
+ // Now compare each of the elements
+ return a._a != b._a ||
+ Unsafe.Add(ref a._a, 1) != Unsafe.Add(ref b._a, 1) ||
+ Unsafe.Add(ref a._a, 2) != Unsafe.Add(ref b._a, 2) ||
+ Unsafe.Add(ref a._a, 3) != Unsafe.Add(ref b._a, 3);
+ }
+
+ public string ToString(string format)
+ {
+ return ToString(format, null);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static char HexToChar(int a)
+ {
+ a = a & 0xf;
+ return (char)((a > 9) ? a - 10 + 0x61 : a + 0x30);
+ }
+
+ private static unsafe int HexsToChars(char* guidChars, int a, int b)
+ {
+ guidChars[0] = HexToChar(a >> 4);
+ guidChars[1] = HexToChar(a);
+
+ guidChars[2] = HexToChar(b >> 4);
+ guidChars[3] = HexToChar(b);
+
+ return 4;
+ }
+
+ private static unsafe int HexsToCharsHexOutput(char* guidChars, int a, int b)
+ {
+ guidChars[0] = '0';
+ guidChars[1] = 'x';
+
+ guidChars[2] = HexToChar(a >> 4);
+ guidChars[3] = HexToChar(a);
+
+ guidChars[4] = ',';
+ guidChars[5] = '0';
+ guidChars[6] = 'x';
+
+ guidChars[7] = HexToChar(b >> 4);
+ guidChars[8] = HexToChar(b);
+
+ return 9;
+ }
+
+ // IFormattable interface
+ // We currently ignore provider
+ public string ToString(string format, IFormatProvider provider)
+ {
+ if (format == null || format.Length == 0)
+ format = "D";
+
+ // all acceptable format strings are of length 1
+ if (format.Length != 1)
+ throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+
+ int guidSize;
+ switch (format[0])
+ {
+ case 'D':
+ case 'd':
+ guidSize = 36;
+ break;
+ case 'N':
+ case 'n':
+ guidSize = 32;
+ break;
+ case 'B':
+ case 'b':
+ case 'P':
+ case 'p':
+ guidSize = 38;
+ break;
+ case 'X':
+ case 'x':
+ guidSize = 68;
+ break;
+ default:
+ throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+ }
+
+ string guidString = string.FastAllocateString(guidSize);
+
+ int bytesWritten;
+ bool result = TryFormat(new Span<char>(ref guidString.GetRawStringData(), guidString.Length), out bytesWritten, format);
+ Debug.Assert(result && bytesWritten == guidString.Length, "Formatting guid should have succeeded.");
+
+ return guidString;
+ }
+
+ // Returns whether the guid is successfully formatted as a span.
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default)
+ {
+ if (format.Length == 0)
+ format = "D";
+
+ // all acceptable format strings are of length 1
+ if (format.Length != 1)
+ throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+
+ bool dash = true;
+ bool hex = false;
+ int braces = 0;
+
+ int guidSize;
+
+ switch (format[0])
+ {
+ case 'D':
+ case 'd':
+ guidSize = 36;
+ break;
+ case 'N':
+ case 'n':
+ dash = false;
+ guidSize = 32;
+ break;
+ case 'B':
+ case 'b':
+ braces = '{' + ('}' << 16);
+ guidSize = 38;
+ break;
+ case 'P':
+ case 'p':
+ braces = '(' + (')' << 16);
+ guidSize = 38;
+ break;
+ case 'X':
+ case 'x':
+ braces = '{' + ('}' << 16);
+ dash = false;
+ hex = true;
+ guidSize = 68;
+ break;
+ default:
+ throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+ }
+
+ if (destination.Length < guidSize)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ unsafe
+ {
+ fixed (char* guidChars = &MemoryMarshal.GetReference(destination))
+ {
+ char * p = guidChars;
+
+ if (braces != 0)
+ *p++ = (char)braces;
+
+ if (hex)
+ {
+ // {0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}
+ *p++ = '0';
+ *p++ = 'x';
+ p += HexsToChars(p, _a >> 24, _a >> 16);
+ p += HexsToChars(p, _a >> 8, _a);
+ *p++ = ',';
+ *p++ = '0';
+ *p++ = 'x';
+ p += HexsToChars(p, _b >> 8, _b);
+ *p++ = ',';
+ *p++ = '0';
+ *p++ = 'x';
+ p += HexsToChars(p, _c >> 8, _c);
+ *p++ = ',';
+ *p++ = '{';
+ p += HexsToCharsHexOutput(p, _d, _e);
+ *p++ = ',';
+ p += HexsToCharsHexOutput(p, _f, _g);
+ *p++ = ',';
+ p += HexsToCharsHexOutput(p, _h, _i);
+ *p++ = ',';
+ p += HexsToCharsHexOutput(p, _j, _k);
+ *p++ = '}';
+ }
+ else
+ {
+ // [{|(]dddddddd[-]dddd[-]dddd[-]dddd[-]dddddddddddd[}|)]
+ p += HexsToChars(p, _a >> 24, _a >> 16);
+ p += HexsToChars(p, _a >> 8, _a);
+ if (dash)
+ *p++ = '-';
+ p += HexsToChars(p, _b >> 8, _b);
+ if (dash)
+ *p++ = '-';
+ p += HexsToChars(p, _c >> 8, _c);
+ if (dash)
+ *p++ = '-';
+ p += HexsToChars(p, _d, _e);
+ if (dash)
+ *p++ = '-';
+ p += HexsToChars(p, _f, _g);
+ p += HexsToChars(p, _h, _i);
+ p += HexsToChars(p, _j, _k);
+ }
+
+ if (braces != 0)
+ *p++ = (char)(braces >> 16);
+
+ Debug.Assert(p - guidChars == guidSize);
+ }
+ }
+
+ charsWritten = guidSize;
+ return true;
+ }
+
+ bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
+ {
+ // Like with the IFormattable implementation, provider is ignored.
+ return TryFormat(destination, out charsWritten, format);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/HResults.cs b/src/System.Private.CoreLib/shared/System/HResults.cs
new file mode 100644
index 0000000000..c242389db3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/HResults.cs
@@ -0,0 +1,128 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//=============================================================================
+//
+//
+// Purpose: Define HResult constants. Every exception has one of these.
+//
+//
+//===========================================================================*/
+// Note: FACILITY_URT is defined as 0x13 (0x8013xxxx). Within that
+// range, 0x1yyy is for Runtime errors (used for Security, Metadata, etc).
+// In that subrange, 0x15zz and 0x16zz have been allocated for classlib-type
+// HResults. Also note that some of our HResults have to map to certain
+// COM HR's, etc.
+
+// Reflection will use 0x1600 -> 0x161f. IO will use 0x1620 -> 0x163f.
+// Security will use 0x1640 -> 0x165f
+
+
+using System;
+
+namespace System
+{
+ internal static partial class HResults
+ {
+ internal const int S_OK = unchecked((int)0x00000000);
+ internal const int COR_E_ABANDONEDMUTEX = unchecked((int)0x8013152D);
+ internal const int COR_E_AMBIGUOUSMATCH = unchecked((int)0x8000211D);
+ internal const int COR_E_APPDOMAINUNLOADED = unchecked((int)0x80131014);
+ internal const int COR_E_APPLICATION = unchecked((int)0x80131600);
+ internal const int COR_E_ARGUMENT = unchecked((int)0x80070057);
+ internal const int COR_E_ARGUMENTOUTOFRANGE = unchecked((int)0x80131502);
+ internal const int COR_E_ARITHMETIC = unchecked((int)0x80070216);
+ internal const int COR_E_ARRAYTYPEMISMATCH = unchecked((int)0x80131503);
+ internal const int COR_E_BADEXEFORMAT = unchecked((int)0x800700C1);
+ internal const int COR_E_BADIMAGEFORMAT = unchecked((int)0x8007000B);
+ internal const int COR_E_CANNOTUNLOADAPPDOMAIN = unchecked((int)0x80131015);
+ internal const int COR_E_COMEMULATE = unchecked((int)0x80131535);
+ internal const int COR_E_CONTEXTMARSHAL = unchecked((int)0x80131504);
+ internal const int COR_E_CUSTOMATTRIBUTEFORMAT = unchecked((int)0x80131605);
+ internal const int COR_E_DATAMISALIGNED = unchecked((int)0x80131541);
+ internal const int COR_E_DIRECTORYNOTFOUND = unchecked((int)0x80070003);
+ internal const int COR_E_DIVIDEBYZERO = unchecked((int)0x80020012); // DISP_E_DIVBYZERO
+ internal const int COR_E_DLLNOTFOUND = unchecked((int)0x80131524);
+ internal const int COR_E_DUPLICATEWAITOBJECT = unchecked((int)0x80131529);
+ internal const int COR_E_ENDOFSTREAM = unchecked((int)0x80070026); // OS defined
+ internal const int COR_E_ENTRYPOINTNOTFOUND = unchecked((int)0x80131523);
+ internal const int COR_E_EXCEPTION = unchecked((int)0x80131500);
+ internal const int COR_E_EXECUTIONENGINE = unchecked((int)0x80131506);
+ internal const int COR_E_FIELDACCESS = unchecked((int)0x80131507);
+ internal const int COR_E_FILELOAD = unchecked((int)0x80131621);
+ internal const int COR_E_FILENOTFOUND = unchecked((int)0x80070002);
+ internal const int COR_E_FORMAT = unchecked((int)0x80131537);
+ internal const int COR_E_HOSTPROTECTION = unchecked((int)0x80131640);
+ internal const int COR_E_INDEXOUTOFRANGE = unchecked((int)0x80131508);
+ internal const int COR_E_INSUFFICIENTEXECUTIONSTACK = unchecked((int)0x80131578);
+ internal const int COR_E_INSUFFICIENTMEMORY = unchecked((int)0x8013153D);
+ internal const int COR_E_INVALIDCAST = unchecked((int)0x80004002);
+ internal const int COR_E_INVALIDCOMOBJECT = unchecked((int)0x80131527);
+ internal const int COR_E_INVALIDFILTERCRITERIA = unchecked((int)0x80131601);
+ internal const int COR_E_INVALIDOLEVARIANTTYPE = unchecked((int)0x80131531);
+ internal const int COR_E_INVALIDOPERATION = unchecked((int)0x80131509);
+ internal const int COR_E_INVALIDPROGRAM = unchecked((int)0x8013153A);
+ internal const int COR_E_IO = unchecked((int)0x80131620);
+ internal const int COR_E_KEYNOTFOUND = unchecked((int)0x80131577);
+ internal const int COR_E_MARSHALDIRECTIVE = unchecked((int)0x80131535);
+ internal const int COR_E_MEMBERACCESS = unchecked((int)0x8013151A);
+ internal const int COR_E_METHODACCESS = unchecked((int)0x80131510);
+ internal const int COR_E_MISSINGFIELD = unchecked((int)0x80131511);
+ internal const int COR_E_MISSINGMANIFESTRESOURCE = unchecked((int)0x80131532);
+ internal const int COR_E_MISSINGMEMBER = unchecked((int)0x80131512);
+ internal const int COR_E_MISSINGMETHOD = unchecked((int)0x80131513);
+ internal const int COR_E_MISSINGSATELLITEASSEMBLY = unchecked((int)0x80131536);
+ internal const int COR_E_MULTICASTNOTSUPPORTED = unchecked((int)0x80131514);
+ internal const int COR_E_NOTFINITENUMBER = unchecked((int)0x80131528);
+ internal const int COR_E_NOTSUPPORTED = unchecked((int)0x80131515);
+ internal const int COR_E_NULLREFERENCE = unchecked((int)0x80004003);
+ internal const int COR_E_OBJECTDISPOSED = unchecked((int)0x80131622);
+ internal const int COR_E_OPERATIONCANCELED = unchecked((int)0x8013153B);
+ internal const int COR_E_OUTOFMEMORY = unchecked((int)0x8007000E);
+ internal const int COR_E_OVERFLOW = unchecked((int)0x80131516);
+ internal const int COR_E_PATHTOOLONG = unchecked((int)0x800700CE);
+ internal const int COR_E_PLATFORMNOTSUPPORTED = unchecked((int)0x80131539);
+ internal const int COR_E_RANK = unchecked((int)0x80131517);
+ internal const int COR_E_REFLECTIONTYPELOAD = unchecked((int)0x80131602);
+ internal const int COR_E_RUNTIMEWRAPPED = unchecked((int)0x8013153E);
+ internal const int COR_E_SAFEARRAYRANKMISMATCH = unchecked((int)0x80131538);
+ internal const int COR_E_SAFEARRAYTYPEMISMATCH = unchecked((int)0x80131533);
+ internal const int COR_E_SAFEHANDLEMISSINGATTRIBUTE = unchecked((int)0x80131623);
+ internal const int COR_E_SECURITY = unchecked((int)0x8013150A);
+ internal const int COR_E_SEMAPHOREFULL = unchecked((int)0x8013152B);
+ internal const int COR_E_SERIALIZATION = unchecked((int)0x8013150C);
+ internal const int COR_E_STACKOVERFLOW = unchecked((int)0x800703E9);
+ internal const int COR_E_SYNCHRONIZATIONLOCK = unchecked((int)0x80131518);
+ internal const int COR_E_SYSTEM = unchecked((int)0x80131501);
+ internal const int COR_E_TARGET = unchecked((int)0x80131603);
+ internal const int COR_E_TARGETINVOCATION = unchecked((int)0x80131604);
+ internal const int COR_E_TARGETPARAMCOUNT = unchecked((int)0x8002000E);
+ internal const int COR_E_THREADABORTED = unchecked((int)0x80131530);
+ internal const int COR_E_THREADINTERRUPTED = unchecked((int)0x80131519);
+ internal const int COR_E_THREADSTART = unchecked((int)0x80131525);
+ internal const int COR_E_THREADSTATE = unchecked((int)0x80131520);
+ internal const int COR_E_THREADSTOP = unchecked((int)0x80131521);
+ internal const int COR_E_TIMEOUT = unchecked((int)0x80131505);
+ internal const int COR_E_TYPEACCESS = unchecked((int)0x80131543);
+ internal const int COR_E_TYPEINITIALIZATION = unchecked((int)0x80131534);
+ internal const int COR_E_TYPELOAD = unchecked((int)0x80131522);
+ internal const int COR_E_TYPEUNLOADED = unchecked((int)0x80131013);
+ internal const int COR_E_UNAUTHORIZEDACCESS = unchecked((int)0x80070005);
+ internal const int COR_E_UNSUPPORTEDFORMAT = unchecked((int)0x80131523);
+ internal const int COR_E_VERIFICATION = unchecked((int)0x8013150D);
+ internal const int COR_E_WAITHANDLECANNOTBEOPENED = unchecked((int)0x8013152C);
+ internal const int DISP_E_OVERFLOW = unchecked((int)0x8002000A);
+ internal const int E_BOUNDS = unchecked((int)0x8000000B);
+ internal const int E_CHANGED_STATE = unchecked((int)0x8000000C);
+ internal const int E_FAIL = unchecked((int)0x80004005);
+ internal const int E_HANDLE = unchecked((int)0x80070006);
+ internal const int E_INVALIDARG = unchecked((int)0x80070057);
+ internal const int E_NOTIMPL = unchecked((int)0x80004001);
+ internal const int E_POINTER = unchecked((int)0x80004003);
+ internal const int ERROR_MRM_MAP_NOT_FOUND = unchecked((int)0x80073B1F);
+ internal const int RO_E_CLOSED = unchecked((int)0x80000013);
+ internal const int TYPE_E_TYPEMISMATCH = unchecked((int)0x80028CA0);
+ internal const int CO_E_NOTINITIALIZED = unchecked((int)0x800401F0);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/HashCode.cs b/src/System.Private.CoreLib/shared/System/HashCode.cs
new file mode 100644
index 0000000000..4be57be02a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/HashCode.cs
@@ -0,0 +1,430 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*
+
+The xxHash32 implementation is based on the code published by Yann Collet:
+https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c
+
+ xxHash - Fast Hash algorithm
+ Copyright (C) 2012-2016, Yann Collet
+
+ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ You can contact the author at :
+ - xxHash homepage: http://www.xxhash.com
+ - xxHash source repository : https://github.com/Cyan4973/xxHash
+
+*/
+
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ // xxHash32 is used for the hash code.
+ // https://github.com/Cyan4973/xxHash
+
+ public struct HashCode
+ {
+ private static readonly uint s_seed = GenerateGlobalSeed();
+
+ private const uint Prime1 = 2654435761U;
+ private const uint Prime2 = 2246822519U;
+ private const uint Prime3 = 3266489917U;
+ private const uint Prime4 = 668265263U;
+ private const uint Prime5 = 374761393U;
+
+ private uint _v1, _v2, _v3, _v4;
+ private uint _queue1, _queue2, _queue3;
+ private uint _length;
+
+ private static unsafe uint GenerateGlobalSeed()
+ {
+ uint result;
+ Interop.GetRandomBytes((byte*)&result, sizeof(uint));
+ return result;
+ }
+
+ public static int Combine<T1>(T1 value1)
+ {
+ // Provide a way of diffusing bits from something with a limited
+ // input hash space. For example, many enums only have a few
+ // possible hashes, only using the bottom few bits of the code. Some
+ // collections are built on the assumption that hashes are spread
+ // over a larger space, so diffusing the bits may help the
+ // collection work more efficiently.
+
+ var hc1 = (uint)(value1?.GetHashCode() ?? 0);
+
+ uint hash = MixEmptyState();
+ hash += 4;
+
+ hash = QueueRound(hash, hc1);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine<T1, T2>(T1 value1, T2 value2)
+ {
+ var hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ var hc2 = (uint)(value2?.GetHashCode() ?? 0);
+
+ uint hash = MixEmptyState();
+ hash += 8;
+
+ hash = QueueRound(hash, hc1);
+ hash = QueueRound(hash, hc2);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3)
+ {
+ var hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ var hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ var hc3 = (uint)(value3?.GetHashCode() ?? 0);
+
+ uint hash = MixEmptyState();
+ hash += 12;
+
+ hash = QueueRound(hash, hc1);
+ hash = QueueRound(hash, hc2);
+ hash = QueueRound(hash, hc3);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4)
+ {
+ var hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ var hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ var hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ var hc4 = (uint)(value4?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 16;
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
+ {
+ var hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ var hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ var hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ var hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ var hc5 = (uint)(value5?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 20;
+
+ hash = QueueRound(hash, hc5);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine<T1, T2, T3, T4, T5, T6>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
+ {
+ var hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ var hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ var hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ var hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ var hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ var hc6 = (uint)(value6?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 24;
+
+ hash = QueueRound(hash, hc5);
+ hash = QueueRound(hash, hc6);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine<T1, T2, T3, T4, T5, T6, T7>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7)
+ {
+ var hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ var hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ var hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ var hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ var hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ var hc6 = (uint)(value6?.GetHashCode() ?? 0);
+ var hc7 = (uint)(value7?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 28;
+
+ hash = QueueRound(hash, hc5);
+ hash = QueueRound(hash, hc6);
+ hash = QueueRound(hash, hc7);
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ public static int Combine<T1, T2, T3, T4, T5, T6, T7, T8>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8)
+ {
+ var hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ var hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ var hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ var hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ var hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ var hc6 = (uint)(value6?.GetHashCode() ?? 0);
+ var hc7 = (uint)(value7?.GetHashCode() ?? 0);
+ var hc8 = (uint)(value8?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ v1 = Round(v1, hc5);
+ v2 = Round(v2, hc6);
+ v3 = Round(v3, hc7);
+ v4 = Round(v4, hc8);
+
+ uint hash = MixState(v1, v2, v3, v4);
+ hash += 32;
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint Rol(uint value, int count)
+ => (value << count) | (value >> (32 - count));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4)
+ {
+ v1 = s_seed + Prime1 + Prime2;
+ v2 = s_seed + Prime2;
+ v3 = s_seed;
+ v4 = s_seed - Prime1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint Round(uint hash, uint input)
+ {
+ hash += input * Prime2;
+ hash = Rol(hash, 13);
+ hash *= Prime1;
+ return hash;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint QueueRound(uint hash, uint queuedValue)
+ {
+ hash += queuedValue * Prime3;
+ return Rol(hash, 17) * Prime4;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint MixState(uint v1, uint v2, uint v3, uint v4)
+ {
+ return Rol(v1, 1) + Rol(v2, 7) + Rol(v3, 12) + Rol(v4, 18);
+ }
+
+ private static uint MixEmptyState()
+ {
+ return s_seed + Prime5;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint MixFinal(uint hash)
+ {
+ hash ^= hash >> 15;
+ hash *= Prime2;
+ hash ^= hash >> 13;
+ hash *= Prime3;
+ hash ^= hash >> 16;
+ return hash;
+ }
+
+ public void Add<T>(T value)
+ {
+ Add(value?.GetHashCode() ?? 0);
+ }
+
+ public void Add<T>(T value, IEqualityComparer<T> comparer)
+ {
+ Add(comparer != null ? comparer.GetHashCode(value) : (value?.GetHashCode() ?? 0));
+ }
+
+ private void Add(int value)
+ {
+ // The original xxHash works as follows:
+ // 0. Initialize immediately. We can't do this in a struct (no
+ // default ctor).
+ // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators.
+ // 2. Accumulate remaining blocks of length 4 (1 uint) into the
+ // hash.
+ // 3. Accumulate remaining blocks of length 1 into the hash.
+
+ // There is no need for #3 as this type only accepts ints. _queue1,
+ // _queue2 and _queue3 are basically a buffer so that when
+ // ToHashCode is called we can execute #2 correctly.
+
+ // We need to initialize the xxHash32 state (_v1 to _v4) lazily (see
+ // #0) nd the last place that can be done if you look at the
+ // original code is just before the first block of 16 bytes is mixed
+ // in. The xxHash32 state is never used for streams containing fewer
+ // than 16 bytes.
+
+ // To see what's really going on here, have a look at the Combine
+ // methods.
+
+ var val = (uint)value;
+
+ // Storing the value of _length locally shaves of quite a few bytes
+ // in the resulting machine code.
+ uint previousLength = _length++;
+ uint position = previousLength % 4;
+
+ // Switch can't be inlined.
+
+ if (position == 0)
+ _queue1 = val;
+ else if (position == 1)
+ _queue2 = val;
+ else if (position == 2)
+ _queue3 = val;
+ else // position == 3
+ {
+ if (previousLength == 3)
+ Initialize(out _v1, out _v2, out _v3, out _v4);
+
+ _v1 = Round(_v1, _queue1);
+ _v2 = Round(_v2, _queue2);
+ _v3 = Round(_v3, _queue3);
+ _v4 = Round(_v4, val);
+ }
+ }
+
+ public int ToHashCode()
+ {
+ // Storing the value of _length locally shaves of quite a few bytes
+ // in the resulting machine code.
+ uint length = _length;
+
+ // position refers to the *next* queue position in this method, so
+ // position == 1 means that _queue1 is populated; _queue2 would have
+ // been populated on the next call to Add.
+ uint position = length % 4;
+
+ // If the length is less than 4, _v1 to _v4 don't contain anything
+ // yet. xxHash32 treats this differently.
+
+ uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4);
+
+ // _length is incremented once per Add(Int32) and is therefore 4
+ // times too small (xxHash length is in bytes, not ints).
+
+ hash += length * 4;
+
+ // Mix what remains in the queue
+
+ // Switch can't be inlined right now, so use as few branches as
+ // possible by manually excluding impossible scenarios (position > 1
+ // is always false if position is not > 0).
+ if (position > 0)
+ {
+ hash = QueueRound(hash, _queue1);
+ if (position > 1)
+ {
+ hash = QueueRound(hash, _queue2);
+ if (position > 2)
+ hash = QueueRound(hash, _queue3);
+ }
+ }
+
+ hash = MixFinal(hash);
+ return (int)hash;
+ }
+
+#pragma warning disable 0809
+ // Obsolete member 'memberA' overrides non-obsolete member 'memberB'.
+ // Disallowing GetHashCode and Equals is by design
+
+ // * We decided to not override GetHashCode() to produce the hash code
+ // as this would be weird, both naming-wise as well as from a
+ // behavioral standpoint (GetHashCode() should return the object's
+ // hash code, not the one being computed).
+
+ // * Even though ToHashCode() can be called safely multiple times on
+ // this implementation, it is not part of the contract. If the
+ // implementation has to change in the future we don't want to worry
+ // about people who might have incorrectly used this type.
+
+ [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => throw new NotSupportedException(SR.HashCode_HashCodeNotSupported);
+
+ [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj) => throw new NotSupportedException(SR.HashCode_EqualityNotSupported);
+#pragma warning restore 0809
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IAsyncResult.cs b/src/System.Private.CoreLib/shared/System/IAsyncResult.cs
new file mode 100644
index 0000000000..0abeaca525
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IAsyncResult.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Interface: IAsyncResult
+**
+** Purpose: Interface to encapsulate the results of an async
+** operation
+**
+===========================================================*/
+
+using System;
+using System.Threading;
+
+namespace System
+{
+ public interface IAsyncResult
+ {
+ bool IsCompleted { get; }
+
+ WaitHandle AsyncWaitHandle { get; }
+
+
+ Object AsyncState { get; }
+
+ bool CompletedSynchronously { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ICloneable.cs b/src/System.Private.CoreLib/shared/System/ICloneable.cs
new file mode 100644
index 0000000000..9f123e45c8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ICloneable.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public interface ICloneable
+ {
+ object Clone();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IComparable.cs b/src/System.Private.CoreLib/shared/System/IComparable.cs
new file mode 100644
index 0000000000..72aeeb027c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IComparable.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ // The IComparable interface is implemented by classes that support an
+ // ordering of instances of the class. The ordering represented by
+ // IComparable can be used to sort arrays and collections of objects
+ // that implement the interface.
+ //
+ public interface IComparable
+ {
+ // Interface does not need to be marked with the serializable attribute
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship. An implementation of this method must return
+ // a value less than zero if this is less than object, zero
+ // if this is equal to object, or a value greater than zero
+ // if this is greater than object.
+ //
+ int CompareTo(Object obj);
+ }
+
+ // Generic version of IComparable.
+
+ public interface IComparable<in T>
+ {
+ // Interface does not need to be marked with the serializable attribute
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship. An implementation of this method must return
+ // a value less than zero if this is less than object, zero
+ // if this is equal to object, or a value greater than zero
+ // if this is greater than object.
+ //
+ int CompareTo(T other);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IConvertible.cs b/src/System.Private.CoreLib/shared/System/IConvertible.cs
new file mode 100644
index 0000000000..87351127f2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IConvertible.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ // The IConvertible interface represents an object that contains a value. This
+ // interface is implemented by the following types in the System namespace:
+ // Boolean, Char, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64,
+ // Single, Double, Decimal, DateTime, and String. The interface may
+ // be implemented by other types that are to be considered values. For example,
+ // a library of nullable database types could implement IConvertible.
+ //
+ // Note: The interface was originally proposed as IValue.
+ //
+ // The implementations of IConvertible provided by the System.XXX value classes
+ // simply forward to the appropriate Value.ToXXX(YYY) methods (a description of
+ // the Value class follows below). In cases where a Value.ToXXX(YYY) method
+ // does not exist (because the particular conversion is not supported), the
+ // IConvertible implementation should simply throw an InvalidCastException.
+
+ [CLSCompliant(false)]
+ public interface IConvertible
+ {
+ // Returns the type code of this object. An implementation of this method
+ // must not return TypeCode.Empty (which represents a null reference) or
+ // TypeCode.Object (which represents an object that doesn't implement the
+ // IConvertible interface). An implementation of this method should return
+ // TypeCode.DBNull if the value of this object is a database null. For
+ // example, a nullable integer type should return TypeCode.DBNull if the
+ // value of the object is the database null. Otherwise, an implementation
+ // of this method should return the TypeCode that best describes the
+ // internal representation of the object.
+
+ TypeCode GetTypeCode();
+
+ // The ToXXX methods convert the value of the underlying object to the
+ // given type. If a particular conversion is not supported, the
+ // implementation must throw an InvalidCastException. If the value of the
+ // underlying object is not within the range of the target type, the
+ // implementation must throw an OverflowException. The
+ // IFormatProvider will be used to get a NumberFormatInfo or similar
+ // appropriate service object, and may safely be null.
+
+ bool ToBoolean(IFormatProvider provider);
+ char ToChar(IFormatProvider provider);
+ sbyte ToSByte(IFormatProvider provider);
+ byte ToByte(IFormatProvider provider);
+ short ToInt16(IFormatProvider provider);
+ ushort ToUInt16(IFormatProvider provider);
+ int ToInt32(IFormatProvider provider);
+ uint ToUInt32(IFormatProvider provider);
+ long ToInt64(IFormatProvider provider);
+ ulong ToUInt64(IFormatProvider provider);
+ float ToSingle(IFormatProvider provider);
+ double ToDouble(IFormatProvider provider);
+ Decimal ToDecimal(IFormatProvider provider);
+ DateTime ToDateTime(IFormatProvider provider);
+ String ToString(IFormatProvider provider);
+ Object ToType(Type conversionType, IFormatProvider provider);
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/ICustomFormatter.cs b/src/System.Private.CoreLib/shared/System/ICustomFormatter.cs
new file mode 100644
index 0000000000..47340f3093
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ICustomFormatter.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Interface: ICustomFormatter
+**
+**
+** Purpose: Marks a class as providing special formatting
+**
+**
+===========================================================*/
+
+using System;
+
+namespace System
+{
+ public interface ICustomFormatter
+ {
+ // Interface does not need to be marked with the serializable attribute
+ String Format(String format, Object arg, IFormatProvider formatProvider);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IDisposable.cs b/src/System.Private.CoreLib/shared/System/IDisposable.cs
new file mode 100644
index 0000000000..24f0740edc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IDisposable.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Interface: IDisposable
+**
+**
+** Purpose: Interface for assisting with deterministic finalization.
+**
+**
+===========================================================*/
+
+namespace System
+{
+ // IDisposable is an attempt at helping to solve problems with deterministic
+ // finalization. The GC of course doesn't leave any way to deterministically
+ // know when a finalizer will run. This forces classes that hold onto OS
+ // resources or some sort of important state (such as a FileStream or a
+ // network connection) to provide a Close or Dispose method so users can
+ // run clean up code deterministically. We have formalized this into an
+ // interface with one method. Classes may privately implement IDisposable and
+ // provide a Close method instead, if that name is by far the expected name
+ // for objects in that domain (ie, you don't Dispose of a FileStream, you Close
+ // it).
+ //
+ // This interface could be theoretically used as a marker by a compiler to
+ // ensure a disposable object has been cleaned up along all code paths if it's
+ // been allocated in that method, though in practice any compiler that
+ // draconian may tick off any number of people. Perhaps an external tool (like
+ // like Purify or BoundsChecker) could do this. Instead, C# has added a using
+ // clause, which will generate a try/finally statement where the resource
+ // passed into the using clause will always have it's Dispose method called.
+ // Syntax is using(FileStream fs = ...) { .. };
+ //
+ // Dispose should meet the following conditions:
+ // 1) Be safely callable multiple times
+ // 2) Release any resources associated with the instance
+ // 3) Call the base class's Dispose method, if necessary
+ // 4) Suppress finalization of this class to help the GC by reducing the
+ // number of objects on the finalization queue.
+ // 5) Dispose shouldn't generally throw exceptions, except for very serious
+ // errors that are particularly unexpected. (ie, OutOfMemoryException)
+ // Ideally, nothing should go wrong with your object by calling Dispose.
+ //
+ // If possible, a class should define a finalizer that calls Dispose.
+ // However, in many situations, this is impractical. For instance, take the
+ // classic example of a Stream and a StreamWriter (which has an internal
+ // buffer of data to write to the Stream). If both objects are collected
+ // before Close or Dispose has been called on either, then the GC may run the
+ // finalizer for the Stream first, before the StreamWriter. At that point, any
+ // data buffered by the StreamWriter cannot be written to the Stream. In this
+ // case, it doesn't make much sense to provide a finalizer on the StreamWriter
+ // since you cannot solve this problem correctly.
+ public interface IDisposable
+ {
+ void Dispose();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IEquatable.cs b/src/System.Private.CoreLib/shared/System/IEquatable.cs
new file mode 100644
index 0000000000..1264fdd57a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IEquatable.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System
+{
+ public interface IEquatable<T>
+ {
+ bool Equals(T other);
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/IFormatProvider.cs b/src/System.Private.CoreLib/shared/System/IFormatProvider.cs
new file mode 100644
index 0000000000..0c17354af3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IFormatProvider.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Notes a class which knows how to return formatting information
+**
+**
+============================================================*/
+
+using System;
+
+namespace System
+{
+ public interface IFormatProvider
+ {
+ // Interface does not need to be marked with the serializable attribute
+ Object GetFormat(Type formatType);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IFormattable.cs b/src/System.Private.CoreLib/shared/System/IFormattable.cs
new file mode 100644
index 0000000000..1f2f7022cc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IFormattable.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System
+{
+ public interface IFormattable
+ {
+ String ToString(String format, IFormatProvider formatProvider);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/BinaryWriter.cs b/src/System.Private.CoreLib/shared/System/IO/BinaryWriter.cs
new file mode 100644
index 0000000000..ad1d31f577
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/BinaryWriter.cs
@@ -0,0 +1,441 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System.Diagnostics;
+using System.Buffers;
+
+namespace System.IO
+{
+ // This abstract base class represents a writer that can write
+ // primitives to an arbitrary stream. A subclass can override methods to
+ // give unique encodings.
+ //
+ public class BinaryWriter : IDisposable
+ {
+ public static readonly BinaryWriter Null = new BinaryWriter();
+
+ protected Stream OutStream;
+ private byte[] _buffer; // temp space for writing primitives to.
+ private Encoding _encoding;
+ private Encoder _encoder;
+
+ private bool _leaveOpen;
+
+ // Perf optimization stuff
+ private byte[] _largeByteBuffer; // temp space for writing chars.
+ private int _maxChars; // max # of chars we can put in _largeByteBuffer
+ // Size should be around the max number of chars/string * Encoding's max bytes/char
+ private const int LargeByteBufferSize = 256;
+
+ // Protected default constructor that sets the output stream
+ // to a null stream (a bit bucket).
+ protected BinaryWriter()
+ {
+ OutStream = Stream.Null;
+ _buffer = new byte[16];
+ _encoding = EncodingCache.UTF8NoBOM;
+ _encoder = _encoding.GetEncoder();
+ }
+
+ public BinaryWriter(Stream output) : this(output, EncodingCache.UTF8NoBOM, false)
+ {
+ }
+
+ public BinaryWriter(Stream output, Encoding encoding) : this(output, encoding, false)
+ {
+ }
+
+ public BinaryWriter(Stream output, Encoding encoding, bool leaveOpen)
+ {
+ if (output == null)
+ throw new ArgumentNullException(nameof(output));
+ if (encoding == null)
+ throw new ArgumentNullException(nameof(encoding));
+ if (!output.CanWrite)
+ throw new ArgumentException(SR.Argument_StreamNotWritable);
+
+ OutStream = output;
+ _buffer = new byte[16];
+ _encoding = encoding;
+ _encoder = _encoding.GetEncoder();
+ _leaveOpen = leaveOpen;
+ }
+
+ // Closes this writer and releases any system resources associated with the
+ // writer. Following a call to Close, any operations on the writer
+ // may raise exceptions.
+ public virtual void Close()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_leaveOpen)
+ OutStream.Flush();
+ else
+ OutStream.Close();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ // Returns the stream associated with the writer. It flushes all pending
+ // writes before returning. All subclasses should override Flush to
+ // ensure that all buffered data is sent to the stream.
+ public virtual Stream BaseStream
+ {
+ get
+ {
+ Flush();
+ return OutStream;
+ }
+ }
+
+ // Clears all buffers for this writer and causes any buffered data to be
+ // written to the underlying device.
+ public virtual void Flush()
+ {
+ OutStream.Flush();
+ }
+
+ public virtual long Seek(int offset, SeekOrigin origin)
+ {
+ return OutStream.Seek(offset, origin);
+ }
+
+ // Writes a boolean to this stream. A single byte is written to the stream
+ // with the value 0 representing false or the value 1 representing true.
+ //
+ public virtual void Write(bool value)
+ {
+ _buffer[0] = (byte)(value ? 1 : 0);
+ OutStream.Write(_buffer, 0, 1);
+ }
+
+ // Writes a byte to this stream. The current position of the stream is
+ // advanced by one.
+ //
+ public virtual void Write(byte value)
+ {
+ OutStream.WriteByte(value);
+ }
+
+ // Writes a signed byte to this stream. The current position of the stream
+ // is advanced by one.
+ //
+ [CLSCompliant(false)]
+ public virtual void Write(sbyte value)
+ {
+ OutStream.WriteByte((byte)value);
+ }
+
+ // Writes a byte array to this stream.
+ //
+ // This default implementation calls the Write(Object, int, int)
+ // method to write the byte array.
+ //
+ public virtual void Write(byte[] buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer));
+ OutStream.Write(buffer, 0, buffer.Length);
+ }
+
+ // Writes a section of a byte array to this stream.
+ //
+ // This default implementation calls the Write(Object, int, int)
+ // method to write the byte array.
+ //
+ public virtual void Write(byte[] buffer, int index, int count)
+ {
+ OutStream.Write(buffer, index, count);
+ }
+
+
+ // Writes a character to this stream. The current position of the stream is
+ // advanced by two.
+ // Note this method cannot handle surrogates properly in UTF-8.
+ //
+ public unsafe virtual void Write(char ch)
+ {
+ if (Char.IsSurrogate(ch))
+ throw new ArgumentException(SR.Arg_SurrogatesNotAllowedAsSingleChar);
+
+ Debug.Assert(_encoding.GetMaxByteCount(1) <= 16, "_encoding.GetMaxByteCount(1) <= 16)");
+ int numBytes = 0;
+ fixed (byte* pBytes = &_buffer[0])
+ {
+ numBytes = _encoder.GetBytes(&ch, 1, pBytes, _buffer.Length, flush: true);
+ }
+ OutStream.Write(_buffer, 0, numBytes);
+ }
+
+ // Writes a character array to this stream.
+ //
+ // This default implementation calls the Write(Object, int, int)
+ // method to write the character array.
+ //
+ public virtual void Write(char[] chars)
+ {
+ if (chars == null)
+ throw new ArgumentNullException(nameof(chars));
+
+ byte[] bytes = _encoding.GetBytes(chars, 0, chars.Length);
+ OutStream.Write(bytes, 0, bytes.Length);
+ }
+
+ // Writes a section of a character array to this stream.
+ //
+ // This default implementation calls the Write(Object, int, int)
+ // method to write the character array.
+ //
+ public virtual void Write(char[] chars, int index, int count)
+ {
+ byte[] bytes = _encoding.GetBytes(chars, index, count);
+ OutStream.Write(bytes, 0, bytes.Length);
+ }
+
+
+ // Writes a double to this stream. The current position of the stream is
+ // advanced by eight.
+ //
+ public unsafe virtual void Write(double value)
+ {
+ ulong TmpValue = *(ulong*)&value;
+ _buffer[0] = (byte)TmpValue;
+ _buffer[1] = (byte)(TmpValue >> 8);
+ _buffer[2] = (byte)(TmpValue >> 16);
+ _buffer[3] = (byte)(TmpValue >> 24);
+ _buffer[4] = (byte)(TmpValue >> 32);
+ _buffer[5] = (byte)(TmpValue >> 40);
+ _buffer[6] = (byte)(TmpValue >> 48);
+ _buffer[7] = (byte)(TmpValue >> 56);
+ OutStream.Write(_buffer, 0, 8);
+ }
+
+ public virtual void Write(decimal value)
+ {
+ Decimal.GetBytes(value, _buffer);
+ OutStream.Write(_buffer, 0, 16);
+ }
+
+ // Writes a two-byte signed integer to this stream. The current position of
+ // the stream is advanced by two.
+ //
+ public virtual void Write(short value)
+ {
+ _buffer[0] = (byte)value;
+ _buffer[1] = (byte)(value >> 8);
+ OutStream.Write(_buffer, 0, 2);
+ }
+
+ // Writes a two-byte unsigned integer to this stream. The current position
+ // of the stream is advanced by two.
+ //
+ [CLSCompliant(false)]
+ public virtual void Write(ushort value)
+ {
+ _buffer[0] = (byte)value;
+ _buffer[1] = (byte)(value >> 8);
+ OutStream.Write(_buffer, 0, 2);
+ }
+
+ // Writes a four-byte signed integer to this stream. The current position
+ // of the stream is advanced by four.
+ //
+ public virtual void Write(int value)
+ {
+ _buffer[0] = (byte)value;
+ _buffer[1] = (byte)(value >> 8);
+ _buffer[2] = (byte)(value >> 16);
+ _buffer[3] = (byte)(value >> 24);
+ OutStream.Write(_buffer, 0, 4);
+ }
+
+ // Writes a four-byte unsigned integer to this stream. The current position
+ // of the stream is advanced by four.
+ //
+ [CLSCompliant(false)]
+ public virtual void Write(uint value)
+ {
+ _buffer[0] = (byte)value;
+ _buffer[1] = (byte)(value >> 8);
+ _buffer[2] = (byte)(value >> 16);
+ _buffer[3] = (byte)(value >> 24);
+ OutStream.Write(_buffer, 0, 4);
+ }
+
+ // Writes an eight-byte signed integer to this stream. The current position
+ // of the stream is advanced by eight.
+ //
+ public virtual void Write(long value)
+ {
+ _buffer[0] = (byte)value;
+ _buffer[1] = (byte)(value >> 8);
+ _buffer[2] = (byte)(value >> 16);
+ _buffer[3] = (byte)(value >> 24);
+ _buffer[4] = (byte)(value >> 32);
+ _buffer[5] = (byte)(value >> 40);
+ _buffer[6] = (byte)(value >> 48);
+ _buffer[7] = (byte)(value >> 56);
+ OutStream.Write(_buffer, 0, 8);
+ }
+
+ // Writes an eight-byte unsigned integer to this stream. The current
+ // position of the stream is advanced by eight.
+ //
+ [CLSCompliant(false)]
+ public virtual void Write(ulong value)
+ {
+ _buffer[0] = (byte)value;
+ _buffer[1] = (byte)(value >> 8);
+ _buffer[2] = (byte)(value >> 16);
+ _buffer[3] = (byte)(value >> 24);
+ _buffer[4] = (byte)(value >> 32);
+ _buffer[5] = (byte)(value >> 40);
+ _buffer[6] = (byte)(value >> 48);
+ _buffer[7] = (byte)(value >> 56);
+ OutStream.Write(_buffer, 0, 8);
+ }
+
+ // Writes a float to this stream. The current position of the stream is
+ // advanced by four.
+ //
+ public unsafe virtual void Write(float value)
+ {
+ uint TmpValue = *(uint*)&value;
+ _buffer[0] = (byte)TmpValue;
+ _buffer[1] = (byte)(TmpValue >> 8);
+ _buffer[2] = (byte)(TmpValue >> 16);
+ _buffer[3] = (byte)(TmpValue >> 24);
+ OutStream.Write(_buffer, 0, 4);
+ }
+
+
+ // Writes a length-prefixed string to this stream in the BinaryWriter's
+ // current Encoding. This method first writes the length of the string as
+ // a four-byte unsigned integer, and then writes that many characters
+ // to the stream.
+ //
+ public unsafe virtual void Write(String value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ int len = _encoding.GetByteCount(value);
+ Write7BitEncodedInt(len);
+
+ if (_largeByteBuffer == null)
+ {
+ _largeByteBuffer = new byte[LargeByteBufferSize];
+ _maxChars = _largeByteBuffer.Length / _encoding.GetMaxByteCount(1);
+ }
+
+ if (len <= _largeByteBuffer.Length)
+ {
+ //Debug.Assert(len == _encoding.GetBytes(chars, 0, chars.Length, _largeByteBuffer, 0), "encoding's GetByteCount & GetBytes gave different answers! encoding type: "+_encoding.GetType().Name);
+ _encoding.GetBytes(value, 0, value.Length, _largeByteBuffer, 0);
+ OutStream.Write(_largeByteBuffer, 0, len);
+ }
+ else
+ {
+ // Aggressively try to not allocate memory in this loop for
+ // runtime performance reasons. Use an Encoder to write out
+ // the string correctly (handling surrogates crossing buffer
+ // boundaries properly).
+ int charStart = 0;
+ int numLeft = value.Length;
+#if DEBUG
+ int totalBytes = 0;
+#endif
+ while (numLeft > 0)
+ {
+ // Figure out how many chars to process this round.
+ int charCount = (numLeft > _maxChars) ? _maxChars : numLeft;
+ int byteLen;
+
+ checked
+ {
+ if (charStart < 0 || charCount < 0 || charStart > value.Length - charCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(charCount));
+ }
+ fixed (char* pChars = value)
+ {
+ fixed (byte* pBytes = &_largeByteBuffer[0])
+ {
+ byteLen = _encoder.GetBytes(pChars + charStart, charCount, pBytes, _largeByteBuffer.Length, charCount == numLeft);
+ }
+ }
+ }
+#if DEBUG
+ totalBytes += byteLen;
+ Debug.Assert(totalBytes <= len && byteLen <= _largeByteBuffer.Length, "BinaryWriter::Write(String) - More bytes encoded than expected!");
+#endif
+ OutStream.Write(_largeByteBuffer, 0, byteLen);
+ charStart += charCount;
+ numLeft -= charCount;
+ }
+#if DEBUG
+ Debug.Assert(totalBytes == len, "BinaryWriter::Write(String) - Didn't write out all the bytes!");
+#endif
+ }
+ }
+
+ public virtual void Write(ReadOnlySpan<byte> buffer)
+ {
+ if (GetType() == typeof(BinaryWriter))
+ {
+ OutStream.Write(buffer);
+ }
+ else
+ {
+ byte[] array = ArrayPool<byte>.Shared.Rent(buffer.Length);
+ try
+ {
+ buffer.CopyTo(array);
+ Write(array, 0, buffer.Length);
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(array);
+ }
+ }
+ }
+
+ public virtual void Write(ReadOnlySpan<char> chars)
+ {
+ byte[] bytes = ArrayPool<byte>.Shared.Rent(_encoding.GetMaxByteCount(chars.Length));
+ try
+ {
+ int bytesWritten = _encoding.GetBytes(chars, bytes);
+ Write(bytes, 0, bytesWritten);
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(bytes);
+ }
+ }
+
+ protected void Write7BitEncodedInt(int value)
+ {
+ // Write out an int 7 bits at a time. The high bit of the byte,
+ // when on, tells reader to continue reading more bytes.
+ uint v = (uint)value; // support negative numbers
+ while (v >= 0x80)
+ {
+ Write((byte)(v | 0x80));
+ v >>= 7;
+ }
+ Write((byte)v);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/DirectoryNotFoundException.cs b/src/System.Private.CoreLib/shared/System/IO/DirectoryNotFoundException.cs
new file mode 100644
index 0000000000..d7c6eacb9a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/DirectoryNotFoundException.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.IO
+{
+ /*
+ * Thrown when trying to access a directory that doesn't exist on disk.
+ * From COM Interop, this exception is thrown for 2 HRESULTS:
+ * the Win32 errorcode-as-HRESULT ERROR_PATH_NOT_FOUND (0x80070003)
+ * and STG_E_PATHNOTFOUND (0x80030003).
+ */
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class DirectoryNotFoundException : IOException
+ {
+ public DirectoryNotFoundException()
+ : base(SR.Arg_DirectoryNotFoundException)
+ {
+ HResult = HResults.COR_E_DIRECTORYNOTFOUND;
+ }
+
+ public DirectoryNotFoundException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_DIRECTORYNOTFOUND;
+ }
+
+ public DirectoryNotFoundException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_DIRECTORYNOTFOUND;
+ }
+
+ protected DirectoryNotFoundException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/DisableMediaInsertionPrompt.cs b/src/System.Private.CoreLib/shared/System/IO/DisableMediaInsertionPrompt.cs
new file mode 100644
index 0000000000..aa10e8d883
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/DisableMediaInsertionPrompt.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO
+{
+ /// <summary>
+ /// Simple wrapper to safely disable the normal media insertion prompt for
+ /// removable media (floppies, cds, memory cards, etc.)
+ /// </summary>
+ /// <remarks>
+ /// Note that removable media file systems lazily load. After starting the OS
+ /// they won't be loaded until you have media in the drive- and as such the
+ /// prompt won't happen. You have to have had media in at least once to get
+ /// the file system to load and then have removed it.
+ /// </remarks>
+ internal struct DisableMediaInsertionPrompt : IDisposable
+ {
+ private bool _disableSuccess;
+ private uint _oldMode;
+
+ public static DisableMediaInsertionPrompt Create()
+ {
+ DisableMediaInsertionPrompt prompt = new DisableMediaInsertionPrompt();
+ prompt._disableSuccess = Interop.Kernel32.SetThreadErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS, out prompt._oldMode);
+ return prompt;
+ }
+
+ public void Dispose()
+ {
+ uint ignore;
+ if (_disableSuccess)
+ Interop.Kernel32.SetThreadErrorMode(_oldMode, out ignore);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/EncodingCache.cs b/src/System.Private.CoreLib/shared/System/IO/EncodingCache.cs
new file mode 100644
index 0000000000..53379bc77f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/EncodingCache.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+
+namespace System.IO
+{
+ internal static class EncodingCache
+ {
+ internal static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/EndOfStreamException.cs b/src/System.Private.CoreLib/shared/System/IO/EndOfStreamException.cs
new file mode 100644
index 0000000000..606073ad9a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/EndOfStreamException.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.IO
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class EndOfStreamException : IOException
+ {
+ public EndOfStreamException()
+ : base(SR.Arg_EndOfStreamException)
+ {
+ HResult = HResults.COR_E_ENDOFSTREAM;
+ }
+
+ public EndOfStreamException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ENDOFSTREAM;
+ }
+
+ public EndOfStreamException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_ENDOFSTREAM;
+ }
+
+ protected EndOfStreamException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/Error.cs b/src/System.Private.CoreLib/shared/System/IO/Error.cs
new file mode 100644
index 0000000000..1e319a0680
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/Error.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Globalization;
+
+namespace System.IO
+{
+ /// <summary>
+ /// Provides centralized methods for creating exceptions for System.IO.FileSystem.
+ /// </summary>
+ internal static class Error
+ {
+ internal static Exception GetStreamIsClosed()
+ {
+ return new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed);
+ }
+
+ internal static Exception GetEndOfFile()
+ {
+ return new EndOfStreamException(SR.IO_EOF_ReadBeyondEOF);
+ }
+
+ internal static Exception GetFileNotOpen()
+ {
+ return new ObjectDisposedException(null, SR.ObjectDisposed_FileClosed);
+ }
+
+ internal static Exception GetReadNotSupported()
+ {
+ return new NotSupportedException(SR.NotSupported_UnreadableStream);
+ }
+
+ internal static Exception GetSeekNotSupported()
+ {
+ return new NotSupportedException(SR.NotSupported_UnseekableStream);
+ }
+
+ internal static Exception GetWriteNotSupported()
+ {
+ return new NotSupportedException(SR.NotSupported_UnwritableStream);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileAccess.cs b/src/System.Private.CoreLib/shared/System/IO/FileAccess.cs
new file mode 100644
index 0000000000..1b70bae172
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileAccess.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.IO
+{
+ // Contains constants for specifying the access you want for a file.
+ // You can have Read, Write or ReadWrite access.
+ //
+ [Flags]
+ public enum FileAccess
+ {
+ // Specifies read access to the file. Data can be read from the file and
+ // the file pointer can be moved. Combine with WRITE for read-write access.
+ Read = 1,
+
+ // Specifies write access to the file. Data can be written to the file and
+ // the file pointer can be moved. Combine with READ for read-write access.
+ Write = 2,
+
+ // Specifies read and write access to the file. Data can be written to the
+ // file and the file pointer can be moved. Data can also be read from the
+ // file.
+ ReadWrite = 3,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileLoadException.cs b/src/System.Private.CoreLib/shared/System/IO/FileLoadException.cs
new file mode 100644
index 0000000000..8c31244e1d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileLoadException.cs
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.IO
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public partial class FileLoadException : IOException
+ {
+ public FileLoadException()
+ : base(SR.IO_FileLoad)
+ {
+ HResult = HResults.COR_E_FILELOAD;
+ }
+
+ public FileLoadException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_FILELOAD;
+ }
+
+ public FileLoadException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_FILELOAD;
+ }
+
+ public FileLoadException(string message, string fileName) : base(message)
+ {
+ HResult = HResults.COR_E_FILELOAD;
+ FileName = fileName;
+ }
+
+ public FileLoadException(string message, string fileName, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_FILELOAD;
+ FileName = fileName;
+ }
+
+ public override string Message
+ {
+ get
+ {
+ if (_message == null)
+ {
+ _message = FormatFileLoadExceptionMessage(FileName, HResult);
+ }
+ return _message;
+ }
+ }
+
+ public string FileName { get; }
+ public string FusionLog { get; }
+
+ public override string ToString()
+ {
+ string s = GetType().ToString() + ": " + Message;
+
+ if (FileName != null && FileName.Length != 0)
+ s += Environment.NewLine + SR.Format(SR.IO_FileName_Name, FileName);
+
+ if (InnerException != null)
+ s = s + " ---> " + InnerException.ToString();
+
+ if (StackTrace != null)
+ s += Environment.NewLine + StackTrace;
+
+ if (FusionLog != null)
+ {
+ if (s == null)
+ s = " ";
+ s += Environment.NewLine;
+ s += Environment.NewLine;
+ s += FusionLog;
+ }
+
+ return s;
+ }
+
+ protected FileLoadException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ FileName = info.GetString("FileLoad_FileName");
+ FusionLog = info.GetString("FileLoad_FusionLog");
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("FileLoad_FileName", FileName, typeof(string));
+ info.AddValue("FileLoad_FusionLog", FusionLog, typeof(string));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileMode.cs b/src/System.Private.CoreLib/shared/System/IO/FileMode.cs
new file mode 100644
index 0000000000..77f2fe6f20
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileMode.cs
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO
+{
+ // Contains constants for specifying how the OS should open a file.
+ // These will control whether you overwrite a file, open an existing
+ // file, or some combination thereof.
+ //
+ // To append to a file, use Append (which maps to OpenOrCreate then we seek
+ // to the end of the file). To truncate a file or create it if it doesn't
+ // exist, use Create.
+ //
+ public enum FileMode
+ {
+ // Creates a new file. An exception is raised if the file already exists.
+ CreateNew = 1,
+
+ // Creates a new file. If the file already exists, it is overwritten.
+ Create = 2,
+
+ // Opens an existing file. An exception is raised if the file does not exist.
+ Open = 3,
+
+ // Opens the file if it exists. Otherwise, creates a new file.
+ OpenOrCreate = 4,
+
+ // Opens an existing file. Once opened, the file is truncated so that its
+ // size is zero bytes. The calling process must open the file with at least
+ // WRITE access. An exception is raised if the file does not exist.
+ Truncate = 5,
+
+ // Opens the file if it exists and seeks to the end. Otherwise,
+ // creates a new file.
+ Append = 6,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileNotFoundException.cs b/src/System.Private.CoreLib/shared/System/IO/FileNotFoundException.cs
new file mode 100644
index 0000000000..72cff4cfc0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileNotFoundException.cs
@@ -0,0 +1,110 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.IO
+{
+ // Thrown when trying to access a file that doesn't exist on disk.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public partial class FileNotFoundException : IOException
+ {
+ public FileNotFoundException()
+ : base(SR.IO_FileNotFound)
+ {
+ HResult = HResults.COR_E_FILENOTFOUND;
+ }
+
+ public FileNotFoundException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_FILENOTFOUND;
+ }
+
+ public FileNotFoundException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_FILENOTFOUND;
+ }
+
+ public FileNotFoundException(string message, string fileName)
+ : base(message)
+ {
+ HResult = HResults.COR_E_FILENOTFOUND;
+ FileName = fileName;
+ }
+
+ public FileNotFoundException(string message, string fileName, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_FILENOTFOUND;
+ FileName = fileName;
+ }
+
+ public override string Message
+ {
+ get
+ {
+ SetMessageField();
+ return _message;
+ }
+ }
+
+ private void SetMessageField()
+ {
+ if (_message == null)
+ {
+ if ((FileName == null) &&
+ (HResult == System.HResults.COR_E_EXCEPTION))
+ _message = SR.IO_FileNotFound;
+
+ else if (FileName != null)
+ _message = FileLoadException.FormatFileLoadExceptionMessage(FileName, HResult);
+ }
+ }
+
+ public string FileName { get; }
+ public string FusionLog { get; }
+
+ public override string ToString()
+ {
+ string s = GetType().ToString() + ": " + Message;
+
+ if (FileName != null && FileName.Length != 0)
+ s += Environment.NewLine + SR.Format(SR.IO_FileName_Name, FileName);
+
+ if (InnerException != null)
+ s = s + " ---> " + InnerException.ToString();
+
+ if (StackTrace != null)
+ s += Environment.NewLine + StackTrace;
+
+ if (FusionLog != null)
+ {
+ if (s == null)
+ s = " ";
+ s += Environment.NewLine;
+ s += Environment.NewLine;
+ s += FusionLog;
+ }
+ return s;
+ }
+
+ protected FileNotFoundException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ FileName = info.GetString("FileNotFound_FileName");
+ FusionLog = info.GetString("FileNotFound_FusionLog");
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("FileNotFound_FileName", FileName, typeof(string));
+ info.AddValue("FileNotFound_FusionLog", FusionLog, typeof(string));
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileOptions.cs b/src/System.Private.CoreLib/shared/System/IO/FileOptions.cs
new file mode 100644
index 0000000000..ae8396a588
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileOptions.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace System.IO
+{
+ // Maps to FILE_FLAG_DELETE_ON_CLOSE and similar values from winbase.h.
+ // We didn't expose a number of these values because we didn't believe
+ // a number of them made sense in managed code, at least not yet.
+ [Flags]
+ public enum FileOptions
+ {
+ // NOTE: any change to FileOptions enum needs to be
+ // matched in the FileStream ctor for error validation
+ None = 0,
+ WriteThrough = unchecked((int)0x80000000),
+ Asynchronous = unchecked((int)0x40000000), // FILE_FLAG_OVERLAPPED
+ // NoBuffering = 0x20000000,
+ RandomAccess = 0x10000000,
+ DeleteOnClose = 0x04000000,
+ SequentialScan = 0x08000000,
+ // AllowPosix = 0x01000000, // FILE_FLAG_POSIX_SEMANTICS
+ // BackupOrRestore,
+ // DisallowReparsePoint = 0x00200000, // FILE_FLAG_OPEN_REPARSE_POINT
+ // NoRemoteRecall = 0x00100000, // FILE_FLAG_OPEN_NO_RECALL
+ // FirstPipeInstance = 0x00080000, // FILE_FLAG_FIRST_PIPE_INSTANCE
+ Encrypted = 0x00004000, // FILE_ATTRIBUTE_ENCRYPTED
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileShare.cs b/src/System.Private.CoreLib/shared/System/IO/FileShare.cs
new file mode 100644
index 0000000000..e9b9b5e32f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileShare.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.IO
+{
+ // Contains constants for controlling file sharing options while
+ // opening files. You can specify what access other processes trying
+ // to open the same file concurrently can have.
+ //
+ // Note these values currently match the values for FILE_SHARE_READ,
+ // FILE_SHARE_WRITE, and FILE_SHARE_DELETE in winnt.h
+ //
+ [Flags]
+ public enum FileShare
+ {
+ // No sharing. Any request to open the file (by this process or another
+ // process) will fail until the file is closed.
+ None = 0,
+
+ // Allows subsequent opening of the file for reading. If this flag is not
+ // specified, any request to open the file for reading (by this process or
+ // another process) will fail until the file is closed.
+ Read = 1,
+
+ // Allows subsequent opening of the file for writing. If this flag is not
+ // specified, any request to open the file for writing (by this process or
+ // another process) will fail until the file is closed.
+ Write = 2,
+
+ // Allows subsequent opening of the file for writing or reading. If this flag
+ // is not specified, any request to open the file for writing or reading (by
+ // this process or another process) will fail until the file is closed.
+ ReadWrite = 3,
+
+ // Open the file, but allow someone else to delete the file.
+ Delete = 4,
+
+ // Whether the file handle should be inheritable by child processes.
+ // Note this is not directly supported like this by Win32.
+ Inheritable = 0x10,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.Linux.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.Linux.cs
new file mode 100644
index 0000000000..873c4eb559
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.Linux.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ public partial class FileStream : Stream
+ {
+ /// <summary>Prevents other processes from reading from or writing to the FileStream.</summary>
+ /// <param name="position">The beginning of the range to lock.</param>
+ /// <param name="length">The range to be locked.</param>
+ private void LockInternal(long position, long length)
+ {
+ CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_WRLCK));
+ }
+
+ /// <summary>Allows access by other processes to all or part of a file that was previously locked.</summary>
+ /// <param name="position">The beginning of the range to unlock.</param>
+ /// <param name="length">The range to be unlocked.</param>
+ private void UnlockInternal(long position, long length)
+ {
+ CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_UNLCK));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.OSX.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.OSX.cs
new file mode 100644
index 0000000000..f29e922337
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.OSX.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO
+{
+ public partial class FileStream : Stream
+ {
+ private void LockInternal(long position, long length)
+ {
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_OSXFileLocking);
+ }
+
+ private void UnlockInternal(long position, long length)
+ {
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_OSXFileLocking);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs
new file mode 100644
index 0000000000..d9fcf65711
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs
@@ -0,0 +1,813 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ /// <summary>Provides an implementation of a file stream for Unix files.</summary>
+ public partial class FileStream : Stream
+ {
+ /// <summary>File mode.</summary>
+ private FileMode _mode;
+
+ /// <summary>Advanced options requested when opening the file.</summary>
+ private FileOptions _options;
+
+ /// <summary>If the file was opened with FileMode.Append, the length of the file when opened; otherwise, -1.</summary>
+ private long _appendStart = -1;
+
+ /// <summary>
+ /// Extra state used by the file stream when _useAsyncIO is true. This includes
+ /// the semaphore used to serialize all operation, the buffer/offset/count provided by the
+ /// caller for ReadAsync/WriteAsync operations, and the last successful task returned
+ /// synchronously from ReadAsync which can be reused if the count matches the next request.
+ /// Only initialized when <see cref="_useAsyncIO"/> is true.
+ /// </summary>
+ private AsyncState _asyncState;
+
+ /// <summary>Lazily-initialized value for whether the file supports seeking.</summary>
+ private bool? _canSeek;
+
+ private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
+ {
+ // FileStream performs most of the general argument validation. We can assume here that the arguments
+ // are all checked and consistent (e.g. non-null-or-empty path; valid enums in mode, access, share, and options; etc.)
+ // Store the arguments
+ _mode = mode;
+ _options = options;
+
+ if (_useAsyncIO)
+ _asyncState = new AsyncState();
+
+ // Translate the arguments into arguments for an open call.
+ Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, _access, share, options);
+
+ // If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and
+ // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out
+ // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the
+ // actual permissions will typically be less than what we select here.
+ const Interop.Sys.Permissions OpenPermissions =
+ Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR |
+ Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
+ Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
+
+ // Open the file and store the safe handle.
+ return SafeFileHandle.Open(_path, openFlags, (int)OpenPermissions);
+ }
+
+ /// <summary>Initializes a stream for reading or writing a Unix file.</summary>
+ /// <param name="mode">How the file should be opened.</param>
+ /// <param name="share">What other access to the file should be allowed. This is currently ignored.</param>
+ private void Init(FileMode mode, FileShare share)
+ {
+ _fileHandle.IsAsync = _useAsyncIO;
+
+ // Lock the file if requested via FileShare. This is only advisory locking. FileShare.None implies an exclusive
+ // lock on the file and all other modes use a shared lock. While this is not as granular as Windows, not mandatory,
+ // and not atomic with file opening, it's better than nothing.
+ Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
+ if (Interop.Sys.FLock(_fileHandle, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)
+ {
+ // The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone
+ // else and we would block trying to access it. Other errors, such as ENOTSUP (locking isn't supported) or
+ // EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value,
+ // given again that this is only advisory / best-effort.
+ Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+ if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
+ {
+ throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
+ }
+ }
+
+ // These provide hints around how the file will be accessed. Specifying both RandomAccess
+ // and Sequential together doesn't make sense as they are two competing options on the same spectrum,
+ // so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).
+ Interop.Sys.FileAdvice fadv =
+ (_options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM :
+ (_options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL :
+ 0;
+ if (fadv != 0)
+ {
+ CheckFileCall(Interop.Sys.PosixFAdvise(_fileHandle, 0, 0, fadv),
+ ignoreNotSupported: true); // just a hint.
+ }
+
+ if (_mode == FileMode.Append)
+ {
+ // Jump to the end of the file if opened as Append.
+ _appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End);
+ }
+ else if (mode == FileMode.Create || mode == FileMode.Truncate)
+ {
+ // Truncate the file now if the file mode requires it. This ensures that the file only will be truncated
+ // if opened successfully.
+ CheckFileCall(Interop.Sys.FTruncate(_fileHandle, 0));
+ }
+ }
+
+ /// <summary>Initializes a stream from an already open file handle (file descriptor).</summary>
+ private void InitFromHandle(SafeFileHandle handle, FileAccess access, bool useAsyncIO)
+ {
+ if (useAsyncIO)
+ _asyncState = new AsyncState();
+
+ if (CanSeekCore(handle)) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor
+ SeekCore(handle, 0, SeekOrigin.Current);
+ }
+
+ /// <summary>Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file.</summary>
+ /// <param name="mode">The FileMode provided to the stream's constructor.</param>
+ /// <param name="access">The FileAccess provided to the stream's constructor</param>
+ /// <param name="share">The FileShare provided to the stream's constructor</param>
+ /// <param name="options">The FileOptions provided to the stream's constructor</param>
+ /// <returns>The flags value to be passed to the open system call.</returns>
+ private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileShare share, FileOptions options)
+ {
+ // Translate FileMode. Most of the values map cleanly to one or more options for open.
+ Interop.Sys.OpenFlags flags = default(Interop.Sys.OpenFlags);
+ switch (mode)
+ {
+ default:
+ case FileMode.Open: // Open maps to the default behavior for open(...). No flags needed.
+ case FileMode.Truncate: // We truncate the file after getting the lock
+ break;
+
+ case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later
+ case FileMode.OpenOrCreate:
+ case FileMode.Create: // We truncate the file after getting the lock
+ flags |= Interop.Sys.OpenFlags.O_CREAT;
+ break;
+
+ case FileMode.CreateNew:
+ flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL);
+ break;
+ }
+
+ // Translate FileAccess. All possible values map cleanly to corresponding values for open.
+ switch (access)
+ {
+ case FileAccess.Read:
+ flags |= Interop.Sys.OpenFlags.O_RDONLY;
+ break;
+
+ case FileAccess.ReadWrite:
+ flags |= Interop.Sys.OpenFlags.O_RDWR;
+ break;
+
+ case FileAccess.Write:
+ flags |= Interop.Sys.OpenFlags.O_WRONLY;
+ break;
+ }
+
+ // Handle Inheritable, other FileShare flags are handled by Init
+ if ((share & FileShare.Inheritable) == 0)
+ {
+ flags |= Interop.Sys.OpenFlags.O_CLOEXEC;
+ }
+
+ // Translate some FileOptions; some just aren't supported, and others will be handled after calling open.
+ // - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true
+ // - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose
+ // - Encrypted: No equivalent on Unix and is ignored
+ // - RandomAccess: Implemented after open if posix_fadvise is available
+ // - SequentialScan: Implemented after open if posix_fadvise is available
+ // - WriteThrough: Handled here
+ if ((options & FileOptions.WriteThrough) != 0)
+ {
+ flags |= Interop.Sys.OpenFlags.O_SYNC;
+ }
+
+ return flags;
+ }
+
+ /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
+ public override bool CanSeek => CanSeekCore(_fileHandle);
+
+ /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
+ /// <remarks>
+ /// Separated out of CanSeek to enable making non-virtual call to this logic.
+ /// We also pass in the file handle to allow the constructor to use this before it stashes the handle.
+ /// </remarks>
+ private bool CanSeekCore(SafeFileHandle fileHandle)
+ {
+ if (fileHandle.IsClosed)
+ {
+ return false;
+ }
+
+ if (!_canSeek.HasValue)
+ {
+ // Lazily-initialize whether we're able to seek, tested by seeking to our current location.
+ _canSeek = Interop.Sys.LSeek(fileHandle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0;
+ }
+
+ return _canSeek.Value;
+ }
+
+ private long GetLengthInternal()
+ {
+ // Get the length of the file as reported by the OS
+ Interop.Sys.FileStatus status;
+ CheckFileCall(Interop.Sys.FStat(_fileHandle, out status));
+ long length = status.Size;
+
+ // But we may have buffered some data to be written that puts our length
+ // beyond what the OS is aware of. Update accordingly.
+ if (_writePos > 0 && _filePosition + _writePos > length)
+ {
+ length = _writePos + _filePosition;
+ }
+
+ return length;
+ }
+
+ /// <summary>Releases the unmanaged resources used by the stream.</summary>
+ /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ if (_fileHandle != null && !_fileHandle.IsClosed)
+ {
+ // Flush any remaining data in the file
+ try
+ {
+ FlushWriteBuffer();
+ }
+ catch (Exception e) when (IsIoRelatedException(e) && !disposing)
+ {
+ // On finalization, ignore failures from trying to flush the write buffer,
+ // e.g. if this stream is wrapping a pipe and the pipe is now broken.
+ }
+
+ // If DeleteOnClose was requested when constructed, delete the file now.
+ // (Unix doesn't directly support DeleteOnClose, so we mimic it here.)
+ if (_path != null && (_options & FileOptions.DeleteOnClose) != 0)
+ {
+ // Since we still have the file open, this will end up deleting
+ // it (assuming we're the only link to it) once it's closed, but the
+ // name will be removed immediately.
+ Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist
+ }
+ }
+ }
+ finally
+ {
+ if (_fileHandle != null && !_fileHandle.IsClosed)
+ {
+ _fileHandle.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+ }
+
+ /// <summary>Flushes the OS buffer. This does not flush the internal read/write buffer.</summary>
+ private void FlushOSBuffer()
+ {
+ if (Interop.Sys.FSync(_fileHandle) < 0)
+ {
+ Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+ switch (errorInfo.Error)
+ {
+ case Interop.Error.EROFS:
+ case Interop.Error.EINVAL:
+ case Interop.Error.ENOTSUP:
+ // Ignore failures due to the FileStream being bound to a special file that
+ // doesn't support synchronization. In such cases there's nothing to flush.
+ break;
+ default:
+ throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
+ }
+ }
+ }
+
+ private void FlushWriteBufferForWriteByte()
+ {
+ _asyncState?.Wait();
+ try { FlushWriteBuffer(); }
+ finally { _asyncState?.Release(); }
+ }
+
+ /// <summary>Writes any data in the write buffer to the underlying stream and resets the buffer.</summary>
+ private void FlushWriteBuffer()
+ {
+ AssertBufferInvariants();
+ if (_writePos > 0)
+ {
+ WriteNative(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
+ _writePos = 0;
+ }
+ }
+
+ /// <summary>Asynchronously clears all buffers for this stream, causing any buffered data to be written to the underlying device.</summary>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ /// <returns>A task that represents the asynchronous flush operation.</returns>
+ private Task FlushAsyncInternal(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ if (_fileHandle.IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+
+ // As with Win32FileStream, flush the buffers synchronously to avoid race conditions.
+ try
+ {
+ FlushInternalBuffer();
+ }
+ catch (Exception e)
+ {
+ return Task.FromException(e);
+ }
+
+ // We then separately flush to disk asynchronously. This is only
+ // necessary if we support writing; otherwise, we're done.
+ if (CanWrite)
+ {
+ return Task.Factory.StartNew(
+ state => ((FileStream)state).FlushOSBuffer(),
+ this,
+ cancellationToken,
+ TaskCreationOptions.DenyChildAttach,
+ TaskScheduler.Default);
+ }
+ else
+ {
+ return Task.CompletedTask;
+ }
+ }
+
+ /// <summary>Sets the length of this stream to the given value.</summary>
+ /// <param name="value">The new length of the stream.</param>
+ private void SetLengthInternal(long value)
+ {
+ FlushInternalBuffer();
+
+ if (_appendStart != -1 && value < _appendStart)
+ {
+ throw new IOException(SR.IO_SetLengthAppendTruncate);
+ }
+
+ long origPos = _filePosition;
+
+ VerifyOSHandlePosition();
+
+ if (_filePosition != value)
+ {
+ SeekCore(_fileHandle, value, SeekOrigin.Begin);
+ }
+
+ CheckFileCall(Interop.Sys.FTruncate(_fileHandle, value));
+
+ // Return file pointer to where it was before setting length
+ if (origPos != value)
+ {
+ if (origPos < value)
+ {
+ SeekCore(_fileHandle, origPos, SeekOrigin.Begin);
+ }
+ else
+ {
+ SeekCore(_fileHandle, 0, SeekOrigin.End);
+ }
+ }
+ }
+
+ /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
+ private int ReadSpan(Span<byte> destination)
+ {
+ PrepareForReading();
+
+ // Are there any bytes available in the read buffer? If yes,
+ // we can just return from the buffer. If the buffer is empty
+ // or has no more available data in it, we can either refill it
+ // (and then read from the buffer into the user's buffer) or
+ // we can just go directly into the user's buffer, if they asked
+ // for more data than we'd otherwise buffer.
+ int numBytesAvailable = _readLength - _readPos;
+ bool readFromOS = false;
+ if (numBytesAvailable == 0)
+ {
+ // If we're not able to seek, then we're not able to rewind the stream (i.e. flushing
+ // a read buffer), in which case we don't want to use a read buffer. Similarly, if
+ // the user has asked for more data than we can buffer, we also want to skip the buffer.
+ if (!CanSeek || (destination.Length >= _bufferLength))
+ {
+ // Read directly into the user's buffer
+ _readPos = _readLength = 0;
+ return ReadNative(destination);
+ }
+ else
+ {
+ // Read into our buffer.
+ _readLength = numBytesAvailable = ReadNative(GetBuffer());
+ _readPos = 0;
+ if (numBytesAvailable == 0)
+ {
+ return 0;
+ }
+
+ // Note that we did an OS read as part of this Read, so that later
+ // we don't try to do one again if what's in the buffer doesn't
+ // meet the user's request.
+ readFromOS = true;
+ }
+ }
+
+ // Now that we know there's data in the buffer, read from it into the user's buffer.
+ Debug.Assert(numBytesAvailable > 0, "Data must be in the buffer to be here");
+ int bytesRead = Math.Min(numBytesAvailable, destination.Length);
+ new Span<byte>(GetBuffer(), _readPos, bytesRead).CopyTo(destination);
+ _readPos += bytesRead;
+
+ // We may not have had enough data in the buffer to completely satisfy the user's request.
+ // While Read doesn't require that we return as much data as the user requested (any amount
+ // up to the requested count is fine), FileStream on Windows tries to do so by doing a
+ // subsequent read from the file if we tried to satisfy the request with what was in the
+ // buffer but the buffer contained less than the requested count. To be consistent with that
+ // behavior, we do the same thing here on Unix. Note that we may still get less the requested
+ // amount, as the OS may give us back fewer than we request, either due to reaching the end of
+ // file, or due to its own whims.
+ if (!readFromOS && bytesRead < destination.Length)
+ {
+ Debug.Assert(_readPos == _readLength, "bytesToRead should only be < destination.Length if numBytesAvailable < destination.Length");
+ _readPos = _readLength = 0; // no data left in the read buffer
+ bytesRead += ReadNative(destination.Slice(bytesRead));
+ }
+
+ return bytesRead;
+ }
+
+ /// <summary>Unbuffered, reads a block of bytes from the file handle into the given buffer.</summary>
+ /// <param name="buffer">The buffer into which data from the file is read.</param>
+ /// <returns>
+ /// The total number of bytes read into the buffer. This might be less than the number of bytes requested
+ /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
+ /// </returns>
+ private unsafe int ReadNative(Span<byte> buffer)
+ {
+ FlushWriteBuffer(); // we're about to read; dump the write buffer
+
+ VerifyOSHandlePosition();
+
+ int bytesRead;
+ fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer))
+ {
+ bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr, buffer.Length));
+ Debug.Assert(bytesRead <= buffer.Length);
+ }
+ _filePosition += bytesRead;
+ return bytesRead;
+ }
+
+ /// <summary>
+ /// Asynchronously reads a sequence of bytes from the current stream and advances
+ /// the position within the stream by the number of bytes read.
+ /// </summary>
+ /// <param name="destination">The buffer to write the data into.</param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ /// <param name="synchronousResult">If the operation completes synchronously, the number of bytes read.</param>
+ /// <returns>A task that represents the asynchronous read operation.</returns>
+ private Task<int> ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken, out int synchronousResult)
+ {
+ Debug.Assert(_useAsyncIO);
+
+ if (!CanRead) // match Windows behavior; this gets thrown synchronously
+ {
+ throw Error.GetReadNotSupported();
+ }
+
+ // Serialize operations using the semaphore.
+ Task waitTask = _asyncState.WaitAsync();
+
+ // If we got ownership immediately, and if there's enough data in our buffer
+ // to satisfy the full request of the caller, hand back the buffered data.
+ // While it would be a legal implementation of the Read contract, we don't
+ // hand back here less than the amount requested so as to match the behavior
+ // in ReadCore that will make a native call to try to fulfill the remainder
+ // of the request.
+ if (waitTask.Status == TaskStatus.RanToCompletion)
+ {
+ int numBytesAvailable = _readLength - _readPos;
+ if (numBytesAvailable >= destination.Length)
+ {
+ try
+ {
+ PrepareForReading();
+
+ new Span<byte>(GetBuffer(), _readPos, destination.Length).CopyTo(destination.Span);
+ _readPos += destination.Length;
+
+ synchronousResult = destination.Length;
+ return null;
+ }
+ catch (Exception exc)
+ {
+ synchronousResult = 0;
+ return Task.FromException<int>(exc);
+ }
+ finally
+ {
+ _asyncState.Release();
+ }
+ }
+ }
+
+ // Otherwise, issue the whole request asynchronously.
+ synchronousResult = 0;
+ _asyncState.Memory = destination;
+ return waitTask.ContinueWith((t, s) =>
+ {
+ // The options available on Unix for writing asynchronously to an arbitrary file
+ // handle typically amount to just using another thread to do the synchronous write,
+ // which is exactly what this implementation does. This does mean there are subtle
+ // differences in certain FileStream behaviors between Windows and Unix when multiple
+ // asynchronous operations are issued against the stream to execute concurrently; on
+ // Unix the operations will be serialized due to the usage of a semaphore, but the
+ // position /length information won't be updated until after the write has completed,
+ // whereas on Windows it may happen before the write has completed.
+
+ Debug.Assert(t.Status == TaskStatus.RanToCompletion);
+ var thisRef = (FileStream)s;
+ try
+ {
+ Memory<byte> memory = thisRef._asyncState.Memory;
+ thisRef._asyncState.Memory = default(Memory<byte>);
+ return thisRef.ReadSpan(memory.Span);
+ }
+ finally { thisRef._asyncState.Release(); }
+ }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary>
+ private int FillReadBufferForReadByte()
+ {
+ _asyncState?.Wait();
+ try { return ReadNative(_buffer); }
+ finally { _asyncState?.Release(); }
+ }
+
+ /// <summary>Writes a block of bytes to the file stream.</summary>
+ /// <param name="source">The buffer containing data to write to the stream.</param>
+ private void WriteSpan(ReadOnlySpan<byte> source)
+ {
+ PrepareForWriting();
+
+ // If no data is being written, nothing more to do.
+ if (source.Length == 0)
+ {
+ return;
+ }
+
+ // If there's already data in our write buffer, then we need to go through
+ // our buffer to ensure data isn't corrupted.
+ if (_writePos > 0)
+ {
+ // If there's space remaining in the buffer, then copy as much as
+ // we can from the user's buffer into ours.
+ int spaceRemaining = _bufferLength - _writePos;
+ if (spaceRemaining >= source.Length)
+ {
+ source.CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
+ _writePos += source.Length;
+ return;
+ }
+ else if (spaceRemaining > 0)
+ {
+ source.Slice(0, spaceRemaining).CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
+ _writePos += spaceRemaining;
+ source = source.Slice(spaceRemaining);
+ }
+
+ // At this point, the buffer is full, so flush it out.
+ FlushWriteBuffer();
+ }
+
+ // Our buffer is now empty. If using the buffer would slow things down (because
+ // the user's looking to write more data than we can store in the buffer),
+ // skip the buffer. Otherwise, put the remaining data into the buffer.
+ Debug.Assert(_writePos == 0);
+ if (source.Length >= _bufferLength)
+ {
+ WriteNative(source);
+ }
+ else
+ {
+ source.CopyTo(new Span<byte>(GetBuffer()));
+ _writePos = source.Length;
+ }
+ }
+
+ /// <summary>Unbuffered, writes a block of bytes to the file stream.</summary>
+ /// <param name="source">The buffer containing data to write to the stream.</param>
+ private unsafe void WriteNative(ReadOnlySpan<byte> source)
+ {
+ VerifyOSHandlePosition();
+
+ fixed (byte* bufPtr = &MemoryMarshal.GetReference(source))
+ {
+ int offset = 0;
+ int count = source.Length;
+ while (count > 0)
+ {
+ int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count));
+ _filePosition += bytesWritten;
+ offset += bytesWritten;
+ count -= bytesWritten;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Asynchronously writes a sequence of bytes to the current stream, advances
+ /// the current position within this stream by the number of bytes written, and
+ /// monitors cancellation requests.
+ /// </summary>
+ /// <param name="source">The buffer to write data from.</param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ /// <returns>A task that represents the asynchronous write operation.</returns>
+ private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
+ {
+ Debug.Assert(_useAsyncIO);
+
+ if (cancellationToken.IsCancellationRequested)
+ return new ValueTask(Task.FromCanceled(cancellationToken));
+
+ if (_fileHandle.IsClosed)
+ throw Error.GetFileNotOpen();
+
+ if (!CanWrite) // match Windows behavior; this gets thrown synchronously
+ {
+ throw Error.GetWriteNotSupported();
+ }
+
+ // Serialize operations using the semaphore.
+ Task waitTask = _asyncState.WaitAsync();
+
+ // If we got ownership immediately, and if there's enough space in our buffer
+ // to buffer the entire write request, then do so and we're done.
+ if (waitTask.Status == TaskStatus.RanToCompletion)
+ {
+ int spaceRemaining = _bufferLength - _writePos;
+ if (spaceRemaining >= source.Length)
+ {
+ try
+ {
+ PrepareForWriting();
+
+ source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length));
+ _writePos += source.Length;
+
+ return default;
+ }
+ catch (Exception exc)
+ {
+ return new ValueTask(Task.FromException(exc));
+ }
+ finally
+ {
+ _asyncState.Release();
+ }
+ }
+ }
+
+ // Otherwise, issue the whole request asynchronously.
+ _asyncState.ReadOnlyMemory = source;
+ return new ValueTask(waitTask.ContinueWith((t, s) =>
+ {
+ // The options available on Unix for writing asynchronously to an arbitrary file
+ // handle typically amount to just using another thread to do the synchronous write,
+ // which is exactly what this implementation does. This does mean there are subtle
+ // differences in certain FileStream behaviors between Windows and Unix when multiple
+ // asynchronous operations are issued against the stream to execute concurrently; on
+ // Unix the operations will be serialized due to the usage of a semaphore, but the
+ // position/length information won't be updated until after the write has completed,
+ // whereas on Windows it may happen before the write has completed.
+
+ Debug.Assert(t.Status == TaskStatus.RanToCompletion);
+ var thisRef = (FileStream)s;
+ try
+ {
+ ReadOnlyMemory<byte> readOnlyMemory = thisRef._asyncState.ReadOnlyMemory;
+ thisRef._asyncState.ReadOnlyMemory = default(ReadOnlyMemory<byte>);
+ thisRef.WriteSpan(readOnlyMemory.Span);
+ }
+ finally { thisRef._asyncState.Release(); }
+ }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default));
+ }
+
+ /// <summary>Sets the current position of this stream to the given value.</summary>
+ /// <param name="offset">The point relative to origin from which to begin seeking. </param>
+ /// <param name="origin">
+ /// Specifies the beginning, the end, or the current position as a reference
+ /// point for offset, using a value of type SeekOrigin.
+ /// </param>
+ /// <returns>The new position in the stream.</returns>
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
+ {
+ throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
+ }
+ if (_fileHandle.IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+ if (!CanSeek)
+ {
+ throw Error.GetSeekNotSupported();
+ }
+
+ VerifyOSHandlePosition();
+
+ // Flush our write/read buffer. FlushWrite will output any write buffer we have and reset _bufferWritePos.
+ // We don't call FlushRead, as that will do an unnecessary seek to rewind the read buffer, and since we're
+ // about to seek and update our position, we can simply update the offset as necessary and reset our read
+ // position and length to 0. (In the future, for some simple cases we could potentially add an optimization
+ // here to just move data around in the buffer for short jumps, to avoid re-reading the data from disk.)
+ FlushWriteBuffer();
+ if (origin == SeekOrigin.Current)
+ {
+ offset -= (_readLength - _readPos);
+ }
+ _readPos = _readLength = 0;
+
+ // Keep track of where we were, in case we're in append mode and need to verify
+ long oldPos = 0;
+ if (_appendStart >= 0)
+ {
+ oldPos = SeekCore(_fileHandle, 0, SeekOrigin.Current);
+ }
+
+ // Jump to the new location
+ long pos = SeekCore(_fileHandle, offset, origin);
+
+ // Prevent users from overwriting data in a file that was opened in append mode.
+ if (_appendStart != -1 && pos < _appendStart)
+ {
+ SeekCore(_fileHandle, oldPos, SeekOrigin.Begin);
+ throw new IOException(SR.IO_SeekAppendOverwrite);
+ }
+
+ // Return the new position
+ return pos;
+ }
+
+ /// <summary>Sets the current position of this stream to the given value.</summary>
+ /// <param name="offset">The point relative to origin from which to begin seeking. </param>
+ /// <param name="origin">
+ /// Specifies the beginning, the end, or the current position as a reference
+ /// point for offset, using a value of type SeekOrigin.
+ /// </param>
+ /// <returns>The new position in the stream.</returns>
+ private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin)
+ {
+ Debug.Assert(!fileHandle.IsClosed && (GetType() != typeof(FileStream) || CanSeekCore(fileHandle))); // verify that we can seek, but only if CanSeek won't be a virtual call (which could happen in the ctor)
+ Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End);
+
+ long pos = CheckFileCall(Interop.Sys.LSeek(fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin)); // SeekOrigin values are the same as Interop.libc.SeekWhence values
+ _filePosition = pos;
+ return pos;
+ }
+
+ private long CheckFileCall(long result, bool ignoreNotSupported = false)
+ {
+ if (result < 0)
+ {
+ Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+ if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP))
+ {
+ throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
+ }
+ }
+
+ return result;
+ }
+
+ private int CheckFileCall(int result, bool ignoreNotSupported = false)
+ {
+ CheckFileCall((long)result, ignoreNotSupported);
+
+ return result;
+ }
+
+ /// <summary>State used when the stream is in async mode.</summary>
+ private sealed class AsyncState : SemaphoreSlim
+ {
+ internal ReadOnlyMemory<byte> ReadOnlyMemory;
+ internal Memory<byte> Memory;
+
+ /// <summary>Initialize the AsyncState.</summary>
+ internal AsyncState() : base(initialCount: 1, maxCount: 1) { }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.Win32.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.Win32.cs
new file mode 100644
index 0000000000..4b75ad6dad
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.Win32.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+
+namespace System.IO
+{
+ public partial class FileStream : Stream
+ {
+ private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
+ {
+ return CreateFileOpenHandle(mode, share, options);
+ }
+
+ private unsafe SafeFileHandle CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)
+ {
+ Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
+
+ int fAccess =
+ ((_access & FileAccess.Read) == FileAccess.Read ? GENERIC_READ : 0) |
+ ((_access & FileAccess.Write) == FileAccess.Write ? GENERIC_WRITE : 0);
+
+ // Our Inheritable bit was stolen from Windows, but should be set in
+ // the security attributes class. Don't leave this bit set.
+ share &= ~FileShare.Inheritable;
+
+ // Must use a valid Win32 constant here...
+ if (mode == FileMode.Append)
+ mode = FileMode.OpenOrCreate;
+
+ int flagsAndAttributes = (int)options;
+
+ // For mitigating local elevation of privilege attack through named pipes
+ // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
+ // named pipe server can't impersonate a high privileged client security context
+ // (note that this is the effective default on CreateFile2)
+ flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS);
+
+ using (DisableMediaInsertionPrompt.Create())
+ {
+ return ValidateFileHandle(
+ Interop.Kernel32.CreateFile(_path, fAccess, share, ref secAttrs, mode, flagsAndAttributes, IntPtr.Zero));
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.WinRT.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.WinRT.cs
new file mode 100644
index 0000000000..304088eea7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.WinRT.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.InteropServices;
+
+namespace System.IO
+{
+ public partial class FileStream : Stream
+ {
+ private unsafe SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
+ {
+ Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
+
+ int access =
+ ((_access & FileAccess.Read) == FileAccess.Read ? GENERIC_READ : 0) |
+ ((_access & FileAccess.Write) == FileAccess.Write ? GENERIC_WRITE : 0);
+
+ // Our Inheritable bit was stolen from Windows, but should be set in
+ // the security attributes class. Don't leave this bit set.
+ share &= ~FileShare.Inheritable;
+
+ // Must use a valid Win32 constant here...
+ if (mode == FileMode.Append)
+ mode = FileMode.OpenOrCreate;
+
+ Interop.Kernel32.CREATEFILE2_EXTENDED_PARAMETERS parameters = new Interop.Kernel32.CREATEFILE2_EXTENDED_PARAMETERS();
+ parameters.dwSize = (uint)sizeof(Interop.Kernel32.CREATEFILE2_EXTENDED_PARAMETERS);
+ parameters.dwFileFlags = (uint)options;
+ parameters.lpSecurityAttributes = &secAttrs;
+
+ using (DisableMediaInsertionPrompt.Create())
+ {
+ return ValidateFileHandle(Interop.Kernel32.CreateFile2(
+ lpFileName: _path,
+ dwDesiredAccess: access,
+ dwShareMode: share,
+ dwCreationDisposition: mode,
+ pCreateExParams: ref parameters));
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs
new file mode 100644
index 0000000000..80c07dfdf4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs
@@ -0,0 +1,1657 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.CompilerServices;
+
+/*
+ * Win32FileStream supports different modes of accessing the disk - async mode
+ * and sync mode. They are two completely different codepaths in the
+ * sync & async methods (i.e. Read/Write vs. ReadAsync/WriteAsync). File
+ * handles in NT can be opened in only sync or overlapped (async) mode,
+ * and we have to deal with this pain. Stream has implementations of
+ * the sync methods in terms of the async ones, so we'll
+ * call through to our base class to get those methods when necessary.
+ *
+ * Also buffering is added into Win32FileStream as well. Folded in the
+ * code from BufferedStream, so all the comments about it being mostly
+ * aggressive (and the possible perf improvement) apply to Win32FileStream as
+ * well. Also added some buffering to the async code paths.
+ *
+ * Class Invariants:
+ * The class has one buffer, shared for reading & writing. It can only be
+ * used for one or the other at any point in time - not both. The following
+ * should be true:
+ * 0 <= _readPos <= _readLen < _bufferSize
+ * 0 <= _writePos < _bufferSize
+ * _readPos == _readLen && _readPos > 0 implies the read buffer is valid,
+ * but we're at the end of the buffer.
+ * _readPos == _readLen == 0 means the read buffer contains garbage.
+ * Either _writePos can be greater than 0, or _readLen & _readPos can be
+ * greater than zero, but neither can be greater than zero at the same time.
+ *
+ */
+
+namespace System.IO
+{
+ public partial class FileStream : Stream
+ {
+ private bool _canSeek;
+ private bool _isPipe; // Whether to disable async buffering code.
+ private long _appendStart; // When appending, prevent overwriting file.
+
+ private static unsafe IOCompletionCallback s_ioCallback = FileStreamCompletionSource.IOCallback;
+
+ private Task _activeBufferOperation = null; // tracks in-progress async ops using the buffer
+ private PreAllocatedOverlapped _preallocatedOverlapped; // optimization for async ops to avoid per-op allocations
+ private FileStreamCompletionSource _currentOverlappedOwner; // async op currently using the preallocated overlapped
+
+ private void Init(FileMode mode, FileShare share)
+ {
+ // Disallow access to all non-file devices from the Win32FileStream
+ // constructors that take a String. Everyone else can call
+ // CreateFile themselves then use the constructor that takes an
+ // IntPtr. Disallows "con:", "com1:", "lpt1:", etc.
+ int fileType = Interop.Kernel32.GetFileType(_fileHandle);
+ if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK)
+ {
+ _fileHandle.Dispose();
+ throw new NotSupportedException(SR.NotSupported_FileStreamOnNonFiles);
+ }
+
+ // This is necessary for async IO using IO Completion ports via our
+ // managed Threadpool API's. This (theoretically) calls the OS's
+ // BindIoCompletionCallback method, and passes in a stub for the
+ // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
+ // struct for this request and gets a delegate to a managed callback
+ // from there, which it then calls on a threadpool thread. (We allocate
+ // our native OVERLAPPED structs 2 pointers too large and store EE state
+ // & GC handles there, one to an IAsyncResult, the other to a delegate.)
+ if (_useAsyncIO)
+ {
+ try
+ {
+ _fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle);
+ }
+ catch (ArgumentException ex)
+ {
+ throw new IOException(SR.IO_BindHandleFailed, ex);
+ }
+ finally
+ {
+ if (_fileHandle.ThreadPoolBinding == null)
+ {
+ // We should close the handle so that the handle is not open until SafeFileHandle GC
+ Debug.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?");
+ _fileHandle.Dispose();
+ }
+ }
+ }
+
+ _canSeek = true;
+
+ // For Append mode...
+ if (mode == FileMode.Append)
+ {
+ _appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End);
+ }
+ else
+ {
+ _appendStart = -1;
+ }
+ }
+
+ private void InitFromHandle(SafeFileHandle handle, FileAccess access, bool useAsyncIO)
+ {
+#if DEBUG
+ bool hadBinding = handle.ThreadPoolBinding != null;
+
+ try
+ {
+#endif
+ InitFromHandleImpl(handle, access, useAsyncIO);
+#if DEBUG
+ }
+ catch
+ {
+ Debug.Assert(hadBinding || handle.ThreadPoolBinding == null, "We should never error out with a ThreadPoolBinding we've added");
+ throw;
+ }
+#endif
+ }
+
+ private void InitFromHandleImpl(SafeFileHandle handle, FileAccess access, bool useAsyncIO)
+ {
+ int handleType = Interop.Kernel32.GetFileType(handle);
+ Debug.Assert(handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK || handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE || handleType == Interop.Kernel32.FileTypes.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!");
+
+ _canSeek = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
+ _isPipe = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE;
+
+ // This is necessary for async IO using IO Completion ports via our
+ // managed Threadpool API's. This calls the OS's
+ // BindIoCompletionCallback method, and passes in a stub for the
+ // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
+ // struct for this request and gets a delegate to a managed callback
+ // from there, which it then calls on a threadpool thread. (We allocate
+ // our native OVERLAPPED structs 2 pointers too large and store EE
+ // state & a handle to a delegate there.)
+ //
+ // If, however, we've already bound this file handle to our completion port,
+ // don't try to bind it again because it will fail. A handle can only be
+ // bound to a single completion port at a time.
+ if (useAsyncIO && !(handle.IsAsync ?? false))
+ {
+ try
+ {
+ handle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(handle);
+ }
+ catch (Exception ex)
+ {
+ // If you passed in a synchronous handle and told us to use
+ // it asynchronously, throw here.
+ throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle), ex);
+ }
+ }
+ else if (!useAsyncIO)
+ {
+ if (handleType != Interop.Kernel32.FileTypes.FILE_TYPE_PIPE)
+ VerifyHandleIsSync(handle, access);
+ }
+
+ if (_canSeek)
+ SeekCore(handle, 0, SeekOrigin.Current);
+ else
+ _filePosition = 0;
+ }
+
+ private static unsafe Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share)
+ {
+ Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default;
+ if ((share & FileShare.Inheritable) != 0)
+ {
+ secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES
+ {
+ nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES),
+ bInheritHandle = Interop.BOOL.TRUE
+ };
+ }
+ return secAttrs;
+ }
+
+ // Verifies that this handle supports synchronous IO operations (unless you
+ // didn't open it for either reading or writing).
+ private static unsafe void VerifyHandleIsSync(SafeFileHandle handle, FileAccess access)
+ {
+ // Do NOT use this method on pipes. Reading or writing to a pipe may
+ // cause an app to block incorrectly, introducing a deadlock (depending
+ // on whether a write will wake up an already-blocked thread or this
+ // Win32FileStream's thread).
+ Debug.Assert(Interop.Kernel32.GetFileType(handle) != Interop.Kernel32.FileTypes.FILE_TYPE_PIPE);
+
+ byte* bytes = stackalloc byte[1];
+ int numBytesReadWritten;
+ int r = -1;
+
+ // If the handle is a pipe, ReadFile will block until there
+ // has been a write on the other end. We'll just have to deal with it,
+ // For the read end of a pipe, you can mess up and
+ // accidentally read synchronously from an async pipe.
+ if ((access & FileAccess.Read) != 0) // don't use the virtual CanRead or CanWrite, as this may be used in the ctor
+ {
+ r = Interop.Kernel32.ReadFile(handle, bytes, 0, out numBytesReadWritten, IntPtr.Zero);
+ }
+ else if ((access & FileAccess.Write) != 0) // don't use the virtual CanRead or CanWrite, as this may be used in the ctor
+ {
+ r = Interop.Kernel32.WriteFile(handle, bytes, 0, out numBytesReadWritten, IntPtr.Zero);
+ }
+
+ if (r == 0)
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+ switch (errorCode)
+ {
+ case Interop.Errors.ERROR_INVALID_PARAMETER:
+ throw new ArgumentException(SR.Arg_HandleNotSync, "handle");
+ case Interop.Errors.ERROR_INVALID_HANDLE:
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+ }
+ }
+ }
+
+ private bool HasActiveBufferOperation
+ => _activeBufferOperation != null && !_activeBufferOperation.IsCompleted;
+
+ public override bool CanSeek => _canSeek;
+
+ private unsafe long GetLengthInternal()
+ {
+ Interop.Kernel32.FILE_STANDARD_INFO info = new Interop.Kernel32.FILE_STANDARD_INFO();
+
+ if (!Interop.Kernel32.GetFileInformationByHandleEx(_fileHandle, Interop.Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, out info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO)))
+ throw Win32Marshal.GetExceptionForLastWin32Error(_path);
+ long len = info.EndOfFile;
+ // If we're writing near the end of the file, we must include our
+ // internal buffer in our Length calculation. Don't flush because
+ // we use the length of the file in our async write method.
+ if (_writePos > 0 && _filePosition + _writePos > len)
+ len = _writePos + _filePosition;
+ return len;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ // Nothing will be done differently based on whether we are
+ // disposing vs. finalizing. This is taking advantage of the
+ // weak ordering between normal finalizable objects & critical
+ // finalizable objects, which I included in the SafeHandle
+ // design for Win32FileStream, which would often "just work" when
+ // finalized.
+ try
+ {
+ if (_fileHandle != null && !_fileHandle.IsClosed)
+ {
+ // Flush data to disk iff we were writing. After
+ // thinking about this, we also don't need to flush
+ // our read position, regardless of whether the handle
+ // was exposed to the user. They probably would NOT
+ // want us to do this.
+ if (_writePos > 0)
+ {
+ try
+ {
+ FlushWriteBuffer(!disposing);
+ }
+ catch (Exception e) when (IsIoRelatedException(e) && !disposing)
+ {
+ // On finalization, ignore failures from trying to flush the write buffer,
+ // e.g. if this stream is wrapping a pipe and the pipe is now broken.
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (_fileHandle != null && !_fileHandle.IsClosed)
+ {
+ if (_fileHandle.ThreadPoolBinding != null)
+ _fileHandle.ThreadPoolBinding.Dispose();
+
+ _fileHandle.Dispose();
+ }
+
+ if (_preallocatedOverlapped != null)
+ _preallocatedOverlapped.Dispose();
+
+ _canSeek = false;
+
+ // Don't set the buffer to null, to avoid a NullReferenceException
+ // when users have a race condition in their code (i.e. they call
+ // Close when calling another method on Stream like Read).
+ //_buffer = null;
+ base.Dispose(disposing);
+ }
+ }
+
+ private void FlushOSBuffer()
+ {
+ if (!Interop.Kernel32.FlushFileBuffers(_fileHandle))
+ {
+ throw Win32Marshal.GetExceptionForLastWin32Error(_path);
+ }
+ }
+
+ // Returns a task that flushes the internal write buffer
+ private Task FlushWriteAsync(CancellationToken cancellationToken)
+ {
+ Debug.Assert(_useAsyncIO);
+ Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWriteAsync!");
+
+ // If the buffer is already flushed, don't spin up the OS write
+ if (_writePos == 0) return Task.CompletedTask;
+
+ Task flushTask = WriteAsyncInternalCore(new ReadOnlyMemory<byte>(GetBuffer(), 0, _writePos), cancellationToken);
+ _writePos = 0;
+
+ // Update the active buffer operation
+ _activeBufferOperation = HasActiveBufferOperation ?
+ Task.WhenAll(_activeBufferOperation, flushTask) :
+ flushTask;
+
+ return flushTask;
+ }
+
+ private void FlushWriteBufferForWriteByte() => FlushWriteBuffer();
+
+ // Writes are buffered. Anytime the buffer fills up
+ // (_writePos + delta > _bufferSize) or the buffer switches to reading
+ // and there is left over data (_writePos > 0), this function must be called.
+ private void FlushWriteBuffer(bool calledFromFinalizer = false)
+ {
+ if (_writePos == 0) return;
+ Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWrite!");
+
+ if (_useAsyncIO)
+ {
+ Task writeTask = FlushWriteAsync(CancellationToken.None);
+ // With our Whidbey async IO & overlapped support for AD unloads,
+ // we don't strictly need to block here to release resources
+ // since that support takes care of the pinning & freeing the
+ // overlapped struct. We need to do this when called from
+ // Close so that the handle is closed when Close returns, but
+ // we don't need to call EndWrite from the finalizer.
+ // Additionally, if we do call EndWrite, we block forever
+ // because AD unloads prevent us from running the managed
+ // callback from the IO completion port. Blocking here when
+ // called from the finalizer during AD unload is clearly wrong,
+ // but we can't use any sort of test for whether the AD is
+ // unloading because if we weren't unloading, an AD unload
+ // could happen on a separate thread before we call EndWrite.
+ if (!calledFromFinalizer)
+ {
+ writeTask.GetAwaiter().GetResult();
+ }
+ }
+ else
+ {
+ WriteCore(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
+ }
+
+ _writePos = 0;
+ }
+
+ private void SetLengthInternal(long value)
+ {
+ // Handle buffering updates.
+ if (_writePos > 0)
+ {
+ FlushWriteBuffer();
+ }
+ else if (_readPos < _readLength)
+ {
+ FlushReadBuffer();
+ }
+ _readPos = 0;
+ _readLength = 0;
+
+ if (_appendStart != -1 && value < _appendStart)
+ throw new IOException(SR.IO_SetLengthAppendTruncate);
+ SetLengthCore(value);
+ }
+
+ // We absolutely need this method broken out so that WriteInternalCoreAsync can call
+ // a method without having to go through buffering code that might call FlushWrite.
+ private void SetLengthCore(long value)
+ {
+ Debug.Assert(value >= 0, "value >= 0");
+ long origPos = _filePosition;
+
+ VerifyOSHandlePosition();
+ if (_filePosition != value)
+ SeekCore(_fileHandle, value, SeekOrigin.Begin);
+ if (!Interop.Kernel32.SetEndOfFile(_fileHandle))
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+ if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_FileLengthTooBig);
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+ }
+ // Return file pointer to where it was before setting length
+ if (origPos != value)
+ {
+ if (origPos < value)
+ SeekCore(_fileHandle, origPos, SeekOrigin.Begin);
+ else
+ SeekCore(_fileHandle, 0, SeekOrigin.End);
+ }
+ }
+
+ // Instance method to help code external to this MarshalByRefObject avoid
+ // accessing its fields by ref. This avoids a compiler warning.
+ private FileStreamCompletionSource CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource newSource, FileStreamCompletionSource existingSource) => Interlocked.CompareExchange(ref _currentOverlappedOwner, newSource, existingSource);
+
+ private int ReadSpan(Span<byte> destination)
+ {
+ Debug.Assert(!_useAsyncIO, "Must only be used when in synchronous mode");
+ Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength),
+ "We're either reading or writing, but not both.");
+
+ bool isBlocked = false;
+ int n = _readLength - _readPos;
+ // if the read buffer is empty, read into either user's array or our
+ // buffer, depending on number of bytes user asked for and buffer size.
+ if (n == 0)
+ {
+ if (!CanRead) throw Error.GetReadNotSupported();
+ if (_writePos > 0) FlushWriteBuffer();
+ if (!CanSeek || (destination.Length >= _bufferLength))
+ {
+ n = ReadNative(destination);
+ // Throw away read buffer.
+ _readPos = 0;
+ _readLength = 0;
+ return n;
+ }
+ n = ReadNative(GetBuffer());
+ if (n == 0) return 0;
+ isBlocked = n < _bufferLength;
+ _readPos = 0;
+ _readLength = n;
+ }
+ // Now copy min of count or numBytesAvailable (i.e. near EOF) to array.
+ if (n > destination.Length) n = destination.Length;
+ new ReadOnlySpan<byte>(GetBuffer(), _readPos, n).CopyTo(destination);
+ _readPos += n;
+
+ // We may have read less than the number of bytes the user asked
+ // for, but that is part of the Stream contract. Reading again for
+ // more data may cause us to block if we're using a device with
+ // no clear end of file, such as a serial port or pipe. If we
+ // blocked here & this code was used with redirected pipes for a
+ // process's standard output, this can lead to deadlocks involving
+ // two processes. But leave this here for files to avoid what would
+ // probably be a breaking change. --
+
+ // If we are reading from a device with no clear EOF like a
+ // serial port or a pipe, this will cause us to block incorrectly.
+ if (!_isPipe)
+ {
+ // If we hit the end of the buffer and didn't have enough bytes, we must
+ // read some more from the underlying stream. However, if we got
+ // fewer bytes from the underlying stream than we asked for (i.e. we're
+ // probably blocked), don't ask for more bytes.
+ if (n < destination.Length && !isBlocked)
+ {
+ Debug.Assert(_readPos == _readLength, "Read buffer should be empty!");
+ int moreBytesRead = ReadNative(destination.Slice(n));
+ n += moreBytesRead;
+ // We've just made our buffer inconsistent with our position
+ // pointer. We must throw away the read buffer.
+ _readPos = 0;
+ _readLength = 0;
+ }
+ }
+
+ return n;
+ }
+
+ [Conditional("DEBUG")]
+ private void AssertCanRead()
+ {
+ Debug.Assert(!_fileHandle.IsClosed, "!_fileHandle.IsClosed");
+ Debug.Assert(CanRead, "CanRead");
+ }
+
+ /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary>
+ private int FillReadBufferForReadByte() =>
+ _useAsyncIO ?
+ ReadNativeAsync(new Memory<byte>(_buffer), 0, CancellationToken.None).GetAwaiter().GetResult() :
+ ReadNative(_buffer);
+
+ private unsafe int ReadNative(Span<byte> buffer)
+ {
+ Debug.Assert(!_useAsyncIO, $"{nameof(ReadNative)} doesn't work on asynchronous file streams.");
+ AssertCanRead();
+
+ // Make sure we are reading from the right spot
+ VerifyOSHandlePosition();
+
+ int r = ReadFileNative(_fileHandle, buffer, null, out int errorCode);
+
+ if (r == -1)
+ {
+ // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe.
+ if (errorCode == ERROR_BROKEN_PIPE)
+ {
+ r = 0;
+ }
+ else
+ {
+ if (errorCode == ERROR_INVALID_PARAMETER)
+ throw new ArgumentException(SR.Arg_HandleNotSync, "_fileHandle");
+
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+ }
+ }
+ Debug.Assert(r >= 0, "FileStream's ReadNative is likely broken.");
+ _filePosition += r;
+
+ return r;
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
+ throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
+ if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
+ if (!CanSeek) throw Error.GetSeekNotSupported();
+
+ Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
+
+ // If we've got bytes in our buffer to write, write them out.
+ // If we've read in and consumed some bytes, we'll have to adjust
+ // our seek positions ONLY IF we're seeking relative to the current
+ // position in the stream. This simulates doing a seek to the new
+ // position, then a read for the number of bytes we have in our buffer.
+ if (_writePos > 0)
+ {
+ FlushWriteBuffer();
+ }
+ else if (origin == SeekOrigin.Current)
+ {
+ // Don't call FlushRead here, which would have caused an infinite
+ // loop. Simply adjust the seek origin. This isn't necessary
+ // if we're seeking relative to the beginning or end of the stream.
+ offset -= (_readLength - _readPos);
+ }
+ _readPos = _readLength = 0;
+
+ // Verify that internal position is in sync with the handle
+ VerifyOSHandlePosition();
+
+ long oldPos = _filePosition + (_readPos - _readLength);
+ long pos = SeekCore(_fileHandle, offset, origin);
+
+ // Prevent users from overwriting data in a file that was opened in
+ // append mode.
+ if (_appendStart != -1 && pos < _appendStart)
+ {
+ SeekCore(_fileHandle, oldPos, SeekOrigin.Begin);
+ throw new IOException(SR.IO_SeekAppendOverwrite);
+ }
+
+ // We now must update the read buffer. We can in some cases simply
+ // update _readPos within the buffer, copy around the buffer so our
+ // Position property is still correct, and avoid having to do more
+ // reads from the disk. Otherwise, discard the buffer's contents.
+ if (_readLength > 0)
+ {
+ // We can optimize the following condition:
+ // oldPos - _readPos <= pos < oldPos + _readLen - _readPos
+ if (oldPos == pos)
+ {
+ if (_readPos > 0)
+ {
+ Buffer.BlockCopy(GetBuffer(), _readPos, GetBuffer(), 0, _readLength - _readPos);
+ _readLength -= _readPos;
+ _readPos = 0;
+ }
+ // If we still have buffered data, we must update the stream's
+ // position so our Position property is correct.
+ if (_readLength > 0)
+ SeekCore(_fileHandle, _readLength, SeekOrigin.Current);
+ }
+ else if (oldPos - _readPos < pos && pos < oldPos + _readLength - _readPos)
+ {
+ int diff = (int)(pos - oldPos);
+ Buffer.BlockCopy(GetBuffer(), _readPos + diff, GetBuffer(), 0, _readLength - (_readPos + diff));
+ _readLength -= (_readPos + diff);
+ _readPos = 0;
+ if (_readLength > 0)
+ SeekCore(_fileHandle, _readLength, SeekOrigin.Current);
+ }
+ else
+ {
+ // Lose the read buffer.
+ _readPos = 0;
+ _readLength = 0;
+ }
+ Debug.Assert(_readLength >= 0 && _readPos <= _readLength, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen");
+ Debug.Assert(pos == Position, "Seek optimization: pos != Position! Buffer math was mangled.");
+ }
+ return pos;
+ }
+
+ // This doesn't do argument checking. Necessary for SetLength, which must
+ // set the file pointer beyond the end of the file. This will update the
+ // internal position
+ private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin, bool closeInvalidHandle = false)
+ {
+ Debug.Assert(!fileHandle.IsClosed && _canSeek, "!fileHandle.IsClosed && _canSeek");
+ Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End, "origin >= SeekOrigin.Begin && origin <= SeekOrigin.End");
+
+ if (!Interop.Kernel32.SetFilePointerEx(fileHandle, offset, out long ret, (uint)origin))
+ {
+ if (closeInvalidHandle)
+ {
+ throw Win32Marshal.GetExceptionForWin32Error(GetLastWin32ErrorAndDisposeHandleIfInvalid(throwIfInvalidHandle: false), _path);
+ }
+ else
+ {
+ throw Win32Marshal.GetExceptionForLastWin32Error(_path);
+ }
+ }
+
+ _filePosition = ret;
+ return ret;
+ }
+
+ partial void OnBufferAllocated()
+ {
+ Debug.Assert(_buffer != null);
+ Debug.Assert(_preallocatedOverlapped == null);
+
+ if (_useAsyncIO)
+ _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, _buffer);
+ }
+
+ private void WriteSpan(ReadOnlySpan<byte> source)
+ {
+ Debug.Assert(!_useAsyncIO, "Must only be used when in synchronous mode");
+
+ if (_writePos == 0)
+ {
+ // Ensure we can write to the stream, and ready buffer for writing.
+ if (!CanWrite) throw Error.GetWriteNotSupported();
+ if (_readPos < _readLength) FlushReadBuffer();
+ _readPos = 0;
+ _readLength = 0;
+ }
+
+ // If our buffer has data in it, copy data from the user's array into
+ // the buffer, and if we can fit it all there, return. Otherwise, write
+ // the buffer to disk and copy any remaining data into our buffer.
+ // The assumption here is memcpy is cheaper than disk (or net) IO.
+ // (10 milliseconds to disk vs. ~20-30 microseconds for a 4K memcpy)
+ // So the extra copying will reduce the total number of writes, in
+ // non-pathological cases (i.e. write 1 byte, then write for the buffer
+ // size repeatedly)
+ if (_writePos > 0)
+ {
+ int numBytes = _bufferLength - _writePos; // space left in buffer
+ if (numBytes > 0)
+ {
+ if (numBytes >= source.Length)
+ {
+ source.CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
+ _writePos += source.Length;
+ return;
+ }
+ else
+ {
+ source.Slice(0, numBytes).CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
+ _writePos += numBytes;
+ source = source.Slice(numBytes);
+ }
+ }
+ // Reset our buffer. We essentially want to call FlushWrite
+ // without calling Flush on the underlying Stream.
+
+ WriteCore(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
+ _writePos = 0;
+ }
+
+ // If the buffer would slow writes down, avoid buffer completely.
+ if (source.Length >= _bufferLength)
+ {
+ Debug.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted.");
+ WriteCore(source);
+ return;
+ }
+ else if (source.Length == 0)
+ {
+ return; // Don't allocate a buffer then call memcpy for 0 bytes.
+ }
+
+ // Copy remaining bytes into buffer, to write at a later date.
+ source.CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
+ _writePos = source.Length;
+ return;
+ }
+
+ private unsafe void WriteCore(ReadOnlySpan<byte> source)
+ {
+ Debug.Assert(!_useAsyncIO);
+ Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
+ Debug.Assert(CanWrite, "_parent.CanWrite");
+ Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
+
+ // Make sure we are writing to the position that we think we are
+ VerifyOSHandlePosition();
+
+ int errorCode = 0;
+ int r = WriteFileNative(_fileHandle, source, null, out errorCode);
+
+ if (r == -1)
+ {
+ // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing.
+ if (errorCode == ERROR_NO_DATA)
+ {
+ r = 0;
+ }
+ else
+ {
+ // ERROR_INVALID_PARAMETER may be returned for writes
+ // where the position is too large or for synchronous writes
+ // to a handle opened asynchronously.
+ if (errorCode == ERROR_INVALID_PARAMETER)
+ throw new IOException(SR.IO_FileTooLongOrHandleNotSync);
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+ }
+ }
+ Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken.");
+ _filePosition += r;
+ return;
+ }
+
+ private Task<int> ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken, out int synchronousResult)
+ {
+ Debug.Assert(_useAsyncIO);
+ if (!CanRead) throw Error.GetReadNotSupported();
+
+ Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
+
+ if (_isPipe)
+ {
+ // Pipes are tricky, at least when you have 2 different pipes
+ // that you want to use simultaneously. When redirecting stdout
+ // & stderr with the Process class, it's easy to deadlock your
+ // parent & child processes when doing writes 4K at a time. The
+ // OS appears to use a 4K buffer internally. If you write to a
+ // pipe that is full, you will block until someone read from
+ // that pipe. If you try reading from an empty pipe and
+ // Win32FileStream's ReadAsync blocks waiting for data to fill it's
+ // internal buffer, you will be blocked. In a case where a child
+ // process writes to stdout & stderr while a parent process tries
+ // reading from both, you can easily get into a deadlock here.
+ // To avoid this deadlock, don't buffer when doing async IO on
+ // pipes. But don't completely ignore buffered data either.
+ if (_readPos < _readLength)
+ {
+ int n = Math.Min(_readLength - _readPos, destination.Length);
+ new Span<byte>(GetBuffer(), _readPos, n).CopyTo(destination.Span);
+ _readPos += n;
+ synchronousResult = n;
+ return null;
+ }
+ else
+ {
+ Debug.Assert(_writePos == 0, "Win32FileStream must not have buffered write data here! Pipes should be unidirectional.");
+ synchronousResult = 0;
+ return ReadNativeAsync(destination, 0, cancellationToken);
+ }
+ }
+
+ Debug.Assert(!_isPipe, "Should not be a pipe.");
+
+ // Handle buffering.
+ if (_writePos > 0) FlushWriteBuffer();
+ if (_readPos == _readLength)
+ {
+ // I can't see how to handle buffering of async requests when
+ // filling the buffer asynchronously, without a lot of complexity.
+ // The problems I see are issuing an async read, we do an async
+ // read to fill the buffer, then someone issues another read
+ // (either synchronously or asynchronously) before the first one
+ // returns. This would involve some sort of complex buffer locking
+ // that we probably don't want to get into, at least not in V1.
+ // If we did a sync read to fill the buffer, we could avoid the
+ // problem, and any async read less than 64K gets turned into a
+ // synchronous read by NT anyways... --
+
+ if (destination.Length < _bufferLength)
+ {
+ Task<int> readTask = ReadNativeAsync(new Memory<byte>(GetBuffer()), 0, cancellationToken);
+ _readLength = readTask.GetAwaiter().GetResult();
+ int n = Math.Min(_readLength, destination.Length);
+ new Span<byte>(GetBuffer(), 0, n).CopyTo(destination.Span);
+ _readPos = n;
+
+ synchronousResult = n;
+ return null;
+ }
+ else
+ {
+ // Here we're making our position pointer inconsistent
+ // with our read buffer. Throw away the read buffer's contents.
+ _readPos = 0;
+ _readLength = 0;
+ synchronousResult = 0;
+ return ReadNativeAsync(destination, 0, cancellationToken);
+ }
+ }
+ else
+ {
+ int n = Math.Min(_readLength - _readPos, destination.Length);
+ new Span<byte>(GetBuffer(), _readPos, n).CopyTo(destination.Span);
+ _readPos += n;
+
+ if (n == destination.Length)
+ {
+ // Return a completed task
+ synchronousResult = n;
+ return null;
+ }
+ else
+ {
+ // For streams with no clear EOF like serial ports or pipes
+ // we cannot read more data without causing an app to block
+ // incorrectly. Pipes don't go down this path
+ // though. This code needs to be fixed.
+ // Throw away read buffer.
+ _readPos = 0;
+ _readLength = 0;
+ synchronousResult = 0;
+ return ReadNativeAsync(destination.Slice(n), n, cancellationToken);
+ }
+ }
+ }
+
+ private unsafe Task<int> ReadNativeAsync(Memory<byte> destination, int numBufferedBytesRead, CancellationToken cancellationToken)
+ {
+ AssertCanRead();
+ Debug.Assert(_useAsyncIO, "ReadNativeAsync doesn't work on synchronous file streams!");
+
+ // Create and store async stream class library specific data in the async result
+ FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, numBufferedBytesRead, destination);
+ NativeOverlapped* intOverlapped = completionSource.Overlapped;
+
+ // Calculate position in the file we should be at after the read is done
+ if (CanSeek)
+ {
+ long len = Length;
+
+ // Make sure we are reading from the position that we think we are
+ VerifyOSHandlePosition();
+
+ if (_filePosition + destination.Length > len)
+ {
+ if (_filePosition <= len)
+ {
+ destination = destination.Slice(0, (int)(len - _filePosition));
+ }
+ else
+ {
+ destination = default(Memory<byte>);
+ }
+ }
+
+ // Now set the position to read from in the NativeOverlapped struct
+ // For pipes, we should leave the offset fields set to 0.
+ intOverlapped->OffsetLow = unchecked((int)_filePosition);
+ intOverlapped->OffsetHigh = (int)(_filePosition >> 32);
+
+ // When using overlapped IO, the OS is not supposed to
+ // touch the file pointer location at all. We will adjust it
+ // ourselves. This isn't threadsafe.
+
+ // WriteFile should not update the file pointer when writing
+ // in overlapped mode, according to MSDN. But it does update
+ // the file pointer when writing to a UNC path!
+ // So changed the code below to seek to an absolute
+ // location, not a relative one. ReadFile seems consistent though.
+ SeekCore(_fileHandle, destination.Length, SeekOrigin.Current);
+ }
+
+ // queue an async ReadFile operation and pass in a packed overlapped
+ int errorCode = 0;
+ int r = ReadFileNative(_fileHandle, destination.Span, intOverlapped, out errorCode);
+
+ // ReadFile, the OS version, will return 0 on failure. But
+ // my ReadFileNative wrapper returns -1. My wrapper will return
+ // the following:
+ // On error, r==-1.
+ // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING
+ // on async requests that completed sequentially, r==0
+ // You will NEVER RELIABLY be able to get the number of bytes
+ // read back from this call when using overlapped structures! You must
+ // not pass in a non-null lpNumBytesRead to ReadFile when using
+ // overlapped structures! This is by design NT behavior.
+ if (r == -1)
+ {
+ // For pipes, when they hit EOF, they will come here.
+ if (errorCode == ERROR_BROKEN_PIPE)
+ {
+ // Not an error, but EOF. AsyncFSCallback will NOT be
+ // called. Call the user callback here.
+
+ // We clear the overlapped status bit for this special case.
+ // Failure to do so looks like we are freeing a pending overlapped later.
+ intOverlapped->InternalLow = IntPtr.Zero;
+ completionSource.SetCompletedSynchronously(0);
+ }
+ else if (errorCode != ERROR_IO_PENDING)
+ {
+ if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere.
+ {
+ SeekCore(_fileHandle, 0, SeekOrigin.Current);
+ }
+
+ completionSource.ReleaseNativeResource();
+
+ if (errorCode == ERROR_HANDLE_EOF)
+ {
+ throw Error.GetEndOfFile();
+ }
+ else
+ {
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+ }
+ }
+ else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING
+ {
+ // Only once the IO is pending do we register for cancellation
+ completionSource.RegisterForCancellation(cancellationToken);
+ }
+ }
+ else
+ {
+ // Due to a workaround for a race condition in NT's ReadFile &
+ // WriteFile routines, we will always be returning 0 from ReadFileNative
+ // when we do async IO instead of the number of bytes read,
+ // irregardless of whether the operation completed
+ // synchronously or asynchronously. We absolutely must not
+ // set asyncResult._numBytes here, since will never have correct
+ // results.
+ }
+
+ return completionSource.Task;
+ }
+
+ private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
+ {
+ Debug.Assert(_useAsyncIO);
+ Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
+ Debug.Assert(!_isPipe || (_readPos == 0 && _readLength == 0), "Win32FileStream must not have buffered data here! Pipes should be unidirectional.");
+
+ if (!CanWrite) throw Error.GetWriteNotSupported();
+
+ bool writeDataStoredInBuffer = false;
+ if (!_isPipe) // avoid async buffering with pipes, as doing so can lead to deadlocks (see comments in ReadInternalAsyncCore)
+ {
+ // Ensure the buffer is clear for writing
+ if (_writePos == 0)
+ {
+ if (_readPos < _readLength)
+ {
+ FlushReadBuffer();
+ }
+ _readPos = 0;
+ _readLength = 0;
+ }
+
+ // Determine how much space remains in the buffer
+ int remainingBuffer = _bufferLength - _writePos;
+ Debug.Assert(remainingBuffer >= 0);
+
+ // Simple/common case:
+ // - The write is smaller than our buffer, such that it's worth considering buffering it.
+ // - There's no active flush operation, such that we don't have to worry about the existing buffer being in use.
+ // - And the data we're trying to write fits in the buffer, meaning it wasn't already filled by previous writes.
+ // In that case, just store it in the buffer.
+ if (source.Length < _bufferLength && !HasActiveBufferOperation && source.Length <= remainingBuffer)
+ {
+ source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length));
+ _writePos += source.Length;
+ writeDataStoredInBuffer = true;
+
+ // There is one special-but-common case, common because devs often use
+ // byte[] sizes that are powers of 2 and thus fit nicely into our buffer, which is
+ // also a power of 2. If after our write the buffer still has remaining space,
+ // then we're done and can return a completed task now. But if we filled the buffer
+ // completely, we want to do the asynchronous flush/write as part of this operation
+ // rather than waiting until the next write that fills the buffer.
+ if (source.Length != remainingBuffer)
+ return default;
+
+ Debug.Assert(_writePos == _bufferLength);
+ }
+ }
+
+ // At this point, at least one of the following is true:
+ // 1. There was an active flush operation (it could have completed by now, though).
+ // 2. The data doesn't fit in the remaining buffer (or it's a pipe and we chose not to try).
+ // 3. We wrote all of the data to the buffer, filling it.
+ //
+ // If there's an active operation, we can't touch the current buffer because it's in use.
+ // That gives us a choice: we can either allocate a new buffer, or we can skip the buffer
+ // entirely (even if the data would otherwise fit in it). For now, for simplicity, we do
+ // the latter; it could also have performance wins due to OS-level optimizations, and we could
+ // potentially add support for PreAllocatedOverlapped due to having a single buffer. (We can
+ // switch to allocating a new buffer, potentially experimenting with buffer pooling, should
+ // performance data suggest it's appropriate.)
+ //
+ // If the data doesn't fit in the remaining buffer, it could be because it's so large
+ // it's greater than the entire buffer size, in which case we'd always skip the buffer,
+ // or it could be because there's more data than just the space remaining. For the latter
+ // case, we need to issue an asynchronous write to flush that data, which then turns this into
+ // the first case above with an active operation.
+ //
+ // If we already stored the data, then we have nothing additional to write beyond what
+ // we need to flush.
+ //
+ // In any of these cases, we have the same outcome:
+ // - If there's data in the buffer, flush it by writing it out asynchronously.
+ // - Then, if there's any data to be written, issue a write for it concurrently.
+ // We return a Task that represents one or both.
+
+ // Flush the buffer asynchronously if there's anything to flush
+ Task flushTask = null;
+ if (_writePos > 0)
+ {
+ flushTask = FlushWriteAsync(cancellationToken);
+
+ // If we already copied all of the data into the buffer,
+ // simply return the flush task here. Same goes for if the task has
+ // already completed and was unsuccessful.
+ if (writeDataStoredInBuffer ||
+ flushTask.IsFaulted ||
+ flushTask.IsCanceled)
+ {
+ return new ValueTask(flushTask);
+ }
+ }
+
+ Debug.Assert(!writeDataStoredInBuffer);
+ Debug.Assert(_writePos == 0);
+
+ // Finally, issue the write asynchronously, and return a Task that logically
+ // represents the write operation, including any flushing done.
+ Task writeTask = WriteAsyncInternalCore(source, cancellationToken);
+ return new ValueTask(
+ (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask :
+ (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask :
+ Task.WhenAll(flushTask, writeTask));
+ }
+
+ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
+ {
+ Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
+ Debug.Assert(CanWrite, "_parent.CanWrite");
+ Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
+ Debug.Assert(_useAsyncIO, "WriteInternalCoreAsync doesn't work on synchronous file streams!");
+
+ // Create and store async stream class library specific data in the async result
+ FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, 0, source);
+ NativeOverlapped* intOverlapped = completionSource.Overlapped;
+
+ if (CanSeek)
+ {
+ // Make sure we set the length of the file appropriately.
+ long len = Length;
+
+ // Make sure we are writing to the position that we think we are
+ VerifyOSHandlePosition();
+
+ if (_filePosition + source.Length > len)
+ {
+ SetLengthCore(_filePosition + source.Length);
+ }
+
+ // Now set the position to read from in the NativeOverlapped struct
+ // For pipes, we should leave the offset fields set to 0.
+ intOverlapped->OffsetLow = (int)_filePosition;
+ intOverlapped->OffsetHigh = (int)(_filePosition >> 32);
+
+ // When using overlapped IO, the OS is not supposed to
+ // touch the file pointer location at all. We will adjust it
+ // ourselves. This isn't threadsafe.
+ SeekCore(_fileHandle, source.Length, SeekOrigin.Current);
+ }
+
+ int errorCode = 0;
+ // queue an async WriteFile operation and pass in a packed overlapped
+ int r = WriteFileNative(_fileHandle, source.Span, intOverlapped, out errorCode);
+
+ // WriteFile, the OS version, will return 0 on failure. But
+ // my WriteFileNative wrapper returns -1. My wrapper will return
+ // the following:
+ // On error, r==-1.
+ // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING
+ // On async requests that completed sequentially, r==0
+ // You will NEVER RELIABLY be able to get the number of bytes
+ // written back from this call when using overlapped IO! You must
+ // not pass in a non-null lpNumBytesWritten to WriteFile when using
+ // overlapped structures! This is ByDesign NT behavior.
+ if (r == -1)
+ {
+ // For pipes, when they are closed on the other side, they will come here.
+ if (errorCode == ERROR_NO_DATA)
+ {
+ // Not an error, but EOF. AsyncFSCallback will NOT be called.
+ // Completing TCS and return cached task allowing the GC to collect TCS.
+ completionSource.SetCompletedSynchronously(0);
+ return Task.CompletedTask;
+ }
+ else if (errorCode != ERROR_IO_PENDING)
+ {
+ if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere.
+ {
+ SeekCore(_fileHandle, 0, SeekOrigin.Current);
+ }
+
+ completionSource.ReleaseNativeResource();
+
+ if (errorCode == ERROR_HANDLE_EOF)
+ {
+ throw Error.GetEndOfFile();
+ }
+ else
+ {
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+ }
+ }
+ else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING
+ {
+ // Only once the IO is pending do we register for cancellation
+ completionSource.RegisterForCancellation(cancellationToken);
+ }
+ }
+ else
+ {
+ // Due to a workaround for a race condition in NT's ReadFile &
+ // WriteFile routines, we will always be returning 0 from WriteFileNative
+ // when we do async IO instead of the number of bytes written,
+ // irregardless of whether the operation completed
+ // synchronously or asynchronously. We absolutely must not
+ // set asyncResult._numBytes here, since will never have correct
+ // results.
+ }
+
+ return completionSource.Task;
+ }
+
+ // Windows API definitions, from winbase.h and others
+
+ private const int FILE_ATTRIBUTE_NORMAL = 0x00000080;
+ private const int FILE_ATTRIBUTE_ENCRYPTED = 0x00004000;
+ private const int FILE_FLAG_OVERLAPPED = 0x40000000;
+ internal const int GENERIC_READ = unchecked((int)0x80000000);
+ private const int GENERIC_WRITE = 0x40000000;
+
+ private const int FILE_BEGIN = 0;
+ private const int FILE_CURRENT = 1;
+ private const int FILE_END = 2;
+
+ // Error codes (not HRESULTS), from winerror.h
+ internal const int ERROR_BROKEN_PIPE = 109;
+ internal const int ERROR_NO_DATA = 232;
+ private const int ERROR_HANDLE_EOF = 38;
+ private const int ERROR_INVALID_PARAMETER = 87;
+ private const int ERROR_IO_PENDING = 997;
+
+ // __ConsoleStream also uses this code.
+ private unsafe int ReadFileNative(SafeFileHandle handle, Span<byte> bytes, NativeOverlapped* overlapped, out int errorCode)
+ {
+ Debug.Assert(handle != null, "handle != null");
+ Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to ReadFileNative.");
+
+ int r;
+ int numBytesRead = 0;
+
+ fixed (byte* p = &MemoryMarshal.GetReference(bytes))
+ {
+ r = _useAsyncIO ?
+ Interop.Kernel32.ReadFile(handle, p, bytes.Length, IntPtr.Zero, overlapped) :
+ Interop.Kernel32.ReadFile(handle, p, bytes.Length, out numBytesRead, IntPtr.Zero);
+ }
+
+ if (r == 0)
+ {
+ errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
+ return -1;
+ }
+ else
+ {
+ errorCode = 0;
+ return numBytesRead;
+ }
+ }
+
+ private unsafe int WriteFileNative(SafeFileHandle handle, ReadOnlySpan<byte> buffer, NativeOverlapped* overlapped, out int errorCode)
+ {
+ Debug.Assert(handle != null, "handle != null");
+ Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to WriteFileNative.");
+
+ int numBytesWritten = 0;
+ int r;
+
+ fixed (byte* p = &MemoryMarshal.GetReference(buffer))
+ {
+ r = _useAsyncIO ?
+ Interop.Kernel32.WriteFile(handle, p, buffer.Length, IntPtr.Zero, overlapped) :
+ Interop.Kernel32.WriteFile(handle, p, buffer.Length, out numBytesWritten, IntPtr.Zero);
+ }
+
+ if (r == 0)
+ {
+ errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
+ return -1;
+ }
+ else
+ {
+ errorCode = 0;
+ return numBytesWritten;
+ }
+ }
+
+ private int GetLastWin32ErrorAndDisposeHandleIfInvalid(bool throwIfInvalidHandle = false)
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+
+ // If ERROR_INVALID_HANDLE is returned, it doesn't suffice to set
+ // the handle as invalid; the handle must also be closed.
+ //
+ // Marking the handle as invalid but not closing the handle
+ // resulted in exceptions during finalization and locked column
+ // values (due to invalid but unclosed handle) in SQL Win32FileStream
+ // scenarios.
+ //
+ // A more mainstream scenario involves accessing a file on a
+ // network share. ERROR_INVALID_HANDLE may occur because the network
+ // connection was dropped and the server closed the handle. However,
+ // the client side handle is still open and even valid for certain
+ // operations.
+ //
+ // Note that _parent.Dispose doesn't throw so we don't need to special case.
+ // SetHandleAsInvalid only sets _closed field to true (without
+ // actually closing handle) so we don't need to call that as well.
+ if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ {
+ _fileHandle.Dispose();
+
+ if (throwIfInvalidHandle)
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+ }
+
+ return errorCode;
+ }
+
+ public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ // If we're in sync mode, just use the shared CopyToAsync implementation that does
+ // typical read/write looping. We also need to take this path if this is a derived
+ // instance from FileStream, as a derived type could have overridden ReadAsync, in which
+ // case our custom CopyToAsync implementation isn't necessarily correct.
+ if (!_useAsyncIO || GetType() != typeof(FileStream))
+ {
+ return base.CopyToAsync(destination, bufferSize, cancellationToken);
+ }
+
+ StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize);
+
+ // Bail early for cancellation if cancellation has been requested
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled<int>(cancellationToken);
+ }
+
+ // Fail if the file was closed
+ if (_fileHandle.IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+
+ // Do the async copy, with differing implementations based on whether the FileStream was opened as async or sync
+ Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
+ return AsyncModeCopyToAsync(destination, bufferSize, cancellationToken);
+ }
+
+ private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ Debug.Assert(_useAsyncIO, "This implementation is for async mode only");
+ Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
+ Debug.Assert(CanRead, "_parent.CanRead");
+
+ // Make sure any pending writes have been flushed before we do a read.
+ if (_writePos > 0)
+ {
+ await FlushWriteAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ // Typically CopyToAsync would be invoked as the only "read" on the stream, but it's possible some reading is
+ // done and then the CopyToAsync is issued. For that case, see if we have any data available in the buffer.
+ if (GetBuffer() != null)
+ {
+ int bufferedBytes = _readLength - _readPos;
+ if (bufferedBytes > 0)
+ {
+ await destination.WriteAsync(new ReadOnlyMemory<byte>(GetBuffer(), _readPos, bufferedBytes), cancellationToken).ConfigureAwait(false);
+ _readPos = _readLength = 0;
+ }
+ }
+
+ // For efficiency, we avoid creating a new task and associated state for each asynchronous read.
+ // Instead, we create a single reusable awaitable object that will be triggered when an await completes
+ // and reset before going again.
+ var readAwaitable = new AsyncCopyToAwaitable(this);
+
+ // Make sure we are reading from the position that we think we are.
+ // Only set the position in the awaitable if we can seek (e.g. not for pipes).
+ bool canSeek = CanSeek;
+ if (canSeek)
+ {
+ VerifyOSHandlePosition();
+ readAwaitable._position = _filePosition;
+ }
+
+ // Get the buffer to use for the copy operation, as the base CopyToAsync does. We don't try to use
+ // _buffer here, even if it's not null, as concurrent operations are allowed, and another operation may
+ // actually be using the buffer already. Plus, it'll be rare for _buffer to be non-null, as typically
+ // CopyToAsync is used as the only operation performed on the stream, and the buffer is lazily initialized.
+ // Further, typically the CopyToAsync buffer size will be larger than that used by the FileStream, such that
+ // we'd likely be unable to use it anyway. Instead, we rent the buffer from a pool.
+ byte[] copyBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
+
+ // Allocate an Overlapped we can use repeatedly for all operations
+ var awaitableOverlapped = new PreAllocatedOverlapped(AsyncCopyToAwaitable.s_callback, readAwaitable, copyBuffer);
+ var cancellationReg = default(CancellationTokenRegistration);
+ try
+ {
+ // Register for cancellation. We do this once for the whole copy operation, and just try to cancel
+ // whatever read operation may currently be in progress, if there is one. It's possible the cancellation
+ // request could come in between operations, in which case we flag that with explicit calls to ThrowIfCancellationRequested
+ // in the read/write copy loop.
+ if (cancellationToken.CanBeCanceled)
+ {
+ cancellationReg = cancellationToken.Register(s =>
+ {
+ var innerAwaitable = (AsyncCopyToAwaitable)s;
+ unsafe
+ {
+ lock (innerAwaitable.CancellationLock) // synchronize with cleanup of the overlapped
+ {
+ if (innerAwaitable._nativeOverlapped != null)
+ {
+ // Try to cancel the I/O. We ignore the return value, as cancellation is opportunistic and we
+ // don't want to fail the operation because we couldn't cancel it.
+ Interop.Kernel32.CancelIoEx(innerAwaitable._fileStream._fileHandle, innerAwaitable._nativeOverlapped);
+ }
+ }
+ }
+ }, readAwaitable);
+ }
+
+ // Repeatedly read from this FileStream and write the results to the destination stream.
+ while (true)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ readAwaitable.ResetForNextOperation();
+
+ try
+ {
+ bool synchronousSuccess;
+ int errorCode;
+ unsafe
+ {
+ // Allocate a native overlapped for our reusable overlapped, and set position to read based on the next
+ // desired address stored in the awaitable. (This position may be 0, if either we're at the beginning or
+ // if the stream isn't seekable.)
+ readAwaitable._nativeOverlapped = _fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(awaitableOverlapped);
+ if (canSeek)
+ {
+ readAwaitable._nativeOverlapped->OffsetLow = unchecked((int)readAwaitable._position);
+ readAwaitable._nativeOverlapped->OffsetHigh = (int)(readAwaitable._position >> 32);
+ }
+
+ // Kick off the read.
+ synchronousSuccess = ReadFileNative(_fileHandle, copyBuffer, readAwaitable._nativeOverlapped, out errorCode) >= 0;
+ }
+
+ // If the operation did not synchronously succeed, it either failed or initiated the asynchronous operation.
+ if (!synchronousSuccess)
+ {
+ switch (errorCode)
+ {
+ case ERROR_IO_PENDING:
+ // Async operation in progress.
+ break;
+ case ERROR_BROKEN_PIPE:
+ case ERROR_HANDLE_EOF:
+ // We're at or past the end of the file, and the overlapped callback
+ // won't be raised in these cases. Mark it as completed so that the await
+ // below will see it as such.
+ readAwaitable.MarkCompleted();
+ break;
+ default:
+ // Everything else is an error (and there won't be a callback).
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+ }
+ }
+
+ // Wait for the async operation (which may or may not have already completed), then throw if it failed.
+ await readAwaitable;
+ switch (readAwaitable._errorCode)
+ {
+ case 0: // success
+ Debug.Assert(readAwaitable._numBytes >= 0, $"Expected non-negative numBytes, got {readAwaitable._numBytes}");
+ break;
+ case ERROR_BROKEN_PIPE: // logically success with 0 bytes read (write end of pipe closed)
+ case ERROR_HANDLE_EOF: // logically success with 0 bytes read (read at end of file)
+ Debug.Assert(readAwaitable._numBytes == 0, $"Expected 0 bytes read, got {readAwaitable._numBytes}");
+ break;
+ case Interop.Errors.ERROR_OPERATION_ABORTED: // canceled
+ throw new OperationCanceledException(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true));
+ default: // error
+ throw Win32Marshal.GetExceptionForWin32Error((int)readAwaitable._errorCode, _path);
+ }
+
+ // Successful operation. If we got zero bytes, we're done: exit the read/write loop.
+ int numBytesRead = (int)readAwaitable._numBytes;
+ if (numBytesRead == 0)
+ {
+ break;
+ }
+
+ // Otherwise, update the read position for next time accordingly.
+ if (canSeek)
+ {
+ readAwaitable._position += numBytesRead;
+ }
+ }
+ finally
+ {
+ // Free the resources for this read operation
+ unsafe
+ {
+ NativeOverlapped* overlapped;
+ lock (readAwaitable.CancellationLock) // just an Exchange, but we need this to be synchronized with cancellation, so using the same lock
+ {
+ overlapped = readAwaitable._nativeOverlapped;
+ readAwaitable._nativeOverlapped = null;
+ }
+ if (overlapped != null)
+ {
+ _fileHandle.ThreadPoolBinding.FreeNativeOverlapped(overlapped);
+ }
+ }
+ }
+
+ // Write out the read data.
+ await destination.WriteAsync(new ReadOnlyMemory<byte>(copyBuffer, 0, (int)readAwaitable._numBytes), cancellationToken).ConfigureAwait(false);
+ }
+ }
+ finally
+ {
+ // Cleanup from the whole copy operation
+ cancellationReg.Dispose();
+ awaitableOverlapped.Dispose();
+
+ ArrayPool<byte>.Shared.Return(copyBuffer);
+
+ // Make sure the stream's current position reflects where we ended up
+ if (!_fileHandle.IsClosed && CanSeek)
+ {
+ SeekCore(_fileHandle, 0, SeekOrigin.End);
+ }
+ }
+ }
+
+ /// <summary>Used by CopyToAsync to enable awaiting the result of an overlapped I/O operation with minimal overhead.</summary>
+ private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion
+ {
+ /// <summary>Sentinel object used to indicate that the I/O operation has completed before being awaited.</summary>
+ private readonly static Action s_sentinel = () => { };
+ /// <summary>Cached delegate to IOCallback.</summary>
+ internal static readonly IOCompletionCallback s_callback = IOCallback;
+
+ /// <summary>The FileStream that owns this instance.</summary>
+ internal readonly FileStream _fileStream;
+
+ /// <summary>Tracked position representing the next location from which to read.</summary>
+ internal long _position;
+ /// <summary>The current native overlapped pointer. This changes for each operation.</summary>
+ internal NativeOverlapped* _nativeOverlapped;
+ /// <summary>
+ /// null if the operation is still in progress,
+ /// s_sentinel if the I/O operation completed before the await,
+ /// s_callback if it completed after the await yielded.
+ /// </summary>
+ internal Action _continuation;
+ /// <summary>Last error code from completed operation.</summary>
+ internal uint _errorCode;
+ /// <summary>Last number of read bytes from completed operation.</summary>
+ internal uint _numBytes;
+
+ /// <summary>Lock object used to protect cancellation-related access to _nativeOverlapped.</summary>
+ internal object CancellationLock => this;
+
+ /// <summary>Initialize the awaitable.</summary>
+ internal unsafe AsyncCopyToAwaitable(FileStream fileStream)
+ {
+ _fileStream = fileStream;
+ }
+
+ /// <summary>Reset state to prepare for the next read operation.</summary>
+ internal void ResetForNextOperation()
+ {
+ Debug.Assert(_position >= 0, $"Expected non-negative position, got {_position}");
+ _continuation = null;
+ _errorCode = 0;
+ _numBytes = 0;
+ }
+
+ /// <summary>Overlapped callback: store the results, then invoke the continuation delegate.</summary>
+ internal static unsafe void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP)
+ {
+ var awaitable = (AsyncCopyToAwaitable)ThreadPoolBoundHandle.GetNativeOverlappedState(pOVERLAP);
+
+ Debug.Assert(!ReferenceEquals(awaitable._continuation, s_sentinel), "Sentinel must not have already been set as the continuation");
+ awaitable._errorCode = errorCode;
+ awaitable._numBytes = numBytes;
+
+ (awaitable._continuation ?? Interlocked.CompareExchange(ref awaitable._continuation, s_sentinel, null))?.Invoke();
+ }
+
+ /// <summary>
+ /// Called when it's known that the I/O callback for an operation will not be invoked but we'll
+ /// still be awaiting the awaitable.
+ /// </summary>
+ internal void MarkCompleted()
+ {
+ Debug.Assert(_continuation == null, "Expected null continuation");
+ _continuation = s_sentinel;
+ }
+
+ public AsyncCopyToAwaitable GetAwaiter() => this;
+ public bool IsCompleted => ReferenceEquals(_continuation, s_sentinel);
+ public void GetResult() { }
+ public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation);
+ public void UnsafeOnCompleted(Action continuation)
+ {
+ if (ReferenceEquals(_continuation, s_sentinel) ||
+ Interlocked.CompareExchange(ref _continuation, continuation, null) != null)
+ {
+ Debug.Assert(ReferenceEquals(_continuation, s_sentinel), $"Expected continuation set to s_sentinel, got ${_continuation}");
+ Task.Run(continuation);
+ }
+ }
+ }
+
+ // Unlike Flush(), FlushAsync() always flushes to disk. This is intentional.
+ // Legend is that we chose not to flush the OS file buffers in Flush() in fear of
+ // perf problems with frequent, long running FlushFileBuffers() calls. But we don't
+ // have that problem with FlushAsync() because we will call FlushFileBuffers() in the background.
+ private Task FlushAsyncInternal(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ if (_fileHandle.IsClosed)
+ throw Error.GetFileNotOpen();
+
+ // The always synchronous data transfer between the OS and the internal buffer is intentional
+ // because this is needed to allow concurrent async IO requests. Concurrent data transfer
+ // between the OS and the internal buffer will result in race conditions. Since FlushWrite and
+ // FlushRead modify internal state of the stream and transfer data between the OS and the
+ // internal buffer, they cannot be truly async. We will, however, flush the OS file buffers
+ // asynchronously because it doesn't modify any internal state of the stream and is potentially
+ // a long running process.
+ try
+ {
+ FlushInternalBuffer();
+ }
+ catch (Exception e)
+ {
+ return Task.FromException(e);
+ }
+
+ if (CanWrite)
+ {
+ return Task.Factory.StartNew(
+ state => ((FileStream)state).FlushOSBuffer(),
+ this,
+ cancellationToken,
+ TaskCreationOptions.DenyChildAttach,
+ TaskScheduler.Default);
+ }
+ else
+ {
+ return Task.CompletedTask;
+ }
+ }
+
+ private void LockInternal(long position, long length)
+ {
+ int positionLow = unchecked((int)(position));
+ int positionHigh = unchecked((int)(position >> 32));
+ int lengthLow = unchecked((int)(length));
+ int lengthHigh = unchecked((int)(length >> 32));
+
+ if (!Interop.Kernel32.LockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh))
+ {
+ throw Win32Marshal.GetExceptionForLastWin32Error(_path);
+ }
+ }
+
+ private void UnlockInternal(long position, long length)
+ {
+ int positionLow = unchecked((int)(position));
+ int positionHigh = unchecked((int)(position >> 32));
+ int lengthLow = unchecked((int)(length));
+ int lengthHigh = unchecked((int)(length >> 32));
+
+ if (!Interop.Kernel32.UnlockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh))
+ {
+ throw Win32Marshal.GetExceptionForLastWin32Error(_path);
+ }
+ }
+
+ private SafeFileHandle ValidateFileHandle(SafeFileHandle fileHandle)
+ {
+ if (fileHandle.IsInvalid)
+ {
+ // Return a meaningful exception with the full path.
+
+ // NT5 oddity - when trying to open "C:\" as a Win32FileStream,
+ // we usually get ERROR_PATH_NOT_FOUND from the OS. We should
+ // probably be consistent w/ every other directory.
+ int errorCode = Marshal.GetLastWin32Error();
+
+ if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && _path.Length == PathInternal.GetRootLength(_path))
+ errorCode = Interop.Errors.ERROR_ACCESS_DENIED;
+
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+ }
+
+ fileHandle.IsAsync = _useAsyncIO;
+ return fileHandle;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.cs
new file mode 100644
index 0000000000..a6ad63c6eb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.cs
@@ -0,0 +1,881 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Security;
+
+namespace System.IO
+{
+ public partial class FileStream : Stream
+ {
+ private const FileShare DefaultShare = FileShare.Read;
+ private const bool DefaultIsAsync = false;
+ internal const int DefaultBufferSize = 4096;
+
+ private byte[] _buffer;
+ private int _bufferLength;
+ private readonly SafeFileHandle _fileHandle;
+
+ /// <summary>Whether the file is opened for reading, writing, or both.</summary>
+ private readonly FileAccess _access;
+
+ /// <summary>The path to the opened file.</summary>
+ private readonly string _path;
+
+ /// <summary>The next available byte to be read from the _buffer.</summary>
+ private int _readPos;
+
+ /// <summary>The number of valid bytes in _buffer.</summary>
+ private int _readLength;
+
+ /// <summary>The next location in which a write should occur to the buffer.</summary>
+ private int _writePos;
+
+ /// <summary>
+ /// Whether asynchronous read/write/flush operations should be performed using async I/O.
+ /// On Windows FileOptions.Asynchronous controls how the file handle is configured,
+ /// and then as a result how operations are issued against that file handle. On Unix,
+ /// there isn't any distinction around how file descriptors are created for async vs
+ /// sync, but we still differentiate how the operations are issued in order to provide
+ /// similar behavioral semantics and performance characteristics as on Windows. On
+ /// Windows, if non-async, async read/write requests just delegate to the base stream,
+ /// and no attempt is made to synchronize between sync and async operations on the stream;
+ /// if async, then async read/write requests are implemented specially, and sync read/write
+ /// requests are coordinated with async ones by implementing the sync ones over the async
+ /// ones. On Unix, we do something similar. If non-async, async read/write requests just
+ /// delegate to the base stream, and no attempt is made to synchronize. If async, we use
+ /// a semaphore to coordinate both sync and async operations.
+ /// </summary>
+ private readonly bool _useAsyncIO;
+
+ /// <summary>cached task for read ops that complete synchronously</summary>
+ private Task<int> _lastSynchronouslyCompletedTask = null;
+
+ /// <summary>
+ /// Currently cached position in the stream. This should always mirror the underlying file's actual position,
+ /// and should only ever be out of sync if another stream with access to this same file manipulates it, at which
+ /// point we attempt to error out.
+ /// </summary>
+ private long _filePosition;
+
+ /// <summary>Whether the file stream's handle has been exposed.</summary>
+ private bool _exposedHandle;
+
+ [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public FileStream(IntPtr handle, FileAccess access)
+ : this(handle, access, true, DefaultBufferSize, false)
+ {
+ }
+
+ [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public FileStream(IntPtr handle, FileAccess access, bool ownsHandle)
+ : this(handle, access, ownsHandle, DefaultBufferSize, false)
+ {
+ }
+
+ [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
+ : this(handle, access, ownsHandle, bufferSize, false)
+ {
+ }
+
+ [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
+ {
+ SafeFileHandle safeHandle = new SafeFileHandle(handle, ownsHandle: ownsHandle);
+ try
+ {
+ ValidateAndInitFromHandle(safeHandle, access, bufferSize, isAsync);
+ }
+ catch
+ {
+ // We don't want to take ownership of closing passed in handles
+ // *unless* the constructor completes successfully.
+ GC.SuppressFinalize(safeHandle);
+
+ // This would also prevent Close from being called, but is unnecessary
+ // as we've removed the object from the finalizer queue.
+ //
+ // safeHandle.SetHandleAsInvalid();
+ throw;
+ }
+
+ // Note: Cleaner to set the following fields in ValidateAndInitFromHandle,
+ // but we can't as they're readonly.
+ _access = access;
+ _useAsyncIO = isAsync;
+
+ // As the handle was passed in, we must set the handle field at the very end to
+ // avoid the finalizer closing the handle when we throw errors.
+ _fileHandle = safeHandle;
+ }
+
+ public FileStream(SafeFileHandle handle, FileAccess access)
+ : this(handle, access, DefaultBufferSize)
+ {
+ }
+
+ public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
+ : this(handle, access, bufferSize, GetDefaultIsAsync(handle))
+ {
+ }
+
+ private void ValidateAndInitFromHandle(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
+ {
+ if (handle.IsInvalid)
+ throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle));
+
+ if (access < FileAccess.Read || access > FileAccess.ReadWrite)
+ throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum);
+ if (bufferSize <= 0)
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+
+ if (handle.IsClosed)
+ throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
+ if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.Value)
+ throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle));
+
+ _exposedHandle = true;
+ _bufferLength = bufferSize;
+
+ InitFromHandle(handle, access, isAsync);
+ }
+
+ public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
+ {
+ ValidateAndInitFromHandle(handle, access, bufferSize, isAsync);
+
+ // Note: Cleaner to set the following fields in ValidateAndInitFromHandle,
+ // but we can't as they're readonly.
+ _access = access;
+ _useAsyncIO = isAsync;
+
+ // As the handle was passed in, we must set the handle field at the very end to
+ // avoid the finalizer closing the handle when we throw errors.
+ _fileHandle = handle;
+ }
+
+ public FileStream(string path, FileMode mode) :
+ this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), DefaultShare, DefaultBufferSize, DefaultIsAsync)
+ { }
+
+ public FileStream(string path, FileMode mode, FileAccess access) :
+ this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync)
+ { }
+
+ public FileStream(string path, FileMode mode, FileAccess access, FileShare share) :
+ this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync)
+ { }
+
+ public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) :
+ this(path, mode, access, share, bufferSize, DefaultIsAsync)
+ { }
+
+ public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) :
+ this(path, mode, access, share, bufferSize, useAsync ? FileOptions.Asynchronous : FileOptions.None)
+ { }
+
+ public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path);
+ if (path.Length == 0)
+ throw new ArgumentException(SR.Argument_EmptyPath, nameof(path));
+
+ // don't include inheritable in our bounds check for share
+ FileShare tempshare = share & ~FileShare.Inheritable;
+ string badArg = null;
+
+ if (mode < FileMode.CreateNew || mode > FileMode.Append)
+ badArg = nameof(mode);
+ else if (access < FileAccess.Read || access > FileAccess.ReadWrite)
+ badArg = nameof(access);
+ else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete))
+ badArg = nameof(share);
+
+ if (badArg != null)
+ throw new ArgumentOutOfRangeException(badArg, SR.ArgumentOutOfRange_Enum);
+
+ // NOTE: any change to FileOptions enum needs to be matched here in the error validation
+ if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0)
+ throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_Enum);
+
+ if (bufferSize <= 0)
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+
+ // Write access validation
+ if ((access & FileAccess.Write) == 0)
+ {
+ if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append)
+ {
+ // No write access, mode and access disagree but flag access since mode comes first
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, mode, access), nameof(access));
+ }
+ }
+
+ if ((access & FileAccess.Read) != 0 && mode == FileMode.Append)
+ throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access));
+
+ string fullPath = Path.GetFullPath(path);
+
+ _path = fullPath;
+ _access = access;
+ _bufferLength = bufferSize;
+
+ if ((options & FileOptions.Asynchronous) != 0)
+ _useAsyncIO = true;
+
+ _fileHandle = OpenHandle(mode, share, options);
+
+ try
+ {
+ Init(mode, share);
+ }
+ catch
+ {
+ // If anything goes wrong while setting up the stream, make sure we deterministically dispose
+ // of the opened handle.
+ _fileHandle.Dispose();
+ _fileHandle = null;
+ throw;
+ }
+ }
+
+ private static bool GetDefaultIsAsync(SafeFileHandle handle)
+ {
+ // This will eventually get more complicated as we can actually check the underlying handle type on Windows
+ return handle.IsAsync.HasValue ? handle.IsAsync.Value : false;
+ }
+
+ [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public virtual IntPtr Handle { get { return SafeFileHandle.DangerousGetHandle(); } }
+
+ public virtual void Lock(long position, long length)
+ {
+ if (position < 0 || length < 0)
+ {
+ throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (_fileHandle.IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+
+ LockInternal(position, length);
+ }
+
+ public virtual void Unlock(long position, long length)
+ {
+ if (position < 0 || length < 0)
+ {
+ throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (_fileHandle.IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+
+ UnlockInternal(position, length);
+ }
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Flush() which a subclass might have overridden. To be safe
+ // we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Flush) when we are not sure.
+ if (GetType() != typeof(FileStream))
+ return base.FlushAsync(cancellationToken);
+
+ return FlushAsyncInternal(cancellationToken);
+ }
+
+ public override int Read(byte[] array, int offset, int count)
+ {
+ ValidateReadWriteArgs(array, offset, count);
+ return _useAsyncIO ?
+ ReadAsyncTask(array, offset, count, CancellationToken.None).GetAwaiter().GetResult() :
+ ReadSpan(new Span<byte>(array, offset, count));
+ }
+
+ public override int Read(Span<byte> buffer)
+ {
+ if (GetType() == typeof(FileStream) && !_useAsyncIO)
+ {
+ if (_fileHandle.IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+ return ReadSpan(buffer);
+ }
+ else
+ {
+ // This type is derived from FileStream and/or the stream is in async mode. If this is a
+ // derived type, it may have overridden Read(byte[], int, int) prior to this Read(Span<byte>)
+ // overload being introduced. In that case, this Read(Span<byte>) overload should use the behavior
+ // of Read(byte[],int,int) overload. Or if the stream is in async mode, we can't call the
+ // synchronous ReadSpan, so we similarly call the base Read, which will turn delegate to
+ // Read(byte[],int,int), which will do the right thing if we're in async mode.
+ return base.Read(buffer);
+ }
+ }
+
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Read() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Read/ReadAsync) when we are not sure.
+ // Similarly, if we weren't opened for asynchronous I/O, call to the base implementation so that
+ // Read is invoked asynchronously.
+ if (GetType() != typeof(FileStream) || !_useAsyncIO)
+ return base.ReadAsync(buffer, offset, count, cancellationToken);
+
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled<int>(cancellationToken);
+
+ if (IsClosed)
+ throw Error.GetFileNotOpen();
+
+ return ReadAsyncTask(buffer, offset, count, cancellationToken);
+ }
+
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (!_useAsyncIO || GetType() != typeof(FileStream))
+ {
+ // If we're not using async I/O, delegate to the base, which will queue a call to Read.
+ // Or if this isn't a concrete FileStream, a derived type may have overridden ReadAsync(byte[],...),
+ // which was introduced first, so delegate to the base which will delegate to that.
+ return base.ReadAsync(buffer, cancellationToken);
+ }
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
+ }
+
+ if (IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+
+ Task<int> t = ReadAsyncInternal(buffer, cancellationToken, out int synchronousResult);
+ return t != null ?
+ new ValueTask<int>(t) :
+ new ValueTask<int>(synchronousResult);
+ }
+
+ private Task<int> ReadAsyncTask(byte[] array, int offset, int count, CancellationToken cancellationToken)
+ {
+ Task<int> t = ReadAsyncInternal(new Memory<byte>(array, offset, count), cancellationToken, out int synchronousResult);
+
+ if (t == null)
+ {
+ t = _lastSynchronouslyCompletedTask;
+ Debug.Assert(t == null || t.IsCompletedSuccessfully, "Cached task should have completed successfully");
+
+ if (t == null || t.Result != synchronousResult)
+ {
+ _lastSynchronouslyCompletedTask = t = Task.FromResult(synchronousResult);
+ }
+ }
+
+ return t;
+ }
+
+ public override void Write(byte[] array, int offset, int count)
+ {
+ ValidateReadWriteArgs(array, offset, count);
+ if (_useAsyncIO)
+ {
+ WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, count), CancellationToken.None).GetAwaiter().GetResult();
+ }
+ else
+ {
+ WriteSpan(new ReadOnlySpan<byte>(array, offset, count));
+ }
+ }
+
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ if (GetType() == typeof(FileStream) && !_useAsyncIO)
+ {
+ if (_fileHandle.IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+ WriteSpan(buffer);
+ }
+ else
+ {
+ // This type is derived from FileStream and/or the stream is in async mode. If this is a
+ // derived type, it may have overridden Write(byte[], int, int) prior to this Write(ReadOnlySpan<byte>)
+ // overload being introduced. In that case, this Write(ReadOnlySpan<byte>) overload should use the behavior
+ // of Write(byte[],int,int) overload. Or if the stream is in async mode, we can't call the
+ // synchronous WriteSpan, so we similarly call the base Write, which will turn delegate to
+ // Write(byte[],int,int), which will do the right thing if we're in async mode.
+ base.Write(buffer);
+ }
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Write() or WriteAsync() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Write/WriteAsync) when we are not sure.
+ if (!_useAsyncIO || GetType() != typeof(FileStream))
+ return base.WriteAsync(buffer, offset, count, cancellationToken);
+
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ if (IsClosed)
+ throw Error.GetFileNotOpen();
+
+ return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
+ }
+
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (!_useAsyncIO || GetType() != typeof(FileStream))
+ {
+ // If we're not using async I/O, delegate to the base, which will queue a call to Write.
+ // Or if this isn't a concrete FileStream, a derived type may have overridden WriteAsync(byte[],...),
+ // which was introduced first, so delegate to the base which will delegate to that.
+ return base.WriteAsync(buffer, cancellationToken);
+ }
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask(Task.FromCanceled<int>(cancellationToken));
+ }
+
+ if (IsClosed)
+ {
+ throw Error.GetFileNotOpen();
+ }
+
+ return WriteAsyncInternal(buffer, cancellationToken);
+ }
+
+ /// <summary>
+ /// Clears buffers for this stream and causes any buffered data to be written to the file.
+ /// </summary>
+ public override void Flush()
+ {
+ // Make sure that we call through the public virtual API
+ Flush(flushToDisk: false);
+ }
+
+ /// <summary>
+ /// Clears buffers for this stream, and if <param name="flushToDisk"/> is true,
+ /// causes any buffered data to be written to the file.
+ /// </summary>
+ public virtual void Flush(bool flushToDisk)
+ {
+ if (IsClosed) throw Error.GetFileNotOpen();
+
+ FlushInternalBuffer();
+
+ if (flushToDisk && CanWrite)
+ {
+ FlushOSBuffer();
+ }
+ }
+
+ /// <summary>Gets a value indicating whether the current stream supports reading.</summary>
+ public override bool CanRead
+ {
+ get { return !_fileHandle.IsClosed && (_access & FileAccess.Read) != 0; }
+ }
+
+ /// <summary>Gets a value indicating whether the current stream supports writing.</summary>
+ public override bool CanWrite
+ {
+ get { return !_fileHandle.IsClosed && (_access & FileAccess.Write) != 0; }
+ }
+
+ /// <summary>Validates arguments to Read and Write and throws resulting exceptions.</summary>
+ /// <param name="array">The buffer to read from or write to.</param>
+ /// <param name="offset">The zero-based offset into the array.</param>
+ /// <param name="count">The maximum number of bytes to read or write.</param>
+ private void ValidateReadWriteArgs(byte[] array, int offset, int count)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
+ if (_fileHandle.IsClosed)
+ throw Error.GetFileNotOpen();
+ }
+
+ /// <summary>Sets the length of this stream to the given value.</summary>
+ /// <param name="value">The new length of the stream.</param>
+ public override void SetLength(long value)
+ {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (_fileHandle.IsClosed)
+ throw Error.GetFileNotOpen();
+ if (!CanSeek)
+ throw Error.GetSeekNotSupported();
+ if (!CanWrite)
+ throw Error.GetWriteNotSupported();
+
+ SetLengthInternal(value);
+ }
+
+ public virtual SafeFileHandle SafeFileHandle
+ {
+ get
+ {
+ Flush();
+ _exposedHandle = true;
+ return _fileHandle;
+ }
+ }
+
+ /// <summary>Gets the path that was passed to the constructor.</summary>
+ public virtual string Name { get { return _path ?? SR.IO_UnknownFileName; } }
+
+ /// <summary>Gets a value indicating whether the stream was opened for I/O to be performed synchronously or asynchronously.</summary>
+ public virtual bool IsAsync
+ {
+ get { return _useAsyncIO; }
+ }
+
+ /// <summary>Gets the length of the stream in bytes.</summary>
+ public override long Length
+ {
+ get
+ {
+ if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
+ if (!CanSeek) throw Error.GetSeekNotSupported();
+ return GetLengthInternal();
+ }
+ }
+
+ /// <summary>
+ /// Verify that the actual position of the OS's handle equals what we expect it to.
+ /// This will fail if someone else moved the UnixFileStream's handle or if
+ /// our position updating code is incorrect.
+ /// </summary>
+ private void VerifyOSHandlePosition()
+ {
+ bool verifyPosition = _exposedHandle; // in release, only verify if we've given out the handle such that someone else could be manipulating it
+#if DEBUG
+ verifyPosition = true; // in debug, always make sure our position matches what the OS says it should be
+#endif
+ if (verifyPosition && CanSeek)
+ {
+ long oldPos = _filePosition; // SeekCore will override the current _position, so save it now
+ long curPos = SeekCore(_fileHandle, 0, SeekOrigin.Current);
+ if (oldPos != curPos)
+ {
+ // For reads, this is non-fatal but we still could have returned corrupted
+ // data in some cases, so discard the internal buffer. For writes,
+ // this is a problem; discard the buffer and error out.
+ _readPos = _readLength = 0;
+ if (_writePos > 0)
+ {
+ _writePos = 0;
+ throw new IOException(SR.IO_FileStreamHandlePosition);
+ }
+ }
+ }
+ }
+
+ /// <summary>Verifies that state relating to the read/write buffer is consistent.</summary>
+ [Conditional("DEBUG")]
+ private void AssertBufferInvariants()
+ {
+ // Read buffer values must be in range: 0 <= _bufferReadPos <= _bufferReadLength <= _bufferLength
+ Debug.Assert(0 <= _readPos && _readPos <= _readLength && _readLength <= _bufferLength);
+
+ // Write buffer values must be in range: 0 <= _bufferWritePos <= _bufferLength
+ Debug.Assert(0 <= _writePos && _writePos <= _bufferLength);
+
+ // Read buffering and write buffering can't both be active
+ Debug.Assert((_readPos == 0 && _readLength == 0) || _writePos == 0);
+ }
+
+ /// <summary>Validates that we're ready to read from the stream.</summary>
+ private void PrepareForReading()
+ {
+ if (_fileHandle.IsClosed)
+ throw Error.GetFileNotOpen();
+ if (_readLength == 0 && !CanRead)
+ throw Error.GetReadNotSupported();
+
+ AssertBufferInvariants();
+ }
+
+ /// <summary>Gets or sets the position within the current stream</summary>
+ public override long Position
+ {
+ get
+ {
+ if (_fileHandle.IsClosed)
+ throw Error.GetFileNotOpen();
+
+ if (!CanSeek)
+ throw Error.GetSeekNotSupported();
+
+ AssertBufferInvariants();
+ VerifyOSHandlePosition();
+
+ // We may have read data into our buffer from the handle, such that the handle position
+ // is artificially further along than the consumer's view of the stream's position.
+ // Thus, when reading, our position is really starting from the handle position negatively
+ // offset by the number of bytes in the buffer and positively offset by the number of
+ // bytes into that buffer we've read. When writing, both the read length and position
+ // must be zero, and our position is just the handle position offset positive by how many
+ // bytes we've written into the buffer.
+ return (_filePosition - _readLength) + _readPos + _writePos;
+ }
+ set
+ {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ Seek(value, SeekOrigin.Begin);
+ }
+ }
+
+ internal virtual bool IsClosed => _fileHandle.IsClosed;
+
+ private static bool IsIoRelatedException(Exception e) =>
+ // These all derive from IOException
+ // DirectoryNotFoundException
+ // DriveNotFoundException
+ // EndOfStreamException
+ // FileLoadException
+ // FileNotFoundException
+ // PathTooLongException
+ // PipeException
+ e is IOException ||
+ // Note that SecurityException is only thrown on runtimes that support CAS
+ // e is SecurityException ||
+ e is UnauthorizedAccessException ||
+ e is NotSupportedException ||
+ (e is ArgumentException && !(e is ArgumentNullException));
+
+ /// <summary>
+ /// Gets the array used for buffering reading and writing.
+ /// If the array hasn't been allocated, this will lazily allocate it.
+ /// </summary>
+ /// <returns>The buffer.</returns>
+ private byte[] GetBuffer()
+ {
+ Debug.Assert(_buffer == null || _buffer.Length == _bufferLength);
+ if (_buffer == null)
+ {
+ _buffer = new byte[_bufferLength];
+ OnBufferAllocated();
+ }
+
+ return _buffer;
+ }
+
+ partial void OnBufferAllocated();
+
+ /// <summary>
+ /// Flushes the internal read/write buffer for this stream. If write data has been buffered,
+ /// that data is written out to the underlying file. Or if data has been buffered for
+ /// reading from the stream, the data is dumped and our position in the underlying file
+ /// is rewound as necessary. This does not flush the OS buffer.
+ /// </summary>
+ private void FlushInternalBuffer()
+ {
+ AssertBufferInvariants();
+ if (_writePos > 0)
+ {
+ FlushWriteBuffer();
+ }
+ else if (_readPos < _readLength && CanSeek)
+ {
+ FlushReadBuffer();
+ }
+ }
+
+ /// <summary>Dumps any read data in the buffer and rewinds our position in the stream, accordingly, as necessary.</summary>
+ private void FlushReadBuffer()
+ {
+ // Reading is done by blocks from the file, but someone could read
+ // 1 byte from the buffer then write. At that point, the OS's file
+ // pointer is out of sync with the stream's position. All write
+ // functions should call this function to preserve the position in the file.
+
+ AssertBufferInvariants();
+ Debug.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushReadBuffer!");
+
+ int rewind = _readPos - _readLength;
+ if (rewind != 0)
+ {
+ Debug.Assert(CanSeek, "FileStream will lose buffered read data now.");
+ SeekCore(_fileHandle, rewind, SeekOrigin.Current);
+ }
+ _readPos = _readLength = 0;
+ }
+
+ /// <summary>
+ /// Reads a byte from the file stream. Returns the byte cast to an int
+ /// or -1 if reading from the end of the stream.
+ /// </summary>
+ public override int ReadByte()
+ {
+ PrepareForReading();
+
+ byte[] buffer = GetBuffer();
+ if (_readPos == _readLength)
+ {
+ FlushWriteBuffer();
+ _readLength = FillReadBufferForReadByte();
+ _readPos = 0;
+ if (_readLength == 0)
+ {
+ return -1;
+ }
+ }
+
+ return buffer[_readPos++];
+ }
+
+ /// <summary>
+ /// Writes a byte to the current position in the stream and advances the position
+ /// within the stream by one byte.
+ /// </summary>
+ /// <param name="value">The byte to write to the stream.</param>
+ public override void WriteByte(byte value)
+ {
+ PrepareForWriting();
+
+ // Flush the write buffer if it's full
+ if (_writePos == _bufferLength)
+ FlushWriteBufferForWriteByte();
+
+ // We now have space in the buffer. Store the byte.
+ GetBuffer()[_writePos++] = value;
+ }
+
+ /// <summary>
+ /// Validates that we're ready to write to the stream,
+ /// including flushing a read buffer if necessary.
+ /// </summary>
+ private void PrepareForWriting()
+ {
+ if (_fileHandle.IsClosed)
+ throw Error.GetFileNotOpen();
+
+ // Make sure we're good to write. We only need to do this if there's nothing already
+ // in our write buffer, since if there is something in the buffer, we've already done
+ // this checking and flushing.
+ if (_writePos == 0)
+ {
+ if (!CanWrite) throw Error.GetWriteNotSupported();
+ FlushReadBuffer();
+ Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
+ }
+ }
+
+ ~FileStream()
+ {
+ // Preserved for compatibility since FileStream has defined a
+ // finalizer in past releases and derived classes may depend
+ // on Dispose(false) call.
+ Dispose(false);
+ }
+
+ public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback callback, object state)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (numBytes < 0)
+ throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - offset < numBytes)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
+ if (!CanRead) throw new NotSupportedException(SR.NotSupported_UnreadableStream);
+
+ if (!IsAsync)
+ return base.BeginRead(array, offset, numBytes, callback, state);
+ else
+ return TaskToApm.Begin(ReadAsyncTask(array, offset, numBytes, CancellationToken.None), callback, state);
+ }
+
+ public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback callback, object state)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (numBytes < 0)
+ throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - offset < numBytes)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
+ if (!CanWrite) throw new NotSupportedException(SR.NotSupported_UnwritableStream);
+
+ if (!IsAsync)
+ return base.BeginWrite(array, offset, numBytes, callback, state);
+ else
+ return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, numBytes), CancellationToken.None).AsTask(), callback, state);
+ }
+
+ public override int EndRead(IAsyncResult asyncResult)
+ {
+ if (asyncResult == null)
+ throw new ArgumentNullException(nameof(asyncResult));
+
+ if (!IsAsync)
+ return base.EndRead(asyncResult);
+ else
+ return TaskToApm.End<int>(asyncResult);
+ }
+
+ public override void EndWrite(IAsyncResult asyncResult)
+ {
+ if (asyncResult == null)
+ throw new ArgumentNullException(nameof(asyncResult));
+
+ if (!IsAsync)
+ base.EndWrite(asyncResult);
+ else
+ TaskToApm.End(asyncResult);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs b/src/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs
new file mode 100644
index 0000000000..62ace0918d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs
@@ -0,0 +1,259 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ public partial class FileStream : Stream
+ {
+ // This is an internal object extending TaskCompletionSource with fields
+ // for all of the relevant data necessary to complete the IO operation.
+ // This is used by IOCallback and all of the async methods.
+ private unsafe class FileStreamCompletionSource : TaskCompletionSource<int>
+ {
+ private const long NoResult = 0;
+ private const long ResultSuccess = (long)1 << 32;
+ private const long ResultError = (long)2 << 32;
+ private const long RegisteringCancellation = (long)4 << 32;
+ private const long CompletedCallback = (long)8 << 32;
+ private const ulong ResultMask = ((ulong)uint.MaxValue) << 32;
+
+ private static Action<object> s_cancelCallback;
+
+ private readonly FileStream _stream;
+ private readonly int _numBufferedBytes;
+ private CancellationTokenRegistration _cancellationRegistration;
+#if DEBUG
+ private bool _cancellationHasBeenRegistered;
+#endif
+ private NativeOverlapped* _overlapped; // Overlapped class responsible for operations in progress when an appdomain unload occurs
+ private long _result; // Using long since this needs to be used in Interlocked APIs
+
+ // Using RunContinuationsAsynchronously for compat reasons (old API used Task.Factory.StartNew for continuations)
+ protected FileStreamCompletionSource(FileStream stream, int numBufferedBytes, byte[] bytes)
+ : base(TaskCreationOptions.RunContinuationsAsynchronously)
+ {
+ _numBufferedBytes = numBufferedBytes;
+ _stream = stream;
+ _result = NoResult;
+
+ // Create the native overlapped. We try to use the preallocated overlapped if possible: it's possible if the byte
+ // buffer is null (there's nothing to pin) or the same one that's associated with the preallocated overlapped (and
+ // thus is already pinned) and if no one else is currently using the preallocated overlapped. This is the fast-path
+ // for cases where the user-provided buffer is smaller than the FileStream's buffer (such that the FileStream's
+ // buffer is used) and where operations on the FileStream are not being performed concurrently.
+ Debug.Assert((bytes == null || ReferenceEquals(bytes, _stream._buffer)));
+
+ // The _preallocatedOverlapped is null if the internal buffer was never created, so we check for
+ // a non-null bytes before using the stream's _preallocatedOverlapped
+ _overlapped = bytes != null && _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ?
+ _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(_stream._preallocatedOverlapped) :
+ _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(s_ioCallback, this, bytes);
+ Debug.Assert(_overlapped != null, "AllocateNativeOverlapped returned null");
+ }
+
+ internal NativeOverlapped* Overlapped
+ {
+ get { return _overlapped; }
+ }
+
+ public void SetCompletedSynchronously(int numBytes)
+ {
+ ReleaseNativeResource();
+ TrySetResult(numBytes + _numBufferedBytes);
+ }
+
+ public void RegisterForCancellation(CancellationToken cancellationToken)
+ {
+#if DEBUG
+ Debug.Assert(cancellationToken.CanBeCanceled);
+ Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice");
+ _cancellationHasBeenRegistered = true;
+#endif
+
+ // Quick check to make sure the IO hasn't completed
+ if (_overlapped != null)
+ {
+ var cancelCallback = s_cancelCallback;
+ if (cancelCallback == null) s_cancelCallback = cancelCallback = Cancel;
+
+ // Register the cancellation only if the IO hasn't completed
+ long packedResult = Interlocked.CompareExchange(ref _result, RegisteringCancellation, NoResult);
+ if (packedResult == NoResult)
+ {
+ _cancellationRegistration = cancellationToken.Register(cancelCallback, this);
+
+ // Switch the result, just in case IO completed while we were setting the registration
+ packedResult = Interlocked.Exchange(ref _result, NoResult);
+ }
+ else if (packedResult != CompletedCallback)
+ {
+ // Failed to set the result, IO is in the process of completing
+ // Attempt to take the packed result
+ packedResult = Interlocked.Exchange(ref _result, NoResult);
+ }
+
+ // If we have a callback that needs to be completed
+ if ((packedResult != NoResult) && (packedResult != CompletedCallback) && (packedResult != RegisteringCancellation))
+ {
+ CompleteCallback((ulong)packedResult);
+ }
+ }
+ }
+
+ internal virtual void ReleaseNativeResource()
+ {
+ // Ensure that cancellation has been completed and cleaned up.
+ _cancellationRegistration.Dispose();
+
+ // Free the overlapped.
+ // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory
+ // (this is why we disposed the registration above).
+ if (_overlapped != null)
+ {
+ _stream._fileHandle.ThreadPoolBinding.FreeNativeOverlapped(_overlapped);
+ _overlapped = null;
+ }
+
+ // Ensure we're no longer set as the current completion source (we may not have been to begin with).
+ // Only one operation at a time is eligible to use the preallocated overlapped,
+ _stream.CompareExchangeCurrentOverlappedOwner(null, this);
+ }
+
+ // When doing IO asynchronously (i.e. _isAsync==true), this callback is
+ // called by a free thread in the threadpool when the IO operation
+ // completes.
+ internal static unsafe void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
+ {
+ // Extract the completion source from the overlapped. The state in the overlapped
+ // will either be a Win32FileStream (in the case where the preallocated overlapped was used),
+ // in which case the operation being completed is its _currentOverlappedOwner, or it'll
+ // be directly the FileStreamCompletion that's completing (in the case where the preallocated
+ // overlapped was already in use by another operation).
+ object state = ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped);
+ FileStream fs = state as FileStream;
+ FileStreamCompletionSource completionSource = fs != null ?
+ fs._currentOverlappedOwner :
+ (FileStreamCompletionSource)state;
+ Debug.Assert(completionSource._overlapped == pOverlapped, "Overlaps don't match");
+
+ // Handle reading from & writing to closed pipes. While I'm not sure
+ // this is entirely necessary anymore, maybe it's possible for
+ // an async read on a pipe to be issued and then the pipe is closed,
+ // returning this error. This may very well be necessary.
+ ulong packedResult;
+ if (errorCode != 0 && errorCode != ERROR_BROKEN_PIPE && errorCode != ERROR_NO_DATA)
+ {
+ packedResult = ((ulong)ResultError | errorCode);
+ }
+ else
+ {
+ packedResult = ((ulong)ResultSuccess | numBytes);
+ }
+
+ // Stow the result so that other threads can observe it
+ // And, if no other thread is registering cancellation, continue
+ if (NoResult == Interlocked.Exchange(ref completionSource._result, (long)packedResult))
+ {
+ // Successfully set the state, attempt to take back the callback
+ if (Interlocked.Exchange(ref completionSource._result, CompletedCallback) != NoResult)
+ {
+ // Successfully got the callback, finish the callback
+ completionSource.CompleteCallback(packedResult);
+ }
+ // else: Some other thread stole the result, so now it is responsible to finish the callback
+ }
+ // else: Some other thread is registering a cancellation, so it *must* finish the callback
+ }
+
+ private void CompleteCallback(ulong packedResult)
+ {
+ // Free up the native resource and cancellation registration
+ CancellationToken cancellationToken = _cancellationRegistration.Token; // access before disposing registration
+ ReleaseNativeResource();
+
+ // Unpack the result and send it to the user
+ long result = (long)(packedResult & ResultMask);
+ if (result == ResultError)
+ {
+ int errorCode = unchecked((int)(packedResult & uint.MaxValue));
+ if (errorCode == Interop.Errors.ERROR_OPERATION_ABORTED)
+ {
+ TrySetCanceled(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true));
+ }
+ else
+ {
+ TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode));
+ }
+ }
+ else
+ {
+ Debug.Assert(result == ResultSuccess, "Unknown result");
+ TrySetResult((int)(packedResult & uint.MaxValue) + _numBufferedBytes);
+ }
+ }
+
+ private static void Cancel(object state)
+ {
+ // WARNING: This may potentially be called under a lock (during cancellation registration)
+
+ FileStreamCompletionSource completionSource = state as FileStreamCompletionSource;
+ Debug.Assert(completionSource != null, "Unknown state passed to cancellation");
+ Debug.Assert(completionSource._overlapped != null && !completionSource.Task.IsCompleted, "IO should not have completed yet");
+
+ // If the handle is still valid, attempt to cancel the IO
+ if (!completionSource._stream._fileHandle.IsInvalid &&
+ !Interop.Kernel32.CancelIoEx(completionSource._stream._fileHandle, completionSource._overlapped))
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+
+ // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel.
+ // This probably means that the IO operation has completed.
+ if (errorCode != Interop.Errors.ERROR_NOT_FOUND)
+ {
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+ }
+ }
+ }
+
+ public static FileStreamCompletionSource Create(FileStream stream, int numBufferedBytesRead, ReadOnlyMemory<byte> memory)
+ {
+ // If the memory passed in is the stream's internal buffer, we can use the base FileStreamCompletionSource,
+ // which has a PreAllocatedOverlapped with the memory already pinned. Otherwise, we use the derived
+ // MemoryFileStreamCompletionSource, which Retains the memory, which will result in less pinning in the case
+ // where the underlying memory is backed by pre-pinned buffers.
+ return MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> buffer) && ReferenceEquals(buffer.Array, stream._buffer) ?
+ new FileStreamCompletionSource(stream, numBufferedBytesRead, buffer.Array) :
+ new MemoryFileStreamCompletionSource(stream, numBufferedBytesRead, memory);
+ }
+ }
+
+ /// <summary>
+ /// Extends <see cref="FileStreamCompletionSource"/> with to support disposing of a
+ /// <see cref="MemoryHandle"/> when the operation has completed. This should only be used
+ /// when memory doesn't wrap a byte[].
+ /// </summary>
+ private sealed class MemoryFileStreamCompletionSource : FileStreamCompletionSource
+ {
+ private MemoryHandle _handle; // mutable struct; do not make this readonly
+
+ internal MemoryFileStreamCompletionSource(FileStream stream, int numBufferedBytes, ReadOnlyMemory<byte> memory) :
+ base(stream, numBufferedBytes, bytes: null) // this type handles the pinning, so null is passed for bytes
+ {
+ _handle = memory.Pin();
+ }
+
+ internal override void ReleaseNativeResource()
+ {
+ _handle.Dispose();
+ base.ReleaseNativeResource();
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/IOException.cs b/src/System.Private.CoreLib/shared/System/IO/IOException.cs
new file mode 100644
index 0000000000..89b25d5142
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/IOException.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.IO
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class IOException : SystemException
+ {
+ public IOException()
+ : base(SR.Arg_IOException)
+ {
+ HResult = HResults.COR_E_IO;
+ }
+
+ public IOException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_IO;
+ }
+
+ public IOException(String message, int hresult)
+ : base(message)
+ {
+ HResult = hresult;
+ }
+
+ public IOException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_IO;
+ }
+
+ protected IOException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs
new file mode 100644
index 0000000000..fb319de7c2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs
@@ -0,0 +1,823 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ // A MemoryStream represents a Stream in memory (ie, it has no backing store).
+ // This stream may reduce the need for temporary buffers and files in
+ // an application.
+ //
+ // There are two ways to create a MemoryStream. You can initialize one
+ // from an unsigned byte array, or you can create an empty one. Empty
+ // memory streams are resizable, while ones created with a byte array provide
+ // a stream "view" of the data.
+ public class MemoryStream : Stream
+ {
+ private byte[] _buffer; // Either allocated internally or externally.
+ private int _origin; // For user-provided arrays, start at this origin
+ private int _position; // read/write head.
+ private int _length; // Number of bytes within the memory stream
+ private int _capacity; // length of usable portion of buffer for stream
+ // Note that _capacity == _buffer.Length for non-user-provided byte[]'s
+
+ private bool _expandable; // User-provided buffers aren't expandable.
+ private bool _writable; // Can user write to this stream?
+ private bool _exposable; // Whether the array can be returned to the user.
+ private bool _isOpen; // Is this stream open or closed?
+
+ private Task<int> _lastReadTask; // The last successful task returned from ReadAsync
+
+ private const int MemStreamMaxLength = int.MaxValue;
+
+ public MemoryStream()
+ : this(0)
+ {
+ }
+
+ public MemoryStream(int capacity)
+ {
+ if (capacity < 0)
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NegativeCapacity);
+
+ _buffer = capacity != 0 ? new byte[capacity] : Array.Empty<byte>();
+ _capacity = capacity;
+ _expandable = true;
+ _writable = true;
+ _exposable = true;
+ _origin = 0; // Must be 0 for byte[]'s created by MemoryStream
+ _isOpen = true;
+ }
+
+ public MemoryStream(byte[] buffer)
+ : this(buffer, true)
+ {
+ }
+
+ public MemoryStream(byte[] buffer, bool writable)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+
+ _buffer = buffer;
+ _length = _capacity = buffer.Length;
+ _writable = writable;
+ _exposable = false;
+ _origin = 0;
+ _isOpen = true;
+ }
+
+ public MemoryStream(byte[] buffer, int index, int count)
+ : this(buffer, index, count, true, false)
+ {
+ }
+
+ public MemoryStream(byte[] buffer, int index, int count, bool writable)
+ : this(buffer, index, count, writable, false)
+ {
+ }
+
+ public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - index < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ _buffer = buffer;
+ _origin = _position = index;
+ _length = _capacity = index + count;
+ _writable = writable;
+ _exposable = publiclyVisible; // Can TryGetBuffer/GetBuffer return the array?
+ _expandable = false;
+ _isOpen = true;
+ }
+
+ public override bool CanRead => _isOpen;
+
+ public override bool CanSeek => _isOpen;
+
+ public override bool CanWrite => _writable;
+
+ private void EnsureNotClosed()
+ {
+ if (!_isOpen)
+ throw Error.GetStreamIsClosed();
+ }
+
+ private void EnsureWriteable()
+ {
+ if (!CanWrite)
+ throw Error.GetWriteNotSupported();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ if (disposing)
+ {
+ _isOpen = false;
+ _writable = false;
+ _expandable = false;
+ // Don't set buffer to null - allow TryGetBuffer, GetBuffer & ToArray to work.
+ _lastReadTask = null;
+ }
+ }
+ finally
+ {
+ // Call base.Close() to cleanup async IO resources
+ base.Dispose(disposing);
+ }
+ }
+
+ // returns a bool saying whether we allocated a new array.
+ private bool EnsureCapacity(int value)
+ {
+ // Check for overflow
+ if (value < 0)
+ throw new IOException(SR.IO_StreamTooLong);
+
+ if (value > _capacity)
+ {
+ int newCapacity = value;
+ if (newCapacity < 256)
+ {
+ newCapacity = 256;
+ }
+
+ // We are ok with this overflowing since the next statement will deal
+ // with the cases where _capacity*2 overflows.
+ if (newCapacity < _capacity * 2)
+ {
+ newCapacity = _capacity * 2;
+ }
+
+ // We want to expand the array up to Array.MaxByteArrayLength
+ // And we want to give the user the value that they asked for
+ if ((uint)(_capacity * 2) > Array.MaxByteArrayLength)
+ {
+ newCapacity = value > Array.MaxByteArrayLength ? value : Array.MaxByteArrayLength;
+ }
+
+ Capacity = newCapacity;
+ return true;
+ }
+ return false;
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ try
+ {
+ Flush();
+ return Task.CompletedTask;
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+
+
+ public virtual byte[] GetBuffer()
+ {
+ if (!_exposable)
+ throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer);
+ return _buffer;
+ }
+
+ public virtual bool TryGetBuffer(out ArraySegment<byte> buffer)
+ {
+ if (!_exposable)
+ {
+ buffer = default(ArraySegment<byte>);
+ return false;
+ }
+
+ buffer = new ArraySegment<byte>(_buffer, offset: _origin, count: (_length - _origin));
+ return true;
+ }
+
+ // -------------- PERF: Internal functions for fast direct access of MemoryStream buffer (cf. BinaryReader for usage) ---------------
+
+ // PERF: Internal sibling of GetBuffer, always returns a buffer (cf. GetBuffer())
+ internal byte[] InternalGetBuffer()
+ {
+ return _buffer;
+ }
+
+ // PERF: True cursor position, we don't need _origin for direct access
+ internal int InternalGetPosition()
+ {
+ return _position;
+ }
+
+ // PERF: Takes out Int32 as fast as possible
+ internal int InternalReadInt32()
+ {
+ EnsureNotClosed();
+
+ int pos = (_position += 4); // use temp to avoid a race condition
+ if (pos > _length)
+ {
+ _position = _length;
+ throw Error.GetEndOfFile();
+ }
+ return (int)(_buffer[pos - 4] | _buffer[pos - 3] << 8 | _buffer[pos - 2] << 16 | _buffer[pos - 1] << 24);
+ }
+
+ // PERF: Get actual length of bytes available for read; do sanity checks; shift position - i.e. everything except actual copying bytes
+ internal int InternalEmulateRead(int count)
+ {
+ EnsureNotClosed();
+
+ int n = _length - _position;
+ if (n > count)
+ n = count;
+ if (n < 0)
+ n = 0;
+
+ Debug.Assert(_position + n >= 0, "_position + n >= 0"); // len is less than 2^31 -1.
+ _position += n;
+ return n;
+ }
+
+ // Gets & sets the capacity (number of bytes allocated) for this stream.
+ // The capacity cannot be set to a value less than the current length
+ // of the stream.
+ //
+ public virtual int Capacity
+ {
+ get
+ {
+ EnsureNotClosed();
+ return _capacity - _origin;
+ }
+ set
+ {
+ // Only update the capacity if the MS is expandable and the value is different than the current capacity.
+ // Special behavior if the MS isn't expandable: we don't throw if value is the same as the current capacity
+ if (value < Length)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_SmallCapacity);
+
+ EnsureNotClosed();
+
+ if (!_expandable && (value != Capacity))
+ throw new NotSupportedException(SR.NotSupported_MemStreamNotExpandable);
+
+ // MemoryStream has this invariant: _origin > 0 => !expandable (see ctors)
+ if (_expandable && value != _capacity)
+ {
+ if (value > 0)
+ {
+ byte[] newBuffer = new byte[value];
+ if (_length > 0)
+ {
+ Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _length);
+ }
+ _buffer = newBuffer;
+ }
+ else
+ {
+ _buffer = null;
+ }
+ _capacity = value;
+ }
+ }
+ }
+
+ public override long Length
+ {
+ get
+ {
+ EnsureNotClosed();
+ return _length - _origin;
+ }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ EnsureNotClosed();
+ return _position - _origin;
+ }
+ set
+ {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ EnsureNotClosed();
+
+ if (value > MemStreamMaxLength)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_StreamLength);
+ _position = _origin + (int)value;
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ EnsureNotClosed();
+
+ int n = _length - _position;
+ if (n > count)
+ n = count;
+ if (n <= 0)
+ return 0;
+
+ Debug.Assert(_position + n >= 0, "_position + n >= 0"); // len is less than 2^31 -1.
+
+ if (n <= 8)
+ {
+ int byteCount = n;
+ while (--byteCount >= 0)
+ buffer[offset + byteCount] = _buffer[_position + byteCount];
+ }
+ else
+ Buffer.BlockCopy(_buffer, _position, buffer, offset, n);
+ _position += n;
+
+ return n;
+ }
+
+ public override int Read(Span<byte> buffer)
+ {
+ if (GetType() != typeof(MemoryStream))
+ {
+ // MemoryStream is not sealed, and a derived type may have overridden Read(byte[], int, int) prior
+ // to this Read(Span<byte>) overload being introduced. In that case, this Read(Span<byte>) overload
+ // should use the behavior of Read(byte[],int,int) overload.
+ return base.Read(buffer);
+ }
+
+ EnsureNotClosed();
+
+ int n = Math.Min(_length - _position, buffer.Length);
+ if (n <= 0)
+ return 0;
+
+ // TODO https://github.com/dotnet/coreclr/issues/15076:
+ // Read(byte[], int, int) has an n <= 8 optimization, presumably based
+ // on benchmarking. Determine if/where such a cut-off is here and add
+ // an equivalent optimization if necessary.
+ new Span<byte>(_buffer, _position, n).CopyTo(buffer);
+
+ _position += n;
+ return n;
+ }
+
+ public override Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ // If cancellation was requested, bail early
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled<int>(cancellationToken);
+
+ try
+ {
+ int n = Read(buffer, offset, count);
+ var t = _lastReadTask;
+ Debug.Assert(t == null || t.Status == TaskStatus.RanToCompletion,
+ "Expected that a stored last task completed successfully");
+ return (t != null && t.Result == n) ? t : (_lastReadTask = Task.FromResult<int>(n));
+ }
+ catch (OperationCanceledException oce)
+ {
+ return Task.FromCancellation<int>(oce);
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException<int>(exception);
+ }
+ }
+
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
+ }
+
+ try
+ {
+ // ReadAsync(Memory<byte>,...) needs to delegate to an existing virtual to do the work, in case an existing derived type
+ // has changed or augmented the logic associated with reads. If the Memory wraps an array, we could delegate to
+ // ReadAsync(byte[], ...), but that would defeat part of the purpose, as ReadAsync(byte[], ...) often needs to allocate
+ // a Task<int> for the return value, so we want to delegate to one of the synchronous methods. We could always
+ // delegate to the Read(Span<byte>) method, and that's the most efficient solution when dealing with a concrete
+ // MemoryStream, but if we're dealing with a type derived from MemoryStream, Read(Span<byte>) will end up delegating
+ // to Read(byte[], ...), which requires it to get a byte[] from ArrayPool and copy the data. So, we special-case the
+ // very common case of the Memory<byte> wrapping an array: if it does, we delegate to Read(byte[], ...) with it,
+ // as that will be efficient in both cases, and we fall back to Read(Span<byte>) if the Memory<byte> wrapped something
+ // else; if this is a concrete MemoryStream, that'll be efficient, and only in the case where the Memory<byte> wrapped
+ // something other than an array and this is a MemoryStream-derived type that doesn't override Read(Span<byte>) will
+ // it then fall back to doing the ArrayPool/copy behavior.
+ return new ValueTask<int>(
+ MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> destinationArray) ?
+ Read(destinationArray.Array, destinationArray.Offset, destinationArray.Count) :
+ Read(buffer.Span));
+ }
+ catch (OperationCanceledException oce)
+ {
+ return new ValueTask<int>(Task.FromCancellation<int>(oce));
+ }
+ catch (Exception exception)
+ {
+ return new ValueTask<int>(Task.FromException<int>(exception));
+ }
+ }
+
+ public override int ReadByte()
+ {
+ EnsureNotClosed();
+
+ if (_position >= _length)
+ return -1;
+
+ return _buffer[_position++];
+ }
+
+ public override void CopyTo(Stream destination, int bufferSize)
+ {
+ // Since we did not originally override this method, validate the arguments
+ // the same way Stream does for back-compat.
+ StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize);
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Read() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Read) when we are not sure.
+ if (GetType() != typeof(MemoryStream))
+ {
+ base.CopyTo(destination, bufferSize);
+ return;
+ }
+
+ int originalPosition = _position;
+
+ // Seek to the end of the MemoryStream.
+ int remaining = InternalEmulateRead(_length - originalPosition);
+
+ // If we were already at or past the end, there's no copying to do so just quit.
+ if (remaining > 0)
+ {
+ // Call Write() on the other Stream, using our internal buffer and avoiding any
+ // intermediary allocations.
+ destination.Write(_buffer, originalPosition, remaining);
+ }
+ }
+
+ public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ // This implementation offers better performance compared to the base class version.
+
+ StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize);
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to ReadAsync() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into ReadAsync) when we are not sure.
+ if (GetType() != typeof(MemoryStream))
+ return base.CopyToAsync(destination, bufferSize, cancellationToken);
+
+ // If cancelled - return fast:
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ // Avoid copying data from this buffer into a temp buffer:
+ // (require that InternalEmulateRead does not throw,
+ // otherwise it needs to be wrapped into try-catch-Task.FromException like memStrDest.Write below)
+
+ int pos = _position;
+ int n = InternalEmulateRead(_length - _position);
+
+ // If we were already at or past the end, there's no copying to do so just quit.
+ if (n == 0)
+ return Task.CompletedTask;
+
+ // If destination is not a memory stream, write there asynchronously:
+ MemoryStream memStrDest = destination as MemoryStream;
+ if (memStrDest == null)
+ return destination.WriteAsync(_buffer, pos, n, cancellationToken);
+
+ try
+ {
+ // If destination is a MemoryStream, CopyTo synchronously:
+ memStrDest.Write(_buffer, pos, n);
+ return Task.CompletedTask;
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+
+
+ public override long Seek(long offset, SeekOrigin loc)
+ {
+ EnsureNotClosed();
+
+ if (offset > MemStreamMaxLength)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_StreamLength);
+
+ switch (loc)
+ {
+ case SeekOrigin.Begin:
+ {
+ int tempPosition = unchecked(_origin + (int)offset);
+ if (offset < 0 || tempPosition < _origin)
+ throw new IOException(SR.IO_SeekBeforeBegin);
+ _position = tempPosition;
+ break;
+ }
+ case SeekOrigin.Current:
+ {
+ int tempPosition = unchecked(_position + (int)offset);
+ if (unchecked(_position + offset) < _origin || tempPosition < _origin)
+ throw new IOException(SR.IO_SeekBeforeBegin);
+ _position = tempPosition;
+ break;
+ }
+ case SeekOrigin.End:
+ {
+ int tempPosition = unchecked(_length + (int)offset);
+ if (unchecked(_length + offset) < _origin || tempPosition < _origin)
+ throw new IOException(SR.IO_SeekBeforeBegin);
+ _position = tempPosition;
+ break;
+ }
+ default:
+ throw new ArgumentException(SR.Argument_InvalidSeekOrigin);
+ }
+
+ Debug.Assert(_position >= 0, "_position >= 0");
+ return _position;
+ }
+
+ // Sets the length of the stream to a given value. The new
+ // value must be nonnegative and less than the space remaining in
+ // the array, int.MaxValue - origin
+ // Origin is 0 in all cases other than a MemoryStream created on
+ // top of an existing array and a specific starting offset was passed
+ // into the MemoryStream constructor. The upper bounds prevents any
+ // situations where a stream may be created on top of an array then
+ // the stream is made longer than the maximum possible length of the
+ // array (int.MaxValue).
+ //
+ public override void SetLength(long value)
+ {
+ if (value < 0 || value > int.MaxValue)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_StreamLength);
+
+ EnsureWriteable();
+
+ // Origin wasn't publicly exposed above.
+ Debug.Assert(MemStreamMaxLength == int.MaxValue); // Check parameter validation logic in this method if this fails.
+ if (value > (int.MaxValue - _origin))
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_StreamLength);
+
+ int newLength = _origin + (int)value;
+ bool allocatedNewArray = EnsureCapacity(newLength);
+ if (!allocatedNewArray && newLength > _length)
+ Array.Clear(_buffer, _length, newLength - _length);
+ _length = newLength;
+ if (_position > newLength)
+ _position = newLength;
+ }
+
+ public virtual byte[] ToArray()
+ {
+ int count = _length - _origin;
+ if (count == 0)
+ return Array.Empty<byte>();
+ byte[] copy = new byte[count];
+ Buffer.BlockCopy(_buffer, _origin, copy, 0, count);
+ return copy;
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ EnsureNotClosed();
+ EnsureWriteable();
+
+ int i = _position + count;
+ // Check for overflow
+ if (i < 0)
+ throw new IOException(SR.IO_StreamTooLong);
+
+ if (i > _length)
+ {
+ bool mustZero = _position > _length;
+ if (i > _capacity)
+ {
+ bool allocatedNewArray = EnsureCapacity(i);
+ if (allocatedNewArray)
+ {
+ mustZero = false;
+ }
+ }
+ if (mustZero)
+ {
+ Array.Clear(_buffer, _length, i - _length);
+ }
+ _length = i;
+ }
+ if ((count <= 8) && (buffer != _buffer))
+ {
+ int byteCount = count;
+ while (--byteCount >= 0)
+ {
+ _buffer[_position + byteCount] = buffer[offset + byteCount];
+ }
+ }
+ else
+ {
+ Buffer.BlockCopy(buffer, offset, _buffer, _position, count);
+ }
+ _position = i;
+ }
+
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ if (GetType() != typeof(MemoryStream))
+ {
+ // MemoryStream is not sealed, and a derived type may have overridden Write(byte[], int, int) prior
+ // to this Write(Span<byte>) overload being introduced. In that case, this Write(Span<byte>) overload
+ // should use the behavior of Write(byte[],int,int) overload.
+ base.Write(buffer);
+ return;
+ }
+
+ EnsureNotClosed();
+ EnsureWriteable();
+
+ // Check for overflow
+ int i = _position + buffer.Length;
+ if (i < 0)
+ throw new IOException(SR.IO_StreamTooLong);
+
+ if (i > _length)
+ {
+ bool mustZero = _position > _length;
+ if (i > _capacity)
+ {
+ bool allocatedNewArray = EnsureCapacity(i);
+ if (allocatedNewArray)
+ {
+ mustZero = false;
+ }
+ }
+ if (mustZero)
+ {
+ Array.Clear(_buffer, _length, i - _length);
+ }
+ _length = i;
+ }
+
+ buffer.CopyTo(new Span<byte>(_buffer, _position, buffer.Length));
+ _position = i;
+ }
+
+ public override Task WriteAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ // If cancellation is already requested, bail early
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ try
+ {
+ Write(buffer, offset, count);
+ return Task.CompletedTask;
+ }
+ catch (OperationCanceledException oce)
+ {
+ return Task.FromCancellation<VoidTaskResult>(oce);
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask(Task.FromCanceled(cancellationToken));
+ }
+
+ try
+ {
+ // See corresponding comment in ReadAsync for why we don't just always use Write(ReadOnlySpan<byte>).
+ // Unlike ReadAsync, we could delegate to WriteAsync(byte[], ...) here, but we don't for consistency.
+ if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> sourceArray))
+ {
+ Write(sourceArray.Array, sourceArray.Offset, sourceArray.Count);
+ }
+ else
+ {
+ Write(buffer.Span);
+ }
+ return default;
+ }
+ catch (OperationCanceledException oce)
+ {
+ return new ValueTask(Task.FromCancellation<VoidTaskResult>(oce));
+ }
+ catch (Exception exception)
+ {
+ return new ValueTask(Task.FromException(exception));
+ }
+ }
+
+ public override void WriteByte(byte value)
+ {
+ EnsureNotClosed();
+ EnsureWriteable();
+
+ if (_position >= _length)
+ {
+ int newLength = _position + 1;
+ bool mustZero = _position > _length;
+ if (newLength >= _capacity)
+ {
+ bool allocatedNewArray = EnsureCapacity(newLength);
+ if (allocatedNewArray)
+ {
+ mustZero = false;
+ }
+ }
+ if (mustZero)
+ {
+ Array.Clear(_buffer, _length, _position - _length);
+ }
+ _length = newLength;
+ }
+ _buffer[_position++] = value;
+ }
+
+ // Writes this MemoryStream to another stream.
+ public virtual void WriteTo(Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream), SR.ArgumentNull_Stream);
+
+ EnsureNotClosed();
+
+ stream.Write(_buffer, _origin, _length - _origin);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs b/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs
new file mode 100644
index 0000000000..f364b84df0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs
@@ -0,0 +1,147 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.IO
+{
+ public static partial class Path
+ {
+ public static char[] GetInvalidFileNameChars() => new char[] { '\0', '/' };
+
+ public static char[] GetInvalidPathChars() => new char[] { '\0' };
+
+ // Expands the given path to a fully qualified path.
+ public static string GetFullPath(string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ if (path.Length == 0)
+ throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
+
+ if (path.IndexOf('\0') != -1)
+ throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
+
+ // Expand with current directory if necessary
+ if (!IsPathRooted(path))
+ {
+ path = Combine(Interop.Sys.GetCwd(), path);
+ }
+
+ // We would ideally use realpath to do this, but it resolves symlinks, requires that the file actually exist,
+ // and turns it into a full path, which we only want if fullCheck is true.
+ string collapsedString = PathInternal.RemoveRelativeSegments(path, PathInternal.GetRootLength(path));
+
+ Debug.Assert(collapsedString.Length < path.Length || collapsedString.ToString() == path,
+ "Either we've removed characters, or the string should be unmodified from the input path.");
+
+ string result = collapsedString.Length == 0 ? PathInternal.DirectorySeparatorCharAsString : collapsedString;
+
+ return result;
+ }
+
+ public static string GetFullPath(string path, string basePath)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ if (basePath == null)
+ throw new ArgumentNullException(nameof(basePath));
+
+ if (!IsPathFullyQualified(basePath))
+ throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath));
+
+ if (basePath.Contains('\0') || path.Contains('\0'))
+ throw new ArgumentException(SR.Argument_InvalidPathChars);
+
+ if (IsPathFullyQualified(path))
+ return GetFullPath(path);
+
+ return GetFullPath(CombineInternal(basePath, path));
+ }
+
+ private static string RemoveLongPathPrefix(string path)
+ {
+ return path; // nop. There's nothing special about "long" paths on Unix.
+ }
+
+ public static string GetTempPath()
+ {
+ const string TempEnvVar = "TMPDIR";
+ const string DefaultTempPath = "/tmp/";
+
+ // Get the temp path from the TMPDIR environment variable.
+ // If it's not set, just return the default path.
+ // If it is, return it, ensuring it ends with a slash.
+ string path = Environment.GetEnvironmentVariable(TempEnvVar);
+ return
+ string.IsNullOrEmpty(path) ? DefaultTempPath :
+ PathInternal.IsDirectorySeparator(path[path.Length - 1]) ? path :
+ path + PathInternal.DirectorySeparatorChar;
+ }
+
+ public static string GetTempFileName()
+ {
+ const string Suffix = ".tmp";
+ const int SuffixByteLength = 4;
+
+ // mkstemps takes a char* and overwrites the XXXXXX with six characters
+ // that'll result in a unique file name.
+ string template = GetTempPath() + "tmpXXXXXX" + Suffix + "\0";
+ byte[] name = Encoding.UTF8.GetBytes(template);
+
+ // Create, open, and close the temp file.
+ IntPtr fd = Interop.CheckIo(Interop.Sys.MksTemps(name, SuffixByteLength));
+ Interop.Sys.Close(fd); // ignore any errors from close; nothing to do if cleanup isn't possible
+
+ // 'name' is now the name of the file
+ Debug.Assert(name[name.Length - 1] == '\0');
+ return Encoding.UTF8.GetString(name, 0, name.Length - 1); // trim off the trailing '\0'
+ }
+
+ public static bool IsPathRooted(string path)
+ {
+ if (path == null)
+ return false;
+
+ return IsPathRooted(path.AsSpan());
+ }
+
+ public static bool IsPathRooted(ReadOnlySpan<char> path)
+ {
+ return path.Length > 0 && path[0] == PathInternal.DirectorySeparatorChar;
+ }
+
+ /// <summary>
+ /// Returns the path root or null if path is empty or null.
+ /// </summary>
+ public static string GetPathRoot(string path)
+ {
+ if (PathInternal.IsEffectivelyEmpty(path)) return null;
+
+ return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : string.Empty;
+ }
+
+ public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
+ {
+ return PathInternal.IsEffectivelyEmpty(path) && IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan<char>.Empty;
+ }
+
+ /// <summary>Gets whether the system is case-sensitive.</summary>
+ internal static bool IsCaseSensitive
+ {
+ get
+ {
+ #if PLATFORM_OSX
+ return false;
+ #else
+ return true;
+ #endif
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs
new file mode 100644
index 0000000000..f22a9913ea
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs
@@ -0,0 +1,286 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.IO
+{
+ public static partial class Path
+ {
+ public static char[] GetInvalidFileNameChars() => new char[]
+ {
+ '\"', '<', '>', '|', '\0',
+ (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
+ (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
+ (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
+ (char)31, ':', '*', '?', '\\', '/'
+ };
+
+ public static char[] GetInvalidPathChars() => new char[]
+ {
+ '|', '\0',
+ (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
+ (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
+ (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
+ (char)31
+ };
+
+ // Expands the given path to a fully qualified path.
+ public static string GetFullPath(string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ // If the path would normalize to string empty, we'll consider it empty
+ if (PathInternal.IsEffectivelyEmpty(path))
+ throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
+
+ // Embedded null characters are the only invalid character case we trully care about.
+ // This is because the nulls will signal the end of the string to Win32 and therefore have
+ // unpredictable results.
+ if (path.IndexOf('\0') != -1)
+ throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
+
+ if (PathInternal.IsExtended(path))
+ {
+ // \\?\ paths are considered normalized by definition. Windows doesn't normalize \\?\
+ // paths and neither should we. Even if we wanted to GetFullPathName does not work
+ // properly with device paths. If one wants to pass a \\?\ path through normalization
+ // one can chop off the prefix, pass it to GetFullPath and add it again.
+ return path;
+ }
+
+ return PathHelper.Normalize(path);
+ }
+
+ public static string GetFullPath(string path, string basePath)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ if (basePath == null)
+ throw new ArgumentNullException(nameof(basePath));
+
+ if (!IsPathFullyQualified(basePath))
+ throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath));
+
+ if (basePath.Contains('\0') || path.Contains('\0'))
+ throw new ArgumentException(SR.Argument_InvalidPathChars);
+
+ if (IsPathFullyQualified(path))
+ return GetFullPath(path);
+
+ if (PathInternal.IsEffectivelyEmpty(path))
+ return basePath;
+
+ int length = path.Length;
+ string combinedPath = null;
+
+ if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])))
+ {
+ // Path is current drive rooted i.e. starts with \:
+ // "\Foo" and "C:\Bar" => "C:\Foo"
+ // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo"
+ combinedPath = Join(GetPathRoot(basePath.AsSpan()), path.AsSpan(1)); // Cut the separator to ensure we don't end up with two separators when joining with the root.
+ }
+ else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar)
+ {
+ // Drive relative paths
+ Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2]));
+
+ if (GetVolumeName(path).EqualsOrdinal(GetVolumeName(basePath)))
+ {
+ // Matching root
+ // "C:Foo" and "C:\Bar" => "C:\Bar\Foo"
+ // "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
+ combinedPath = Join(basePath, path.AsSpan(2));
+ }
+ else
+ {
+ // No matching root, root to specified drive
+ // "D:Foo" and "C:\Bar" => "D:Foo"
+ // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo"
+ combinedPath = !PathInternal.IsDevice(basePath)
+ ? path.Insert(2, @"\")
+ : length == 2
+ ? JoinInternal(basePath.AsSpan(0, 4), path, @"\")
+ : JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(0, 2), @"\", path.AsSpan(2));
+ }
+ }
+ else
+ {
+ // "Simple" relative path
+ // "Foo" and "C:\Bar" => "C:\Bar\Foo"
+ // "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
+ combinedPath = JoinInternal(basePath, path);
+ }
+
+ // Device paths are normalized by definition, so passing something of this format (i.e. \\?\C:\.\tmp, \\.\C:\foo)
+ // to Windows APIs won't do anything by design. Additionally, GetFullPathName() in Windows doesn't root
+ // them properly. As such we need to manually remove segments and not use GetFullPath().
+
+ return PathInternal.IsDevice(combinedPath)
+ ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath))
+ : GetFullPath(combinedPath);
+ }
+
+ public static string GetTempPath()
+ {
+ Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath];
+ var builder = new ValueStringBuilder(initialBuffer);
+
+ GetTempPath(ref builder);
+
+ string path = PathHelper.Normalize(ref builder);
+ builder.Dispose();
+ return path;
+ }
+
+ private static void GetTempPath(ref ValueStringBuilder builder)
+ {
+ uint result = 0;
+ while ((result = Interop.Kernel32.GetTempPathW(builder.Capacity, ref builder.GetPinnableReference())) > builder.Capacity)
+ {
+ // Reported size is greater than the buffer size. Increase the capacity.
+ builder.EnsureCapacity(checked((int)result));
+ }
+
+ if (result == 0)
+ throw Win32Marshal.GetExceptionForLastWin32Error();
+
+ builder.Length = (int)result;
+ }
+
+ // Returns a unique temporary file name, and creates a 0-byte file by that
+ // name on disk.
+ public static string GetTempFileName()
+ {
+ Span<char> initialTempPathBuffer = stackalloc char[PathInternal.MaxShortPath];
+ ValueStringBuilder tempPathBuilder = new ValueStringBuilder(initialTempPathBuffer);
+
+ GetTempPath(ref tempPathBuilder);
+
+ Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath];
+ var builder = new ValueStringBuilder(initialBuffer);
+
+ uint result = Interop.Kernel32.GetTempFileNameW(
+ ref tempPathBuilder.GetPinnableReference(), "tmp", 0, ref builder.GetPinnableReference());
+
+ tempPathBuilder.Dispose();
+
+ if (result == 0)
+ throw Win32Marshal.GetExceptionForLastWin32Error();
+
+ builder.Length = builder.RawChars.IndexOf('\0');
+
+ string path = PathHelper.Normalize(ref builder);
+ builder.Dispose();
+ return path;
+ }
+
+ // Tests if the given path contains a root. A path is considered rooted
+ // if it starts with a backslash ("\") or a valid drive letter and a colon (":").
+ public static bool IsPathRooted(string path)
+ {
+ return path != null && IsPathRooted(path.AsSpan());
+ }
+
+ public static bool IsPathRooted(ReadOnlySpan<char> path)
+ {
+ int length = path.Length;
+ return (length >= 1 && PathInternal.IsDirectorySeparator(path[0]))
+ || (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar);
+ }
+
+ // Returns the root portion of the given path. The resulting string
+ // consists of those rightmost characters of the path that constitute the
+ // root of the path. Possible patterns for the resulting string are: An
+ // empty string (a relative path on the current drive), "\" (an absolute
+ // path on the current drive), "X:" (a relative path on a given drive,
+ // where X is the drive letter), "X:\" (an absolute path on a given drive),
+ // and "\\server\share" (a UNC path for a given server and share name).
+ // The resulting string is null if path is null. If the path is empty or
+ // only contains whitespace characters an ArgumentException gets thrown.
+ public static string GetPathRoot(string path)
+ {
+ if (PathInternal.IsEffectivelyEmpty(path))
+ return null;
+
+ ReadOnlySpan<char> result = GetPathRoot(path.AsSpan());
+ if (path.Length == result.Length)
+ return PathInternal.NormalizeDirectorySeparators(path);
+
+ return PathInternal.NormalizeDirectorySeparators(new string(result));
+ }
+
+ /// <remarks>
+ /// Unlike the string overload, this method will not normalize directory separators.
+ /// </remarks>
+ public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
+ {
+ if (PathInternal.IsEffectivelyEmpty(path))
+ return ReadOnlySpan<char>.Empty;
+
+ int pathRoot = PathInternal.GetRootLength(path);
+ return pathRoot <= 0 ? ReadOnlySpan<char>.Empty : path.Slice(0, pathRoot);
+ }
+
+ /// <summary>Gets whether the system is case-sensitive.</summary>
+ internal static bool IsCaseSensitive { get { return false; } }
+
+ /// <summary>
+ /// Returns the volume name for dos, UNC and device paths.
+ /// </summary>
+ internal static ReadOnlySpan<char> GetVolumeName(ReadOnlySpan<char> path)
+ {
+ // 3 cases: UNC ("\\server\share"), Device ("\\?\C:\"), or Dos ("C:\")
+ ReadOnlySpan<char> root = GetPathRoot(path);
+ if (root.Length == 0)
+ return root;
+
+ int offset = GetUncRootLength(path);
+ if (offset >= 0)
+ {
+ // Cut from "\\?\UNC\Server\Share" to "Server\Share"
+ // Cut from "\\Server\Share" to "Server\Share"
+ return TrimEndingDirectorySeparator(root.Slice(offset));
+ }
+ else if (PathInternal.IsDevice(path))
+ {
+ return TrimEndingDirectorySeparator(root.Slice(4)); // Cut from "\\?\C:\" to "C:"
+ }
+
+ return TrimEndingDirectorySeparator(root); // e.g. "C:"
+ }
+
+ /// <summary>
+ /// Trims the ending directory separator if present.
+ /// </summary>
+ /// <param name="path"></param>
+ internal static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
+ PathInternal.EndsInDirectorySeparator(path) ?
+ path.Slice(0, path.Length - 1) :
+ path;
+
+ /// <summary>
+ /// Returns offset as -1 if the path is not in Unc format, otherwise returns the root length.
+ /// </summary>
+ /// <param name="path"></param>
+ /// <returns></returns>
+ internal static int GetUncRootLength(ReadOnlySpan<char> path)
+ {
+ bool isDevice = PathInternal.IsDevice(path);
+
+ if (!isDevice && path.Slice(0, 2).EqualsOrdinal(@"\\") )
+ return 2;
+ else if (isDevice && path.Length >= 8
+ && (path.Slice(0, 8).EqualsOrdinal(PathInternal.UncExtendedPathPrefix)
+ || path.Slice(5, 4).EqualsOrdinal(@"UNC\")))
+ return 8;
+
+ return -1;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.cs b/src/System.Private.CoreLib/shared/System/IO/Path.cs
new file mode 100644
index 0000000000..1e40ab5e60
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.cs
@@ -0,0 +1,771 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.IO
+{
+ // Provides methods for processing file system strings in a cross-platform manner.
+ // Most of the methods don't do a complete parsing (such as examining a UNC hostname),
+ // but they will handle most string operations.
+ public static partial class Path
+ {
+ // Public static readonly variant of the separators. The Path implementation itself is using
+ // internal const variant of the separators for better performance.
+ public static readonly char DirectorySeparatorChar = PathInternal.DirectorySeparatorChar;
+ public static readonly char AltDirectorySeparatorChar = PathInternal.AltDirectorySeparatorChar;
+ public static readonly char VolumeSeparatorChar = PathInternal.VolumeSeparatorChar;
+ public static readonly char PathSeparator = PathInternal.PathSeparator;
+
+ // For generating random file names
+ // 8 random bytes provides 12 chars in our encoding for the 8.3 name.
+ private const int KeyLength = 8;
+
+ [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")]
+ public static readonly char[] InvalidPathChars = GetInvalidPathChars();
+
+ // Changes the extension of a file path. The path parameter
+ // specifies a file path, and the extension parameter
+ // specifies a file extension (with a leading period, such as
+ // ".exe" or ".cs").
+ //
+ // The function returns a file path with the same root, directory, and base
+ // name parts as path, but with the file extension changed to
+ // the specified extension. If path is null, the function
+ // returns null. If path does not contain a file extension,
+ // the new file extension is appended to the path. If extension
+ // is null, any existing extension is removed from path.
+ public static string ChangeExtension(string path, string extension)
+ {
+ if (path != null)
+ {
+ string s = path;
+ for (int i = path.Length - 1; i >= 0; i--)
+ {
+ char ch = path[i];
+ if (ch == '.')
+ {
+ s = path.Substring(0, i);
+ break;
+ }
+ if (PathInternal.IsDirectorySeparator(ch)) break;
+ }
+
+ if (extension != null && path.Length != 0)
+ {
+ s = (extension.Length == 0 || extension[0] != '.') ?
+ s + "." + extension :
+ s + extension;
+ }
+
+ return s;
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Returns the directory portion of a file path. This method effectively
+ /// removes the last segment of the given file path, i.e. it returns a
+ /// string consisting of all characters up to but not including the last
+ /// backslash ("\") in the file path. The returned value is null if the
+ /// specified path is null, empty, or a root (such as "\", "C:", or
+ /// "\\server\share").
+ /// </summary>
+ /// <remarks>
+ /// Directory separators are normalized in the returned string.
+ /// </remarks>
+ public static string GetDirectoryName(string path)
+ {
+ if (path == null || PathInternal.IsEffectivelyEmpty(path))
+ return null;
+
+ int end = GetDirectoryNameOffset(path);
+ return end >= 0 ? PathInternal.NormalizeDirectorySeparators(path.Substring(0, end)) : null;
+ }
+
+ /// <summary>
+ /// Returns the directory portion of a file path. The returned value is empty
+ /// if the specified path is null, empty, or a root (such as "\", "C:", or
+ /// "\\server\share").
+ /// </summary>
+ /// <remarks>
+ /// Unlike the string overload, this method will not normalize directory separators.
+ /// </remarks>
+ public static ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path)
+ {
+ if (PathInternal.IsEffectivelyEmpty(path))
+ return ReadOnlySpan<char>.Empty;
+
+ int end = GetDirectoryNameOffset(path);
+ return end >= 0 ? path.Slice(0, end) : ReadOnlySpan<char>.Empty;
+ }
+
+ private static int GetDirectoryNameOffset(ReadOnlySpan<char> path)
+ {
+ int rootLength = PathInternal.GetRootLength(path);
+ int end = path.Length;
+ if (end <= rootLength)
+ return -1;
+
+ while (end > rootLength && !PathInternal.IsDirectorySeparator(path[--end]));
+
+ // Trim off any remaining separators (to deal with C:\foo\\bar)
+ while (end > rootLength && PathInternal.IsDirectorySeparator(path[end - 1]))
+ end--;
+
+ return end;
+ }
+
+ /// <summary>
+ /// Returns the extension of the given path. The returned value includes the period (".") character of the
+ /// extension except when you have a terminal period when you get string.Empty, such as ".exe" or ".cpp".
+ /// The returned value is null if the given path is null or empty if the given path does not include an
+ /// extension.
+ /// </summary>
+ public static string GetExtension(string path)
+ {
+ if (path == null)
+ return null;
+
+ return new string(GetExtension(path.AsSpan()));
+ }
+
+ /// <summary>
+ /// Returns the extension of the given path.
+ /// </summary>
+ /// <remarks>
+ /// The returned value is an empty ReadOnlySpan if the given path does not include an extension.
+ /// </remarks>
+ public static ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path)
+ {
+ int length = path.Length;
+
+ for (int i = length - 1; i >= 0; i--)
+ {
+ char ch = path[i];
+ if (ch == '.')
+ {
+ if (i != length - 1)
+ return path.Slice(i, length - i);
+ else
+ return ReadOnlySpan<char>.Empty;
+ }
+ if (PathInternal.IsDirectorySeparator(ch))
+ break;
+ }
+ return ReadOnlySpan<char>.Empty;
+ }
+
+ /// <summary>
+ /// Returns the name and extension parts of the given path. The resulting string contains
+ /// the characters of path that follow the last separator in path. The resulting string is
+ /// null if path is null.
+ /// </summary>
+ public static string GetFileName(string path)
+ {
+ if (path == null)
+ return null;
+
+ ReadOnlySpan<char> result = GetFileName(path.AsSpan());
+ if (path.Length == result.Length)
+ return path;
+
+ return new string(result);
+ }
+
+ /// <summary>
+ /// The returned ReadOnlySpan contains the characters of the path that follows the last separator in path.
+ /// </summary>
+ public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path)
+ {
+ int root = GetPathRoot(path).Length;
+
+ // We don't want to cut off "C:\file.txt:stream" (i.e. should be "file.txt:stream")
+ // but we *do* want "C:Foo" => "Foo". This necessitates checking for the root.
+
+ for (int i = path.Length; --i >= 0;)
+ {
+ if (i < root || PathInternal.IsDirectorySeparator(path[i]))
+ return path.Slice(i + 1, path.Length - i - 1);
+ }
+
+ return path;
+ }
+
+ public static string GetFileNameWithoutExtension(string path)
+ {
+ if (path == null)
+ return null;
+
+ ReadOnlySpan<char> result = GetFileNameWithoutExtension(path.AsSpan());
+ if (path.Length == result.Length)
+ return path;
+
+ return new string(result);
+ }
+
+ /// <summary>
+ /// Returns the characters between the last separator and last (.) in the path.
+ /// </summary>
+ public static ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path)
+ {
+ ReadOnlySpan<char> fileName = GetFileName(path);
+ int lastPeriod = fileName.LastIndexOf('.');
+ return lastPeriod == -1 ?
+ fileName : // No extension was found
+ fileName.Slice(0, lastPeriod);
+ }
+
+ /// <summary>
+ /// Returns a cryptographically strong random 8.3 string that can be
+ /// used as either a folder name or a file name.
+ /// </summary>
+ public static unsafe string GetRandomFileName()
+ {
+ byte* pKey = stackalloc byte[KeyLength];
+ Interop.GetRandomBytes(pKey, KeyLength);
+
+ const int RandomFileNameLength = 12;
+ char* pRandomFileName = stackalloc char[RandomFileNameLength];
+ Populate83FileNameFromRandomBytes(pKey, KeyLength, pRandomFileName, RandomFileNameLength);
+ return new string(pRandomFileName, 0, RandomFileNameLength);
+ }
+
+ /// <summary>
+ /// Returns true if the path is fixed to a specific drive or UNC path. This method does no
+ /// validation of the path (URIs will be returned as relative as a result).
+ /// Returns false if the path specified is relative to the current drive or working directory.
+ /// </summary>
+ /// <remarks>
+ /// Handles paths that use the alternate directory separator. It is a frequent mistake to
+ /// assume that rooted paths <see cref="Path.IsPathRooted(string)"/> are not relative. This isn't the case.
+ /// "C:a" is drive relative- meaning that it will be resolved against the current directory
+ /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
+ /// will not be used to modify the path).
+ /// </remarks>
+ /// <exception cref="ArgumentNullException">
+ /// Thrown if <paramref name="path"/> is null.
+ /// </exception>
+ public static bool IsPathFullyQualified(string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ return IsPathFullyQualified(path.AsSpan());
+ }
+
+ public static bool IsPathFullyQualified(ReadOnlySpan<char> path)
+ {
+ return !PathInternal.IsPartiallyQualified(path);
+ }
+
+ /// <summary>
+ /// Tests if a path's file name includes a file extension. A trailing period
+ /// is not considered an extension.
+ /// </summary>
+ public static bool HasExtension(string path)
+ {
+ if (path != null)
+ {
+ return HasExtension(path.AsSpan());
+ }
+ return false;
+ }
+
+ public static bool HasExtension(ReadOnlySpan<char> path)
+ {
+ for (int i = path.Length - 1; i >= 0; i--)
+ {
+ char ch = path[i];
+ if (ch == '.')
+ {
+ return i != path.Length - 1;
+ }
+ if (PathInternal.IsDirectorySeparator(ch))
+ break;
+ }
+ return false;
+ }
+
+ public static string Combine(string path1, string path2)
+ {
+ if (path1 == null || path2 == null)
+ throw new ArgumentNullException((path1 == null) ? nameof(path1) : nameof(path2));
+
+ return CombineInternal(path1, path2);
+ }
+
+ public static string Combine(string path1, string path2, string path3)
+ {
+ if (path1 == null || path2 == null || path3 == null)
+ throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : nameof(path3));
+
+ return CombineInternal(path1, path2, path3);
+ }
+
+ public static string Combine(string path1, string path2, string path3, string path4)
+ {
+ if (path1 == null || path2 == null || path3 == null || path4 == null)
+ throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : (path3 == null) ? nameof(path3) : nameof(path4));
+
+ return CombineInternal(path1, path2, path3, path4);
+ }
+
+ public static string Combine(params string[] paths)
+ {
+ if (paths == null)
+ {
+ throw new ArgumentNullException(nameof(paths));
+ }
+
+ int finalSize = 0;
+ int firstComponent = 0;
+
+ // We have two passes, the first calculates how large a buffer to allocate and does some precondition
+ // checks on the paths passed in. The second actually does the combination.
+
+ for (int i = 0; i < paths.Length; i++)
+ {
+ if (paths[i] == null)
+ {
+ throw new ArgumentNullException(nameof(paths));
+ }
+
+ if (paths[i].Length == 0)
+ {
+ continue;
+ }
+
+ if (IsPathRooted(paths[i]))
+ {
+ firstComponent = i;
+ finalSize = paths[i].Length;
+ }
+ else
+ {
+ finalSize += paths[i].Length;
+ }
+
+ char ch = paths[i][paths[i].Length - 1];
+ if (!PathInternal.IsDirectorySeparator(ch))
+ finalSize++;
+ }
+
+ StringBuilder finalPath = StringBuilderCache.Acquire(finalSize);
+
+ for (int i = firstComponent; i < paths.Length; i++)
+ {
+ if (paths[i].Length == 0)
+ {
+ continue;
+ }
+
+ if (finalPath.Length == 0)
+ {
+ finalPath.Append(paths[i]);
+ }
+ else
+ {
+ char ch = finalPath[finalPath.Length - 1];
+ if (!PathInternal.IsDirectorySeparator(ch))
+ {
+ finalPath.Append(PathInternal.DirectorySeparatorChar);
+ }
+
+ finalPath.Append(paths[i]);
+ }
+ }
+
+ return StringBuilderCache.GetStringAndRelease(finalPath);
+ }
+
+ // Unlike Combine(), Join() methods do not consider rooting. They simply combine paths, ensuring that there
+ // is a directory separator between them.
+
+ public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2)
+ {
+ if (path1.Length == 0)
+ return new string(path2);
+ if (path2.Length == 0)
+ return new string(path1);
+
+ return JoinInternal(path1, path2);
+ }
+
+ public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3)
+ {
+ if (path1.Length == 0)
+ return Join(path2, path3);
+
+ if (path2.Length == 0)
+ return Join(path1, path3);
+
+ if (path3.Length == 0)
+ return Join(path1, path2);
+
+ return JoinInternal(path1, path2, path3);
+ }
+
+ public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, Span<char> destination, out int charsWritten)
+ {
+ charsWritten = 0;
+ if (path1.Length == 0 && path2.Length == 0)
+ return true;
+
+ if (path1.Length == 0 || path2.Length == 0)
+ {
+ ref ReadOnlySpan<char> pathToUse = ref path1.Length == 0 ? ref path2 : ref path1;
+ if (destination.Length < pathToUse.Length)
+ {
+ return false;
+ }
+
+ pathToUse.CopyTo(destination);
+ charsWritten = pathToUse.Length;
+ return true;
+ }
+
+ bool needsSeparator = !(PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2));
+ int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0);
+ if (destination.Length < charsNeeded)
+ return false;
+
+ path1.CopyTo(destination);
+ if (needsSeparator)
+ destination[path1.Length] = DirectorySeparatorChar;
+
+ path2.CopyTo(destination.Slice(path1.Length + (needsSeparator ? 1 : 0)));
+
+ charsWritten = charsNeeded;
+ return true;
+ }
+
+ public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, Span<char> destination, out int charsWritten)
+ {
+ charsWritten = 0;
+ if (path1.Length == 0 && path2.Length == 0 && path3.Length == 0)
+ return true;
+
+ if (path1.Length == 0)
+ return TryJoin(path2, path3, destination, out charsWritten);
+ if (path2.Length == 0)
+ return TryJoin(path1, path3, destination, out charsWritten);
+ if (path3.Length == 0)
+ return TryJoin(path1, path2, destination, out charsWritten);
+
+ int neededSeparators = PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2) ? 0 : 1;
+ bool needsSecondSeparator = !(PathInternal.EndsInDirectorySeparator(path2) || PathInternal.StartsWithDirectorySeparator(path3));
+ if (needsSecondSeparator)
+ neededSeparators++;
+
+ int charsNeeded = path1.Length + path2.Length + path3.Length + neededSeparators;
+ if (destination.Length < charsNeeded)
+ return false;
+
+ bool result = TryJoin(path1, path2, destination, out charsWritten);
+ Debug.Assert(result, "should never fail joining first two paths");
+
+ if (needsSecondSeparator)
+ destination[charsWritten++] = DirectorySeparatorChar;
+
+ path3.CopyTo(destination.Slice(charsWritten));
+ charsWritten += path3.Length;
+
+ return true;
+ }
+
+ private static string CombineInternal(string first, string second)
+ {
+ if (string.IsNullOrEmpty(first))
+ return second;
+
+ if (string.IsNullOrEmpty(second))
+ return first;
+
+ if (IsPathRooted(second.AsSpan()))
+ return second;
+
+ return JoinInternal(first, second);
+ }
+
+ private static string CombineInternal(string first, string second, string third)
+ {
+ if (string.IsNullOrEmpty(first))
+ return CombineInternal(second, third);
+ if (string.IsNullOrEmpty(second))
+ return CombineInternal(first, third);
+ if (string.IsNullOrEmpty(third))
+ return CombineInternal(first, second);
+
+ if (IsPathRooted(third.AsSpan()))
+ return third;
+ if (IsPathRooted(second.AsSpan()))
+ return CombineInternal(second, third);
+
+ return JoinInternal(first, second, third);
+ }
+
+ private static string CombineInternal(string first, string second, string third, string fourth)
+ {
+ if (string.IsNullOrEmpty(first))
+ return CombineInternal(second, third, fourth);
+ if (string.IsNullOrEmpty(second))
+ return CombineInternal(first, third, fourth);
+ if (string.IsNullOrEmpty(third))
+ return CombineInternal(first, second, fourth);
+ if (string.IsNullOrEmpty(fourth))
+ return CombineInternal(first, second, third);
+
+ if (IsPathRooted(fourth.AsSpan()))
+ return fourth;
+ if (IsPathRooted(third.AsSpan()))
+ return CombineInternal(third, fourth);
+ if (IsPathRooted(second.AsSpan()))
+ return CombineInternal(second, third, fourth);
+
+ return JoinInternal(first, second, third, fourth);
+ }
+
+ private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
+ {
+ Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths");
+
+ bool hasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
+ || PathInternal.IsDirectorySeparator(second[0]);
+
+ fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second))
+ {
+ return string.Create(
+ first.Length + second.Length + (hasSeparator ? 0 : 1),
+ (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length, HasSeparator: hasSeparator),
+ (destination, state) =>
+ {
+ new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
+ if (!state.HasSeparator)
+ destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
+ new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.HasSeparator ? 0 : 1)));
+ });
+ }
+ }
+
+ private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
+ {
+ Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths");
+
+ bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
+ || PathInternal.IsDirectorySeparator(second[0]);
+ bool thirdHasSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1])
+ || PathInternal.IsDirectorySeparator(third[0]);
+
+ fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third))
+ {
+ return string.Create(
+ first.Length + second.Length + third.Length + (firstHasSeparator ? 0 : 1) + (thirdHasSeparator ? 0 : 1),
+ (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length,
+ Third: (IntPtr)t, ThirdLength: third.Length, FirstHasSeparator: firstHasSeparator, ThirdHasSeparator: thirdHasSeparator),
+ (destination, state) =>
+ {
+ new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
+ if (!state.FirstHasSeparator)
+ destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
+ new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.FirstHasSeparator ? 0 : 1)));
+ if (!state.ThirdHasSeparator)
+ destination[destination.Length - state.ThirdLength - 1] = PathInternal.DirectorySeparatorChar;
+ new Span<char>((char*)state.Third, state.ThirdLength).CopyTo(destination.Slice(destination.Length - state.ThirdLength));
+ });
+ }
+ }
+
+ private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
+ {
+ Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths");
+
+ bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
+ || PathInternal.IsDirectorySeparator(second[0]);
+ bool thirdHasSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1])
+ || PathInternal.IsDirectorySeparator(third[0]);
+ bool fourthHasSeparator = PathInternal.IsDirectorySeparator(third[third.Length - 1])
+ || PathInternal.IsDirectorySeparator(fourth[0]);
+
+ fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third), u = &MemoryMarshal.GetReference(fourth))
+ {
+ return string.Create(
+ first.Length + second.Length + third.Length + fourth.Length + (firstHasSeparator ? 0 : 1) + (thirdHasSeparator ? 0 : 1) + (fourthHasSeparator ? 0 : 1),
+ (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length,
+ Third: (IntPtr)t, ThirdLength: third.Length, Fourth: (IntPtr)u, FourthLength:fourth.Length,
+ FirstHasSeparator: firstHasSeparator, ThirdHasSeparator: thirdHasSeparator, FourthHasSeparator: fourthHasSeparator),
+ (destination, state) =>
+ {
+ new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
+ if (!state.FirstHasSeparator)
+ destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
+ new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.FirstHasSeparator ? 0 : 1)));
+ if (!state.ThirdHasSeparator)
+ destination[state.FirstLength + state.SecondLength + (state.FirstHasSeparator ? 0 : 1)] = PathInternal.DirectorySeparatorChar;
+ new Span<char>((char*)state.Third, state.ThirdLength).CopyTo(destination.Slice(state.FirstLength + state.SecondLength + (state.FirstHasSeparator ? 0 : 1) + (state.ThirdHasSeparator ? 0 : 1)));
+ if (!state.FourthHasSeparator)
+ destination[destination.Length - state.FourthLength - 1] = PathInternal.DirectorySeparatorChar;
+ new Span<char>((char*)state.Fourth, state.FourthLength).CopyTo(destination.Slice(destination.Length - state.FourthLength));
+ });
+ }
+ }
+
+ private static readonly char[] s_base32Char = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5'};
+
+ private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ Debug.Assert(bytes != null);
+ Debug.Assert(chars != null);
+
+ // This method requires bytes of length 8 and chars of length 12.
+ Debug.Assert(byteCount == 8, $"Unexpected {nameof(byteCount)}");
+ Debug.Assert(charCount == 12, $"Unexpected {nameof(charCount)}");
+
+ byte b0 = bytes[0];
+ byte b1 = bytes[1];
+ byte b2 = bytes[2];
+ byte b3 = bytes[3];
+ byte b4 = bytes[4];
+
+ // Consume the 5 Least significant bits of the first 5 bytes
+ chars[0] = s_base32Char[b0 & 0x1F];
+ chars[1] = s_base32Char[b1 & 0x1F];
+ chars[2] = s_base32Char[b2 & 0x1F];
+ chars[3] = s_base32Char[b3 & 0x1F];
+ chars[4] = s_base32Char[b4 & 0x1F];
+
+ // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4
+ chars[5] = s_base32Char[(
+ ((b0 & 0xE0) >> 5) |
+ ((b3 & 0x60) >> 2))];
+
+ chars[6] = s_base32Char[(
+ ((b1 & 0xE0) >> 5) |
+ ((b4 & 0x60) >> 2))];
+
+ // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4
+ b2 >>= 5;
+
+ Debug.Assert(((b2 & 0xF8) == 0), "Unexpected set bits");
+
+ if ((b3 & 0x80) != 0)
+ b2 |= 0x08;
+ if ((b4 & 0x80) != 0)
+ b2 |= 0x10;
+
+ chars[7] = s_base32Char[b2];
+
+ // Set the file extension separator
+ chars[8] = '.';
+
+ // Consume the 5 Least significant bits of the remaining 3 bytes
+ chars[9] = s_base32Char[(bytes[5] & 0x1F)];
+ chars[10] = s_base32Char[(bytes[6] & 0x1F)];
+ chars[11] = s_base32Char[(bytes[7] & 0x1F)];
+ }
+
+ /// <summary>
+ /// Create a relative path from one path to another. Paths will be resolved before calculating the difference.
+ /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix).
+ /// </summary>
+ /// <param name="relativeTo">The source path the output should be relative to. This path is always considered to be a directory.</param>
+ /// <param name="path">The destination path.</param>
+ /// <returns>The relative path or <paramref name="path"/> if the paths don't share the same root.</returns>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
+ public static string GetRelativePath(string relativeTo, string path)
+ {
+ return GetRelativePath(relativeTo, path, StringComparison);
+ }
+
+ private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
+ {
+ if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo));
+ if (PathInternal.IsEffectivelyEmpty(path)) throw new ArgumentNullException(nameof(path));
+ Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
+
+ relativeTo = GetFullPath(relativeTo);
+ path = GetFullPath(path);
+
+ // Need to check if the roots are different- if they are we need to return the "to" path.
+ if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType))
+ return path;
+
+ int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);
+
+ // If there is nothing in common they can't share the same root, return the "to" path as is.
+ if (commonLength == 0)
+ return path;
+
+ // Trailing separators aren't significant for comparison
+ int relativeToLength = relativeTo.Length;
+ if (PathInternal.EndsInDirectorySeparator(relativeTo))
+ relativeToLength--;
+
+ bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path);
+ int pathLength = path.Length;
+ if (pathEndsInSeparator)
+ pathLength--;
+
+ // If we have effectively the same path, return "."
+ if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";
+
+ // We have the same root, we need to calculate the difference now using the
+ // common Length and Segment count past the length.
+ //
+ // Some examples:
+ //
+ // C:\Foo C:\Bar L3, S1 -> ..\Bar
+ // C:\Foo C:\Foo\Bar L6, S0 -> Bar
+ // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
+ // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
+
+ StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));
+
+ // Add parent segments for segments past the common on the "from" path
+ if (commonLength < relativeToLength)
+ {
+ sb.Append(PathInternal.ParentDirectoryPrefix);
+
+ for (int i = commonLength; i < relativeToLength; i++)
+ {
+ if (PathInternal.IsDirectorySeparator(relativeTo[i]))
+ {
+ sb.Append(PathInternal.ParentDirectoryPrefix);
+ }
+ }
+ }
+ else if (PathInternal.IsDirectorySeparator(path[commonLength]))
+ {
+ // No parent segments and we need to eat the initial separator
+ // (C:\Foo C:\Foo\Bar case)
+ commonLength++;
+ }
+
+ // Now add the rest of the "to" path, adding back the trailing separator
+ int count = pathLength - commonLength;
+ if (pathEndsInSeparator)
+ count++;
+
+ sb.Append(path, commonLength, count);
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
+ /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
+ internal static StringComparison StringComparison
+ {
+ get
+ {
+ return IsCaseSensitive ?
+ StringComparison.Ordinal :
+ StringComparison.OrdinalIgnoreCase;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs
new file mode 100644
index 0000000000..ed49422c1a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs
@@ -0,0 +1,252 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.IO
+{
+ /// <summary>
+ /// Wrapper to help with path normalization.
+ /// </summary>
+ internal class PathHelper
+ {
+ /// <summary>
+ /// Normalize the given path.
+ /// </summary>
+ /// <remarks>
+ /// Normalizes via Win32 GetFullPathName().
+ /// </remarks>
+ /// <param name="path">Path to normalize</param>
+ /// <exception cref="PathTooLongException">Thrown if we have a string that is too large to fit into a UNICODE_STRING.</exception>
+ /// <exception cref="IOException">Thrown if the path is empty.</exception>
+ /// <returns>Normalized path</returns>
+ internal static string Normalize(string path)
+ {
+ Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath];
+ var builder = new ValueStringBuilder(initialBuffer);
+
+ // Get the full path
+ GetFullPathName(path.AsSpan(), ref builder);
+
+ // If we have the exact same string we were passed in, don't allocate another string.
+ // TryExpandShortName does this input identity check.
+ string result = builder.AsSpan().IndexOf('~') >= 0
+ ? TryExpandShortFileName(ref builder, originalPath: path)
+ : builder.AsSpan().Equals(path.AsSpan(), StringComparison.Ordinal) ? path : builder.ToString();
+
+ // Clear the buffer
+ builder.Dispose();
+ return result;
+ }
+
+ /// <summary>
+ /// Normalize the given path.
+ /// </summary>
+ /// <remarks>
+ /// Exceptions are the same as the string overload.
+ /// </remarks>
+ internal static string Normalize(ref ValueStringBuilder path)
+ {
+ Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath];
+ var builder = new ValueStringBuilder(initialBuffer);
+
+ // Get the full path
+ GetFullPathName(path.AsSpan(terminate: true), ref builder);
+
+ string result = builder.AsSpan().IndexOf('~') >= 0
+ ? TryExpandShortFileName(ref builder, originalPath: null)
+ : builder.ToString();
+
+ // Clear the buffer
+ builder.Dispose();
+ return result;
+ }
+
+ /// <summary>
+ /// Calls GetFullPathName on the given path.
+ /// </summary>
+ /// <param name="path">The path name. MUST be null terminated after the span.</param>
+ private static void GetFullPathName(ReadOnlySpan<char> path, ref ValueStringBuilder builder)
+ {
+ // If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as
+ // it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here.
+ Debug.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path));
+
+ uint result = 0;
+ while ((result = Interop.Kernel32.GetFullPathNameW(ref MemoryMarshal.GetReference(path), (uint)builder.Capacity, ref builder.GetPinnableReference(), IntPtr.Zero)) > builder.Capacity)
+ {
+ // Reported size is greater than the buffer size. Increase the capacity.
+ builder.EnsureCapacity(checked((int)result));
+ }
+
+ if (result == 0)
+ {
+ // Failure, get the error and throw
+ int errorCode = Marshal.GetLastWin32Error();
+ if (errorCode == 0)
+ errorCode = Interop.Errors.ERROR_BAD_PATHNAME;
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, path.ToString());
+ }
+
+ builder.Length = (int)result;
+ }
+
+ internal static int PrependDevicePathChars(ref ValueStringBuilder content, bool isDosUnc, ref ValueStringBuilder buffer)
+ {
+ int length = content.Length;
+
+ length += isDosUnc
+ ? PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength
+ : PathInternal.DevicePrefixLength;
+
+ buffer.EnsureCapacity(length + 1);
+ buffer.Length = 0;
+
+ if (isDosUnc)
+ {
+ // Is a \\Server\Share, put \\?\UNC\ in the front
+ buffer.Append(PathInternal.UncExtendedPathPrefix);
+
+ // Copy Server\Share\... over to the buffer
+ buffer.Append(content.AsSpan(PathInternal.UncPrefixLength));
+
+ // Return the prefix difference
+ return PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength;
+ }
+ else
+ {
+ // Not an UNC, put the \\?\ prefix in front, then the original string
+ buffer.Append(PathInternal.ExtendedPathPrefix);
+ buffer.Append(content.AsSpan());
+ return PathInternal.DevicePrefixLength;
+ }
+ }
+
+ internal static string TryExpandShortFileName(ref ValueStringBuilder outputBuilder, string originalPath)
+ {
+ // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
+ // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.
+
+ Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuilder.AsSpan()), "should have resolved by now");
+
+ // We'll have one of a few cases by now (the normalized path will have already:
+ //
+ // 1. Dos path (C:\)
+ // 2. Dos UNC (\\Server\Share)
+ // 3. Dos device path (\\.\C:\, \\?\C:\)
+ //
+ // We want to put the extended syntax on the front if it doesn't already have it (for long path support and speed), which may mean switching from \\.\.
+ //
+ // Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).
+
+ int rootLength = PathInternal.GetRootLength(outputBuilder.AsSpan());
+ bool isDevice = PathInternal.IsDevice(outputBuilder.AsSpan());
+
+ // As this is a corner case we're not going to add a stackalloc here to keep the stack pressure down.
+ var inputBuilder = new ValueStringBuilder();
+
+ bool isDosUnc = false;
+ int rootDifference = 0;
+ bool wasDotDevice = false;
+
+ // Add the extended prefix before expanding to allow growth over MAX_PATH
+ if (isDevice)
+ {
+ // We have one of the following (\\?\ or \\.\)
+ inputBuilder.Append(outputBuilder.AsSpan());
+
+ if (outputBuilder[2] == '.')
+ {
+ wasDotDevice = true;
+ inputBuilder[2] = '?';
+ }
+ }
+ else
+ {
+ isDosUnc = !PathInternal.IsDevice(outputBuilder.AsSpan()) && outputBuilder.Length > 1 && outputBuilder[0] == '\\' && outputBuilder[1] == '\\';
+ rootDifference = PrependDevicePathChars(ref outputBuilder, isDosUnc, ref inputBuilder);
+ }
+
+ rootLength += rootDifference;
+ int inputLength = inputBuilder.Length;
+
+ bool success = false;
+ int foundIndex = inputBuilder.Length - 1;
+
+ while (!success)
+ {
+ uint result = Interop.Kernel32.GetLongPathNameW(
+ ref inputBuilder.GetPinnableReference(terminate: true), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity);
+
+ // Replace any temporary null we added
+ if (inputBuilder[foundIndex] == '\0') inputBuilder[foundIndex] = '\\';
+
+ if (result == 0)
+ {
+ // Look to see if we couldn't find the file
+ int error = Marshal.GetLastWin32Error();
+ if (error != Interop.Errors.ERROR_FILE_NOT_FOUND && error != Interop.Errors.ERROR_PATH_NOT_FOUND)
+ {
+ // Some other failure, give up
+ break;
+ }
+
+ // We couldn't find the path at the given index, start looking further back in the string.
+ foundIndex--;
+
+ for (; foundIndex > rootLength && inputBuilder[foundIndex] != '\\'; foundIndex--) ;
+ if (foundIndex == rootLength)
+ {
+ // Can't trim the path back any further
+ break;
+ }
+ else
+ {
+ // Temporarily set a null in the string to get Windows to look further up the path
+ inputBuilder[foundIndex] = '\0';
+ }
+ }
+ else if (result > outputBuilder.Capacity)
+ {
+ // Not enough space. The result count for this API does not include the null terminator.
+ outputBuilder.EnsureCapacity(checked((int)result));
+ result = Interop.Kernel32.GetLongPathNameW(ref inputBuilder.GetPinnableReference(), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity);
+ }
+ else
+ {
+ // Found the path
+ success = true;
+ outputBuilder.Length = checked((int)result);
+ if (foundIndex < inputLength - 1)
+ {
+ // It was a partial find, put the non-existent part of the path back
+ outputBuilder.Append(inputBuilder.AsSpan(foundIndex, inputBuilder.Length - foundIndex));
+ }
+ }
+ }
+
+ // If we were able to expand the path, use it, otherwise use the original full path result
+ ref ValueStringBuilder builderToUse = ref (success ? ref outputBuilder : ref inputBuilder);
+
+ // Switch back from \\?\ to \\.\ if necessary
+ if (wasDotDevice)
+ builderToUse[2] = '.';
+
+ // Change from \\?\UNC\ to \\?\UN\\ if needed
+ if (isDosUnc)
+ builderToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\';
+
+ // Strip out any added characters at the front of the string
+ ReadOnlySpan<char> output = builderToUse.AsSpan(rootDifference);
+
+ string returnValue = ((originalPath != null) && output.Equals(originalPath.AsSpan(), StringComparison.Ordinal))
+ ? originalPath : new string(output);
+
+ inputBuilder.Dispose();
+ return returnValue;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs
new file mode 100644
index 0000000000..fae309be56
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs
@@ -0,0 +1,102 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace System.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static partial class PathInternal
+ {
+ internal const char DirectorySeparatorChar = '/';
+ internal const char AltDirectorySeparatorChar = '/';
+ internal const char VolumeSeparatorChar = '/';
+ internal const char PathSeparator = ':';
+
+ internal const string DirectorySeparatorCharAsString = "/";
+
+ // There is only one invalid path character in Unix
+ private const char InvalidPathChar = '\0';
+
+ internal const string ParentDirectoryPrefix = @"../";
+
+ internal static int GetRootLength(ReadOnlySpan<char> path)
+ {
+ return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
+ }
+
+ internal static bool IsDirectorySeparator(char c)
+ {
+ // The alternate directory separator char is the same as the directory separator,
+ // so we only need to check one.
+ Debug.Assert(DirectorySeparatorChar == AltDirectorySeparatorChar);
+ return c == DirectorySeparatorChar;
+ }
+
+ /// <summary>
+ /// Normalize separators in the given path. Compresses forward slash runs.
+ /// </summary>
+ internal static string NormalizeDirectorySeparators(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ return path;
+
+ // Make a pass to see if we need to normalize so we can potentially skip allocating
+ bool normalized = true;
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ if (IsDirectorySeparator(path[i])
+ && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))
+ {
+ normalized = false;
+ break;
+ }
+ }
+
+ if (normalized)
+ return path;
+
+ StringBuilder builder = new StringBuilder(path.Length);
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ char current = path[i];
+
+ // Skip if we have another separator following
+ if (IsDirectorySeparator(current)
+ && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))
+ continue;
+
+ builder.Append(current);
+ }
+
+ return builder.ToString();
+ }
+
+ internal static bool IsPartiallyQualified(ReadOnlySpan<char> path)
+ {
+ // This is much simpler than Windows where paths can be rooted, but not fully qualified (such as Drive Relative)
+ // As long as the path is rooted in Unix it doesn't use the current directory and therefore is fully qualified.
+ return !Path.IsPathRooted(path);
+ }
+
+ /// <summary>
+ /// Returns true if the path is effectively empty for the current OS.
+ /// For unix, this is empty or null. For Windows, this is empty, null, or
+ /// just spaces ((char)32).
+ /// </summary>
+ internal static bool IsEffectivelyEmpty(string path)
+ {
+ return string.IsNullOrEmpty(path);
+ }
+
+ internal static bool IsEffectivelyEmpty(ReadOnlySpan<char> path)
+ {
+ return path.IsEmpty;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs
new file mode 100644
index 0000000000..b01482abd2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs
@@ -0,0 +1,428 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace System.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static partial class PathInternal
+ {
+ // All paths in Win32 ultimately end up becoming a path to a File object in the Windows object manager. Passed in paths get mapped through
+ // DosDevice symbolic links in the object tree to actual File objects under \Devices. To illustrate, this is what happens with a typical
+ // path "Foo" passed as a filename to any Win32 API:
+ //
+ // 1. "Foo" is recognized as a relative path and is appended to the current directory (say, "C:\" in our example)
+ // 2. "C:\Foo" is prepended with the DosDevice namespace "\??\"
+ // 3. CreateFile tries to create an object handle to the requested file "\??\C:\Foo"
+ // 4. The Object Manager recognizes the DosDevices prefix and looks
+ // a. First in the current session DosDevices ("\Sessions\1\DosDevices\" for example, mapped network drives go here)
+ // b. If not found in the session, it looks in the Global DosDevices ("\GLOBAL??\")
+ // 5. "C:" is found in DosDevices (in our case "\GLOBAL??\C:", which is a symbolic link to "\Device\HarddiskVolume6")
+ // 6. The full path is now "\Device\HarddiskVolume6\Foo", "\Device\HarddiskVolume6" is a File object and parsing is handed off
+ // to the registered parsing method for Files
+ // 7. The registered open method for File objects is invoked to create the file handle which is then returned
+ //
+ // There are multiple ways to directly specify a DosDevices path. The final format of "\??\" is one way. It can also be specified
+ // as "\\.\" (the most commonly documented way) and "\\?\". If the question mark syntax is used the path will skip normalization
+ // (essentially GetFullPathName()) and path length checks.
+
+ // Windows Kernel-Mode Object Manager
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff565763.aspx
+ // https://channel9.msdn.com/Shows/Going+Deep/Windows-NT-Object-Manager
+ //
+ // Introduction to MS-DOS Device Names
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff548088.aspx
+ //
+ // Local and Global MS-DOS Device Names
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff554302.aspx
+
+ internal const char DirectorySeparatorChar = '\\';
+ internal const char AltDirectorySeparatorChar = '/';
+ internal const char VolumeSeparatorChar = ':';
+ internal const char PathSeparator = ';';
+
+ internal const string DirectorySeparatorCharAsString = "\\";
+
+ internal const string ExtendedPathPrefix = @"\\?\";
+ internal const string UncPathPrefix = @"\\";
+ internal const string UncExtendedPrefixToInsert = @"?\UNC\";
+ internal const string UncExtendedPathPrefix = @"\\?\UNC\";
+ internal const string DevicePathPrefix = @"\\.\";
+ internal const string ParentDirectoryPrefix = @"..\";
+
+ internal const int MaxShortPath = 260;
+ internal const int MaxShortDirectoryPath = 248;
+ // \\?\, \\.\, \??\
+ internal const int DevicePrefixLength = 4;
+ // \\
+ internal const int UncPrefixLength = 2;
+ // \\?\UNC\, \\.\UNC\
+ internal const int UncExtendedPrefixLength = 8;
+
+ /// <summary>
+ /// Returns true if the given character is a valid drive letter
+ /// </summary>
+ internal static bool IsValidDriveChar(char value)
+ {
+ return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));
+ }
+
+ internal static bool EndsWithPeriodOrSpace(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ return false;
+
+ char c = path[path.Length - 1];
+ return c == ' ' || c == '.';
+ }
+
+ /// <summary>
+ /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative,
+ /// AND the path is more than 259 characters. (> MAX_PATH + null). This will also insert the extended
+ /// prefix if the path ends with a period or a space. Trailing periods and spaces are normally eaten
+ /// away from paths during normalization, but if we see such a path at this point it should be
+ /// normalized and has retained the final characters. (Typically from one of the *Info classes)
+ /// </summary>
+ internal static string EnsureExtendedPrefixIfNeeded(string path)
+ {
+ if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path)))
+ {
+ return EnsureExtendedPrefix(path);
+ }
+ else
+ {
+ return path;
+ }
+ }
+
+ /// <summary>
+ /// DO NOT USE- Use EnsureExtendedPrefixIfNeeded. This will be removed shortly.
+ /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative,
+ /// AND the path is more than 259 characters. (> MAX_PATH + null)
+ /// </summary>
+ internal static string EnsureExtendedPrefixOverMaxPath(string path)
+ {
+ if (path != null && path.Length >= MaxShortPath)
+ {
+ return EnsureExtendedPrefix(path);
+ }
+ else
+ {
+ return path;
+ }
+ }
+
+ /// <summary>
+ /// Adds the extended path prefix (\\?\) if not relative or already a device path.
+ /// </summary>
+ internal static string EnsureExtendedPrefix(string path)
+ {
+ // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which
+ // means adding to relative paths will prevent them from getting the appropriate current directory inserted.
+
+ // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it
+ // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly
+ // in the future we wouldn't want normalization to come back and break existing code.
+
+ // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this
+ // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a
+ // normalized base path.)
+ if (IsPartiallyQualified(path) || IsDevice(path))
+ return path;
+
+ // Given \\server\share in longpath becomes \\?\UNC\server\share
+ if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase))
+ return path.Insert(2, UncExtendedPrefixToInsert);
+
+ return ExtendedPathPrefix + path;
+ }
+
+ /// <summary>
+ /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
+ /// </summary>
+ internal static bool IsDevice(ReadOnlySpan<char> path)
+ {
+ // If the path begins with any two separators is will be recognized and normalized and prepped with
+ // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
+ return IsExtended(path)
+ ||
+ (
+ path.Length >= DevicePrefixLength
+ && IsDirectorySeparator(path[0])
+ && IsDirectorySeparator(path[1])
+ && (path[2] == '.' || path[2] == '?')
+ && IsDirectorySeparator(path[3])
+ );
+ }
+
+ /// <summary>
+ /// Returns true if the path is a device UNC (\\?\UNC\, \\.\UNC\)
+ /// </summary>
+ internal static bool IsDeviceUNC(ReadOnlySpan<char> path)
+ {
+ return path.Length >= UncExtendedPrefixLength
+ && IsDevice(path)
+ && IsDirectorySeparator(path[7])
+ && path[4] == 'U'
+ && path[5] == 'N'
+ && path[6] == 'C';
+ }
+
+ /// <summary>
+ /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
+ /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
+ /// and path length checks.
+ /// </summary>
+ internal static bool IsExtended(ReadOnlySpan<char> path)
+ {
+ // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
+ // Skipping of normalization will *only* occur if back slashes ('\') are used.
+ return path.Length >= DevicePrefixLength
+ && path[0] == '\\'
+ && (path[1] == '\\' || path[1] == '?')
+ && path[2] == '?'
+ && path[3] == '\\';
+ }
+
+ /// <summary>
+ /// Check for known wildcard characters. '*' and '?' are the most common ones.
+ /// </summary>
+ internal static bool HasWildCardCharacters(ReadOnlySpan<char> path)
+ {
+ // Question mark is part of dos device syntax so we have to skip if we are
+ int startIndex = IsDevice(path) ? ExtendedPathPrefix.Length : 0;
+
+ // [MS - FSA] 2.1.4.4 Algorithm for Determining if a FileName Is in an Expression
+ // https://msdn.microsoft.com/en-us/library/ff469270.aspx
+ for (int i = startIndex; i < path.Length; i++)
+ {
+ char c = path[i];
+ if (c <= '?') // fast path for common case - '?' is highest wildcard character
+ {
+ if (c == '\"' || c == '<' || c == '>' || c == '*' || c == '?')
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Gets the length of the root of the path (drive, share, etc.).
+ /// </summary>
+ internal static int GetRootLength(ReadOnlySpan<char> path)
+ {
+ int pathLength = path.Length;
+ int i = 0;
+
+ bool deviceSyntax = IsDevice(path);
+ bool deviceUnc = deviceSyntax && IsDeviceUNC(path);
+
+ if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator(path[0]))
+ {
+ // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
+ if (deviceUnc || (pathLength > 1 && IsDirectorySeparator(path[1])))
+ {
+ // UNC (\\?\UNC\ or \\), scan past server\share
+
+ // Start past the prefix ("\\" or "\\?\UNC\")
+ i = deviceUnc ? UncExtendedPrefixLength : UncPrefixLength;
+
+ // Skip two separators at most
+ int n = 2;
+ while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0))
+ i++;
+ }
+ else
+ {
+ // Current drive rooted (e.g. "\foo")
+ i = 1;
+ }
+ }
+ else if (deviceSyntax)
+ {
+ // Device path (e.g. "\\?\.", "\\.\")
+ // Skip any characters following the prefix that aren't a separator
+ i = DevicePrefixLength;
+ while (i < pathLength && !IsDirectorySeparator(path[i]))
+ i++;
+
+ // If there is another separator take it, as long as we have had at least one
+ // non-separator after the prefix (e.g. don't take "\\?\\", but take "\\?\a\")
+ if (i < pathLength && i > DevicePrefixLength && IsDirectorySeparator(path[i]))
+ i++;
+ }
+ else if (pathLength >= 2
+ && path[1] == VolumeSeparatorChar
+ && IsValidDriveChar(path[0]))
+ {
+ // Valid drive specified path ("C:", "D:", etc.)
+ i = 2;
+
+ // If the colon is followed by a directory separator, move past it (e.g "C:\")
+ if (pathLength > 2 && IsDirectorySeparator(path[2]))
+ i++;
+ }
+
+ return i;
+ }
+
+ /// <summary>
+ /// Returns true if the path specified is relative to the current drive or working directory.
+ /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
+ /// validation of the path (URIs will be returned as relative as a result).
+ /// </summary>
+ /// <remarks>
+ /// Handles paths that use the alternate directory separator. It is a frequent mistake to
+ /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
+ /// "C:a" is drive relative- meaning that it will be resolved against the current directory
+ /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
+ /// will not be used to modify the path).
+ /// </remarks>
+ internal static bool IsPartiallyQualified(ReadOnlySpan<char> path)
+ {
+ if (path.Length < 2)
+ {
+ // It isn't fixed, it must be relative. There is no way to specify a fixed
+ // path with one character (or less).
+ return true;
+ }
+
+ if (IsDirectorySeparator(path[0]))
+ {
+ // There is no valid way to specify a relative path with two initial slashes or
+ // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
+ return !(path[1] == '?' || IsDirectorySeparator(path[1]));
+ }
+
+ // The only way to specify a fixed path that doesn't begin with two slashes
+ // is the drive, colon, slash format- i.e. C:\
+ return !((path.Length >= 3)
+ && (path[1] == VolumeSeparatorChar)
+ && IsDirectorySeparator(path[2])
+ // To match old behavior we'll check the drive character for validity as the path is technically
+ // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
+ && IsValidDriveChar(path[0]));
+ }
+
+ /// <summary>
+ /// True if the given character is a directory separator.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool IsDirectorySeparator(char c)
+ {
+ return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
+ }
+
+ /// <summary>
+ /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present.
+ /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip).
+ ///
+ /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false.
+ /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as
+ /// such can't be used here (and is overkill for our uses).
+ ///
+ /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments.
+ /// </summary>
+ /// <remarks>
+ /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do
+ /// not need trimming of trailing whitespace here.
+ ///
+ /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization.
+ ///
+ /// For legacy desktop behavior with ExpandShortPaths:
+ /// - It has no impact on GetPathRoot() so doesn't need consideration.
+ /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share).
+ ///
+ /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was
+ /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you
+ /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by
+ /// this undocumented behavior.
+ ///
+ /// We won't match this old behavior because:
+ ///
+ /// 1. It was undocumented
+ /// 2. It was costly (extremely so if it actually contained '~')
+ /// 3. Doesn't play nice with string logic
+ /// 4. Isn't a cross-plat friendly concept/behavior
+ /// </remarks>
+ internal static string NormalizeDirectorySeparators(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ return path;
+
+ char current;
+
+ // Make a pass to see if we need to normalize so we can potentially skip allocating
+ bool normalized = true;
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ current = path[i];
+ if (IsDirectorySeparator(current)
+ && (current != DirectorySeparatorChar
+ // Check for sequential separators past the first position (we need to keep initial two for UNC/extended)
+ || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))))
+ {
+ normalized = false;
+ break;
+ }
+ }
+
+ if (normalized)
+ return path;
+
+ StringBuilder builder = new StringBuilder(path.Length);
+
+ int start = 0;
+ if (IsDirectorySeparator(path[start]))
+ {
+ start++;
+ builder.Append(DirectorySeparatorChar);
+ }
+
+ for (int i = start; i < path.Length; i++)
+ {
+ current = path[i];
+
+ // If we have a separator
+ if (IsDirectorySeparator(current))
+ {
+ // If the next is a separator, skip adding this
+ if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))
+ {
+ continue;
+ }
+
+ // Ensure it is the primary separator
+ current = DirectorySeparatorChar;
+ }
+
+ builder.Append(current);
+ }
+
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// Returns true if the path is effectively empty for the current OS.
+ /// For unix, this is empty or null. For Windows, this is empty, null, or
+ /// just spaces ((char)32).
+ /// </summary>
+ internal static bool IsEffectivelyEmpty(ReadOnlySpan<char> path)
+ {
+ if (path.IsEmpty)
+ return true;
+
+ foreach (char c in path)
+ {
+ if (c != ' ')
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs
new file mode 100644
index 0000000000..00cb12e920
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs
@@ -0,0 +1,208 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static partial class PathInternal
+ {
+ /// <summary>
+ /// Returns true if the path ends in a directory separator.
+ /// </summary>
+ internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
+ => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
+
+ /// <summary>
+ /// Returns true if the path starts in a directory separator.
+ /// </summary>
+ internal static bool StartsWithDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[0]);
+
+ internal static string EnsureTrailingSeparator(string path)
+ => EndsInDirectorySeparator(path) ? path : path + DirectorySeparatorCharAsString;
+
+ internal static string TrimEndingDirectorySeparator(string path) =>
+ EndsInDirectorySeparator(path) && !IsRoot(path) ?
+ path.Substring(0, path.Length - 1) :
+ path;
+
+ internal static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
+ EndsInDirectorySeparator(path) && !IsRoot(path) ?
+ path.Slice(0, path.Length - 1) :
+ path;
+
+ internal static bool IsRoot(ReadOnlySpan<char> path)
+ => path.Length == GetRootLength(path);
+
+ /// <summary>
+ /// Get the common path length from the start of the string.
+ /// </summary>
+ internal static int GetCommonPathLength(string first, string second, bool ignoreCase)
+ {
+ int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase);
+
+ // If nothing matches
+ if (commonChars == 0)
+ return commonChars;
+
+ // Or we're a full string and equal length or match to a separator
+ if (commonChars == first.Length
+ && (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
+ return commonChars;
+
+ if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
+ return commonChars;
+
+ // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
+ while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
+ commonChars--;
+
+ return commonChars;
+ }
+
+ /// <summary>
+ /// Gets the count of common characters from the left optionally ignoring case
+ /// </summary>
+ internal static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase)
+ {
+ if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0;
+
+ int commonChars = 0;
+
+ fixed (char* f = first)
+ fixed (char* s = second)
+ {
+ char* l = f;
+ char* r = s;
+ char* leftEnd = l + first.Length;
+ char* rightEnd = r + second.Length;
+
+ while (l != leftEnd && r != rightEnd
+ && (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r)))))
+ {
+ commonChars++;
+ l++;
+ r++;
+ }
+ }
+
+ return commonChars;
+ }
+
+ /// <summary>
+ /// Returns true if the two paths have the same root
+ /// </summary>
+ internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType)
+ {
+ int firstRootLength = GetRootLength(first);
+ int secondRootLength = GetRootLength(second);
+
+ return firstRootLength == secondRootLength
+ && string.Compare(
+ strA: first,
+ indexA: 0,
+ strB: second,
+ indexB: 0,
+ length: firstRootLength,
+ comparisonType: comparisonType) == 0;
+ }
+
+ /// <summary>
+ /// Try to remove relative segments from the given path (without combining with a root).
+ /// </summary>
+ /// <param name="rootLength">The length of the root of the given path</param>
+ internal static string RemoveRelativeSegments(string path, int rootLength)
+ {
+ Debug.Assert(rootLength > 0);
+ bool flippedSeparator = false;
+
+ int skip = rootLength;
+ // We treat "\.." , "\." and "\\" as a relative segment. We want to collapse the first separator past the root presuming
+ // the root actually ends in a separator. Otherwise the first segment for RemoveRelativeSegments
+ // in cases like "\\?\C:\.\" and "\\?\C:\..\", the first segment after the root will be ".\" and "..\" which is not considered as a relative segment and hence not be removed.
+ if (PathInternal.IsDirectorySeparator(path[skip - 1]))
+ skip--;
+
+ Span<char> initialBuffer = stackalloc char[260 /* PathInternal.MaxShortPath */];
+ ValueStringBuilder sb = new ValueStringBuilder(initialBuffer);
+
+ // Remove "//", "/./", and "/../" from the path by copying each character to the output,
+ // except the ones we're removing, such that the builder contains the normalized path
+ // at the end.
+ if (skip > 0)
+ {
+ sb.Append(path.AsSpan(0, skip));
+ }
+
+ for (int i = skip; i < path.Length; i++)
+ {
+ char c = path[i];
+
+ if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length)
+ {
+ // Skip this character if it's a directory separator and if the next character is, too,
+ // e.g. "parent//child" => "parent/child"
+ if (PathInternal.IsDirectorySeparator(path[i + 1]))
+ {
+ continue;
+ }
+
+ // Skip this character and the next if it's referring to the current directory,
+ // e.g. "parent/./child" => "parent/child"
+ if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) &&
+ path[i + 1] == '.')
+ {
+ i++;
+ continue;
+ }
+
+ // Skip this character and the next two if it's referring to the parent directory,
+ // e.g. "parent/child/../grandchild" => "parent/grandchild"
+ if (i + 2 < path.Length &&
+ (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) &&
+ path[i + 1] == '.' && path[i + 2] == '.')
+ {
+ // Unwind back to the last slash (and if there isn't one, clear out everything).
+ int s;
+ for (s = sb.Length - 1; s >= skip; s--)
+ {
+ if (PathInternal.IsDirectorySeparator(sb[s]))
+ {
+ sb.Length = (i + 3 >= path.Length && s == skip) ? s + 1 : s; // to avoid removing the complete "\tmp\" segment in cases like \\?\C:\tmp\..\, C:\tmp\..
+ break;
+ }
+ }
+ if (s < skip)
+ {
+ sb.Length = skip;
+ }
+
+ i += 2;
+ continue;
+ }
+ }
+
+ // Normalize the directory separator if needed
+ if (c != PathInternal.DirectorySeparatorChar && c == PathInternal.AltDirectorySeparatorChar)
+ {
+ c = PathInternal.DirectorySeparatorChar;
+ flippedSeparator = true;
+ }
+
+ sb.Append(c);
+ }
+
+ // If we haven't changed the source path, return the original
+ if (!flippedSeparator && sb.Length == path.Length)
+ {
+ sb.Dispose();
+ return path;
+ }
+
+ return sb.Length < rootLength ? path.Substring(0, rootLength) : sb.ToString();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathTooLongException.cs b/src/System.Private.CoreLib/shared/System/IO/PathTooLongException.cs
new file mode 100644
index 0000000000..7af2452736
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/PathTooLongException.cs
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.IO
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class PathTooLongException : IOException
+ {
+ public PathTooLongException()
+ : base(SR.IO_PathTooLong)
+ {
+ HResult = HResults.COR_E_PATHTOOLONG;
+ }
+
+ public PathTooLongException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_PATHTOOLONG;
+ }
+
+ public PathTooLongException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_PATHTOOLONG;
+ }
+
+ protected PathTooLongException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/PinnedBufferMemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/PinnedBufferMemoryStream.cs
new file mode 100644
index 0000000000..94331a2ef8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/PinnedBufferMemoryStream.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: Pins a byte[], exposing it as an unmanaged memory
+** stream. Used in ResourceReader for corner cases.
+**
+**
+===========================================================*/
+
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+
+namespace System.IO
+{
+ internal sealed unsafe class PinnedBufferMemoryStream : UnmanagedMemoryStream
+ {
+ private byte[] _array;
+ private GCHandle _pinningHandle;
+
+ internal PinnedBufferMemoryStream(byte[] array)
+ {
+ Debug.Assert(array != null, "Array can't be null");
+
+ _array = array;
+ _pinningHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
+ // Now the byte[] is pinned for the lifetime of this instance.
+ // But I also need to get a pointer to that block of memory...
+ int len = array.Length;
+ fixed (byte* ptr = &MemoryMarshal.GetReference((Span<byte>)array))
+ Initialize(ptr, len, len, FileAccess.Read);
+ }
+
+ public override int Read(Span<byte> buffer) => ReadCore(buffer);
+
+ public override void Write(ReadOnlySpan<byte> buffer) => WriteCore(buffer);
+
+ ~PinnedBufferMemoryStream()
+ {
+ Dispose(false);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_pinningHandle.IsAllocated)
+ {
+ _pinningHandle.Free();
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/SeekOrigin.cs b/src/System.Private.CoreLib/shared/System/IO/SeekOrigin.cs
new file mode 100644
index 0000000000..3798a0ce70
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/SeekOrigin.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO
+{
+ // Provides seek reference points. To seek to the end of a stream,
+ // call stream.Seek(0, SeekOrigin.End).
+ public enum SeekOrigin
+ {
+ // These constants match Win32's FILE_BEGIN, FILE_CURRENT, and FILE_END
+ Begin = 0,
+ Current = 1,
+ End = 2,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/StreamHelpers.CopyValidation.cs b/src/System.Private.CoreLib/shared/System/IO/StreamHelpers.CopyValidation.cs
new file mode 100644
index 0000000000..45bbd816df
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/StreamHelpers.CopyValidation.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.IO
+{
+ /// <summary>Provides methods to help in the implementation of Stream-derived types.</summary>
+ internal static partial class StreamHelpers
+ {
+ /// <summary>Validate the arguments to CopyTo, as would Stream.CopyTo.</summary>
+ public static void ValidateCopyToArgs(Stream source, Stream destination, int bufferSize)
+ {
+ if (destination == null)
+ {
+ throw new ArgumentNullException(nameof(destination));
+ }
+
+ if (bufferSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, SR.ArgumentOutOfRange_NeedPosNum);
+ }
+
+ bool sourceCanRead = source.CanRead;
+ if (!sourceCanRead && !source.CanWrite)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed);
+ }
+
+ bool destinationCanWrite = destination.CanWrite;
+ if (!destinationCanWrite && !destination.CanRead)
+ {
+ throw new ObjectDisposedException(nameof(destination), SR.ObjectDisposed_StreamClosed);
+ }
+
+ if (!sourceCanRead)
+ {
+ throw new NotSupportedException(SR.NotSupported_UnreadableStream);
+ }
+
+ if (!destinationCanWrite)
+ {
+ throw new NotSupportedException(SR.NotSupported_UnwritableStream);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs b/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs
new file mode 100644
index 0000000000..ea30541f5a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs
@@ -0,0 +1,1422 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ // This class implements a TextReader for reading characters to a Stream.
+ // This is designed for character input in a particular Encoding,
+ // whereas the Stream class is designed for byte input and output.
+ public class StreamReader : TextReader
+ {
+ // StreamReader.Null is threadsafe.
+ public new static readonly StreamReader Null = new NullStreamReader();
+
+ // Using a 1K byte buffer and a 4K FileStream buffer works out pretty well
+ // perf-wise. On even a 40 MB text file, any perf loss by using a 4K
+ // buffer is negated by the win of allocating a smaller byte[], which
+ // saves construction time. This does break adaptive buffering,
+ // but this is slightly faster.
+ private const int DefaultBufferSize = 1024; // Byte buffer size
+ private const int DefaultFileStreamBufferSize = 4096;
+ private const int MinBufferSize = 128;
+
+ private Stream _stream;
+ private Encoding _encoding;
+ private Decoder _decoder;
+ private byte[] _byteBuffer;
+ private char[] _charBuffer;
+ private int _charPos;
+ private int _charLen;
+ // Record the number of valid bytes in the byteBuffer, for a few checks.
+ private int _byteLen;
+ // This is used only for preamble detection
+ private int _bytePos;
+
+ // This is the maximum number of chars we can get from one call to
+ // ReadBuffer. Used so ReadBuffer can tell when to copy data into
+ // a user's char[] directly, instead of our internal char[].
+ private int _maxCharsPerBuffer;
+
+ // We will support looking for byte order marks in the stream and trying
+ // to decide what the encoding might be from the byte order marks, IF they
+ // exist. But that's all we'll do.
+ private bool _detectEncoding;
+
+ // Whether we must still check for the encoding's given preamble at the
+ // beginning of this file.
+ private bool _checkPreamble;
+
+ // Whether the stream is most likely not going to give us back as much
+ // data as we want the next time we call it. We must do the computation
+ // before we do any byte order mark handling and save the result. Note
+ // that we need this to allow users to handle streams used for an
+ // interactive protocol, where they block waiting for the remote end
+ // to send a response, like logging in on a Unix machine.
+ private bool _isBlocked;
+
+ // The intent of this field is to leave open the underlying stream when
+ // disposing of this StreamReader. A name like _leaveOpen is better,
+ // but this type is serializable, and this field's name was _closable.
+ private bool _closable; // Whether to close the underlying stream.
+
+ // We don't guarantee thread safety on StreamReader, but we should at
+ // least prevent users from trying to read anything while an Async
+ // read from the same thread is in progress.
+ private Task _asyncReadTask = Task.CompletedTask;
+
+ private void CheckAsyncTaskInProgress()
+ {
+ // We are not locking the access to _asyncReadTask because this is not meant to guarantee thread safety.
+ // We are simply trying to deter calling any Read APIs while an async Read from the same thread is in progress.
+ if (!_asyncReadTask.IsCompleted)
+ {
+ ThrowAsyncIOInProgress();
+ }
+ }
+
+ private static void ThrowAsyncIOInProgress() =>
+ throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
+
+ // StreamReader by default will ignore illegal UTF8 characters. We don't want to
+ // throw here because we want to be able to read ill-formed data without choking.
+ // The high level goal is to be tolerant of encoding errors when we read and very strict
+ // when we write. Hence, default StreamWriter encoding will throw on error.
+
+ internal StreamReader()
+ {
+ }
+
+ public StreamReader(Stream stream)
+ : this(stream, true)
+ {
+ }
+
+ public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
+ : this(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
+ {
+ }
+
+ public StreamReader(Stream stream, Encoding encoding)
+ : this(stream, encoding, true, DefaultBufferSize, false)
+ {
+ }
+
+ public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks)
+ : this(stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize, false)
+ {
+ }
+
+ // Creates a new StreamReader for the given stream. The
+ // character encoding is set by encoding and the buffer size,
+ // in number of 16-bit characters, is set by bufferSize.
+ //
+ // Note that detectEncodingFromByteOrderMarks is a very
+ // loose attempt at detecting the encoding by looking at the first
+ // 3 bytes of the stream. It will recognize UTF-8, little endian
+ // unicode, and big endian unicode text, but that's it. If neither
+ // of those three match, it will use the Encoding you provided.
+ //
+ public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
+ : this(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false)
+ {
+ }
+
+ public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen)
+ {
+ if (stream == null || encoding == null)
+ {
+ throw new ArgumentNullException(stream == null ? nameof(stream) : nameof(encoding));
+ }
+ if (!stream.CanRead)
+ {
+ throw new ArgumentException(SR.Argument_StreamNotReadable);
+ }
+ if (bufferSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+ }
+
+ Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen);
+ }
+
+ public StreamReader(string path)
+ : this(path, true)
+ {
+ }
+
+ public StreamReader(string path, bool detectEncodingFromByteOrderMarks)
+ : this(path, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize)
+ {
+ }
+
+ public StreamReader(string path, Encoding encoding)
+ : this(path, encoding, true, DefaultBufferSize)
+ {
+ }
+
+ public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks)
+ : this(path, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize)
+ {
+ }
+
+ public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+ if (encoding == null)
+ throw new ArgumentNullException(nameof(encoding));
+ if (path.Length == 0)
+ throw new ArgumentException(SR.Argument_EmptyPath);
+ if (bufferSize <= 0)
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+
+ Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read,
+ DefaultFileStreamBufferSize, FileOptions.SequentialScan);
+ Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen: false);
+ }
+
+ private void Init(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen)
+ {
+ _stream = stream;
+ _encoding = encoding;
+ _decoder = encoding.GetDecoder();
+ if (bufferSize < MinBufferSize)
+ {
+ bufferSize = MinBufferSize;
+ }
+
+ _byteBuffer = new byte[bufferSize];
+ _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
+ _charBuffer = new char[_maxCharsPerBuffer];
+ _byteLen = 0;
+ _bytePos = 0;
+ _detectEncoding = detectEncodingFromByteOrderMarks;
+ _checkPreamble = encoding.Preamble.Length > 0;
+ _isBlocked = false;
+ _closable = !leaveOpen;
+ }
+
+ // Init used by NullStreamReader, to delay load encoding
+ internal void Init(Stream stream)
+ {
+ _stream = stream;
+ _closable = true;
+ }
+
+ public override void Close()
+ {
+ Dispose(true);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ // Dispose of our resources if this StreamReader is closable.
+ // Note that Console.In should be left open.
+ try
+ {
+ // Note that Stream.Close() can potentially throw here. So we need to
+ // ensure cleaning up internal resources, inside the finally block.
+ if (!LeaveOpen && disposing && (_stream != null))
+ {
+ _stream.Close();
+ }
+ }
+ finally
+ {
+ if (!LeaveOpen && (_stream != null))
+ {
+ _stream = null;
+ _encoding = null;
+ _decoder = null;
+ _byteBuffer = null;
+ _charBuffer = null;
+ _charPos = 0;
+ _charLen = 0;
+ base.Dispose(disposing);
+ }
+ }
+ }
+
+ public virtual Encoding CurrentEncoding
+ {
+ get { return _encoding; }
+ }
+
+ public virtual Stream BaseStream
+ {
+ get { return _stream; }
+ }
+
+ internal bool LeaveOpen
+ {
+ get { return !_closable; }
+ }
+
+ // DiscardBufferedData tells StreamReader to throw away its internal
+ // buffer contents. This is useful if the user needs to seek on the
+ // underlying stream to a known location then wants the StreamReader
+ // to start reading from this new point. This method should be called
+ // very sparingly, if ever, since it can lead to very poor performance.
+ // However, it may be the only way of handling some scenarios where
+ // users need to re-read the contents of a StreamReader a second time.
+ public void DiscardBufferedData()
+ {
+ CheckAsyncTaskInProgress();
+
+ _byteLen = 0;
+ _charLen = 0;
+ _charPos = 0;
+ // in general we'd like to have an invariant that encoding isn't null. However,
+ // for startup improvements for NullStreamReader, we want to delay load encoding.
+ if (_encoding != null)
+ {
+ _decoder = _encoding.GetDecoder();
+ }
+ _isBlocked = false;
+ }
+
+ public bool EndOfStream
+ {
+ get
+ {
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (_charPos < _charLen)
+ {
+ return false;
+ }
+
+ // This may block on pipes!
+ int numRead = ReadBuffer();
+ return numRead == 0;
+ }
+ }
+
+ public override int Peek()
+ {
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (_charPos == _charLen)
+ {
+ if (_isBlocked || ReadBuffer() == 0)
+ {
+ return -1;
+ }
+ }
+ return _charBuffer[_charPos];
+ }
+
+ public override int Read()
+ {
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (_charPos == _charLen)
+ {
+ if (ReadBuffer() == 0)
+ {
+ return -1;
+ }
+ }
+ int result = _charBuffer[_charPos];
+ _charPos++;
+ return result;
+ }
+
+ public override int Read(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0 || count < 0)
+ {
+ throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ return ReadSpan(new Span<char>(buffer, index, count));
+ }
+
+ public override int Read(Span<char> buffer) =>
+ GetType() == typeof(StreamReader) ? ReadSpan(buffer) :
+ base.Read(buffer); // Defer to Read(char[], ...) if a derived type may have previously overridden it
+
+ private int ReadSpan(Span<char> buffer)
+ {
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ int charsRead = 0;
+ // As a perf optimization, if we had exactly one buffer's worth of
+ // data read in, let's try writing directly to the user's buffer.
+ bool readToUserBuffer = false;
+ int count = buffer.Length;
+ while (count > 0)
+ {
+ int n = _charLen - _charPos;
+ if (n == 0)
+ {
+ n = ReadBuffer(buffer.Slice(charsRead), out readToUserBuffer);
+ }
+ if (n == 0)
+ {
+ break; // We're at EOF
+ }
+ if (n > count)
+ {
+ n = count;
+ }
+ if (!readToUserBuffer)
+ {
+ new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Slice(charsRead));
+ _charPos += n;
+ }
+
+ charsRead += n;
+ count -= n;
+ // This function shouldn't block for an indefinite amount of time,
+ // or reading from a network stream won't work right. If we got
+ // fewer bytes than we requested, then we want to break right here.
+ if (_isBlocked)
+ {
+ break;
+ }
+ }
+
+ return charsRead;
+ }
+
+ public override string ReadToEnd()
+ {
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ // Call ReadBuffer, then pull data out of charBuffer.
+ StringBuilder sb = new StringBuilder(_charLen - _charPos);
+ do
+ {
+ sb.Append(_charBuffer, _charPos, _charLen - _charPos);
+ _charPos = _charLen; // Note we consumed these characters
+ ReadBuffer();
+ } while (_charLen > 0);
+ return sb.ToString();
+ }
+
+ public override int ReadBlock(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0 || count < 0)
+ {
+ throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ return base.ReadBlock(buffer, index, count);
+ }
+
+ public override int ReadBlock(Span<char> buffer)
+ {
+ if (GetType() != typeof(StreamReader))
+ {
+ // Defer to Read(char[], ...) if a derived type may have previously overridden it.
+ return base.ReadBlock(buffer);
+ }
+
+ int i, n = 0;
+ do
+ {
+ i = ReadSpan(buffer.Slice(n));
+ n += i;
+ } while (i > 0 && n < buffer.Length);
+ return n;
+ }
+
+ // Trims n bytes from the front of the buffer.
+ private void CompressBuffer(int n)
+ {
+ Debug.Assert(_byteLen >= n, "CompressBuffer was called with a number of bytes greater than the current buffer length. Are two threads using this StreamReader at the same time?");
+ Buffer.BlockCopy(_byteBuffer, n, _byteBuffer, 0, _byteLen - n);
+ _byteLen -= n;
+ }
+
+ private void DetectEncoding()
+ {
+ if (_byteLen < 2)
+ {
+ return;
+ }
+ _detectEncoding = false;
+ bool changedEncoding = false;
+ if (_byteBuffer[0] == 0xFE && _byteBuffer[1] == 0xFF)
+ {
+ // Big Endian Unicode
+
+ _encoding = Encoding.BigEndianUnicode;
+ CompressBuffer(2);
+ changedEncoding = true;
+ }
+
+ else if (_byteBuffer[0] == 0xFF && _byteBuffer[1] == 0xFE)
+ {
+ // Little Endian Unicode, or possibly little endian UTF32
+ if (_byteLen < 4 || _byteBuffer[2] != 0 || _byteBuffer[3] != 0)
+ {
+ _encoding = Encoding.Unicode;
+ CompressBuffer(2);
+ changedEncoding = true;
+ }
+ else
+ {
+ _encoding = Encoding.UTF32;
+ CompressBuffer(4);
+ changedEncoding = true;
+ }
+ }
+
+ else if (_byteLen >= 3 && _byteBuffer[0] == 0xEF && _byteBuffer[1] == 0xBB && _byteBuffer[2] == 0xBF)
+ {
+ // UTF-8
+ _encoding = Encoding.UTF8;
+ CompressBuffer(3);
+ changedEncoding = true;
+ }
+ else if (_byteLen >= 4 && _byteBuffer[0] == 0 && _byteBuffer[1] == 0 &&
+ _byteBuffer[2] == 0xFE && _byteBuffer[3] == 0xFF)
+ {
+ // Big Endian UTF32
+ _encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true);
+ CompressBuffer(4);
+ changedEncoding = true;
+ }
+ else if (_byteLen == 2)
+ {
+ _detectEncoding = true;
+ }
+ // Note: in the future, if we change this algorithm significantly,
+ // we can support checking for the preamble of the given encoding.
+
+ if (changedEncoding)
+ {
+ _decoder = _encoding.GetDecoder();
+ int newMaxCharsPerBuffer = _encoding.GetMaxCharCount(_byteBuffer.Length);
+ if (newMaxCharsPerBuffer > _maxCharsPerBuffer)
+ {
+ _charBuffer = new char[newMaxCharsPerBuffer];
+ }
+ _maxCharsPerBuffer = newMaxCharsPerBuffer;
+ }
+ }
+
+ // Trims the preamble bytes from the byteBuffer. This routine can be called multiple times
+ // and we will buffer the bytes read until the preamble is matched or we determine that
+ // there is no match. If there is no match, every byte read previously will be available
+ // for further consumption. If there is a match, we will compress the buffer for the
+ // leading preamble bytes
+ private bool IsPreamble()
+ {
+ if (!_checkPreamble)
+ {
+ return _checkPreamble;
+ }
+
+ ReadOnlySpan<byte> preamble = _encoding.Preamble;
+
+ Debug.Assert(_bytePos <= preamble.Length, "_compressPreamble was called with the current bytePos greater than the preamble buffer length. Are two threads using this StreamReader at the same time?");
+ int len = (_byteLen >= (preamble.Length)) ? (preamble.Length - _bytePos) : (_byteLen - _bytePos);
+
+ for (int i = 0; i < len; i++, _bytePos++)
+ {
+ if (_byteBuffer[_bytePos] != preamble[_bytePos])
+ {
+ _bytePos = 0;
+ _checkPreamble = false;
+ break;
+ }
+ }
+
+ Debug.Assert(_bytePos <= preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+
+ if (_checkPreamble)
+ {
+ if (_bytePos == preamble.Length)
+ {
+ // We have a match
+ CompressBuffer(preamble.Length);
+ _bytePos = 0;
+ _checkPreamble = false;
+ _detectEncoding = false;
+ }
+ }
+
+ return _checkPreamble;
+ }
+
+ internal virtual int ReadBuffer()
+ {
+ _charLen = 0;
+ _charPos = 0;
+
+ if (!_checkPreamble)
+ {
+ _byteLen = 0;
+ }
+
+ do
+ {
+ if (_checkPreamble)
+ {
+ Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
+ Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+
+ if (len == 0)
+ {
+ // EOF but we might have buffered bytes from previous
+ // attempt to detect preamble that needs to be decoded now
+ if (_byteLen > 0)
+ {
+ _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen);
+ // Need to zero out the byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
+ _bytePos = _byteLen = 0;
+ }
+
+ return _charLen;
+ }
+
+ _byteLen += len;
+ }
+ else
+ {
+ Debug.Assert(_bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+ _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
+ Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+
+ if (_byteLen == 0) // We're at EOF
+ {
+ return _charLen;
+ }
+ }
+
+ // _isBlocked == whether we read fewer bytes than we asked for.
+ // Note we must check it here because CompressBuffer or
+ // DetectEncoding will change byteLen.
+ _isBlocked = (_byteLen < _byteBuffer.Length);
+
+ // Check for preamble before detect encoding. This is not to override the
+ // user supplied Encoding for the one we implicitly detect. The user could
+ // customize the encoding which we will loose, such as ThrowOnError on UTF8
+ if (IsPreamble())
+ {
+ continue;
+ }
+
+ // If we're supposed to detect the encoding and haven't done so yet,
+ // do it. Note this may need to be called more than once.
+ if (_detectEncoding && _byteLen >= 2)
+ {
+ DetectEncoding();
+ }
+
+ _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen);
+ } while (_charLen == 0);
+ //Console.WriteLine("ReadBuffer called. chars: "+charLen);
+ return _charLen;
+ }
+
+
+ // This version has a perf optimization to decode data DIRECTLY into the
+ // user's buffer, bypassing StreamReader's own buffer.
+ // This gives a > 20% perf improvement for our encodings across the board,
+ // but only when asking for at least the number of characters that one
+ // buffer's worth of bytes could produce.
+ // This optimization, if run, will break SwitchEncoding, so we must not do
+ // this on the first call to ReadBuffer.
+ private int ReadBuffer(Span<char> userBuffer, out bool readToUserBuffer)
+ {
+ _charLen = 0;
+ _charPos = 0;
+
+ if (!_checkPreamble)
+ {
+ _byteLen = 0;
+ }
+
+ int charsRead = 0;
+
+ // As a perf optimization, we can decode characters DIRECTLY into a
+ // user's char[]. We absolutely must not write more characters
+ // into the user's buffer than they asked for. Calculating
+ // encoding.GetMaxCharCount(byteLen) each time is potentially very
+ // expensive - instead, cache the number of chars a full buffer's
+ // worth of data may produce. Yes, this makes the perf optimization
+ // less aggressive, in that all reads that asked for fewer than AND
+ // returned fewer than _maxCharsPerBuffer chars won't get the user
+ // buffer optimization. This affects reads where the end of the
+ // Stream comes in the middle somewhere, and when you ask for
+ // fewer chars than your buffer could produce.
+ readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;
+
+ do
+ {
+ Debug.Assert(charsRead == 0);
+
+ if (_checkPreamble)
+ {
+ Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
+ Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+
+ if (len == 0)
+ {
+ // EOF but we might have buffered bytes from previous
+ // attempt to detect preamble that needs to be decoded now
+ if (_byteLen > 0)
+ {
+ if (readToUserBuffer)
+ {
+ charsRead = _decoder.GetChars(new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer.Slice(charsRead), flush: false);
+ _charLen = 0; // StreamReader's buffer is empty.
+ }
+ else
+ {
+ charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, charsRead);
+ _charLen += charsRead; // Number of chars in StreamReader's buffer.
+ }
+ }
+
+ return charsRead;
+ }
+
+ _byteLen += len;
+ }
+ else
+ {
+ Debug.Assert(_bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+
+ _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
+
+ Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+
+ if (_byteLen == 0) // EOF
+ {
+ break;
+ }
+ }
+
+ // _isBlocked == whether we read fewer bytes than we asked for.
+ // Note we must check it here because CompressBuffer or
+ // DetectEncoding will change byteLen.
+ _isBlocked = (_byteLen < _byteBuffer.Length);
+
+ // Check for preamble before detect encoding. This is not to override the
+ // user supplied Encoding for the one we implicitly detect. The user could
+ // customize the encoding which we will loose, such as ThrowOnError on UTF8
+ // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
+ // doesn't change the encoding or affect _maxCharsPerBuffer
+ if (IsPreamble())
+ {
+ continue;
+ }
+
+ // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
+ if (_detectEncoding && _byteLen >= 2)
+ {
+ DetectEncoding();
+ // DetectEncoding changes some buffer state. Recompute this.
+ readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer;
+ }
+
+ _charPos = 0;
+ if (readToUserBuffer)
+ {
+ charsRead += _decoder.GetChars(new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer.Slice(charsRead), flush:false);
+ _charLen = 0; // StreamReader's buffer is empty.
+ }
+ else
+ {
+ charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, charsRead);
+ _charLen += charsRead; // Number of chars in StreamReader's buffer.
+ }
+ } while (charsRead == 0);
+
+ _isBlocked &= charsRead < userBuffer.Length;
+
+ //Console.WriteLine("ReadBuffer: charsRead: "+charsRead+" readToUserBuffer: "+readToUserBuffer);
+ return charsRead;
+ }
+
+
+ // Reads a line. A line is defined as a sequence of characters followed by
+ // a carriage return ('\r'), a line feed ('\n'), or a carriage return
+ // immediately followed by a line feed. The resulting string does not
+ // contain the terminating carriage return and/or line feed. The returned
+ // value is null if the end of the input stream has been reached.
+ //
+ public override string ReadLine()
+ {
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (_charPos == _charLen)
+ {
+ if (ReadBuffer() == 0)
+ {
+ return null;
+ }
+ }
+
+ StringBuilder sb = null;
+ do
+ {
+ int i = _charPos;
+ do
+ {
+ char ch = _charBuffer[i];
+ // Note the following common line feed chars:
+ // \n - UNIX \r\n - DOS \r - Mac
+ if (ch == '\r' || ch == '\n')
+ {
+ string s;
+ if (sb != null)
+ {
+ sb.Append(_charBuffer, _charPos, i - _charPos);
+ s = sb.ToString();
+ }
+ else
+ {
+ s = new string(_charBuffer, _charPos, i - _charPos);
+ }
+ _charPos = i + 1;
+ if (ch == '\r' && (_charPos < _charLen || ReadBuffer() > 0))
+ {
+ if (_charBuffer[_charPos] == '\n')
+ {
+ _charPos++;
+ }
+ }
+ return s;
+ }
+ i++;
+ } while (i < _charLen);
+ i = _charLen - _charPos;
+ if (sb == null)
+ {
+ sb = new StringBuilder(i + 80);
+ }
+ sb.Append(_charBuffer, _charPos, i);
+ } while (ReadBuffer() > 0);
+ return sb.ToString();
+ }
+
+ #region Task based Async APIs
+ public override Task<string> ReadLineAsync()
+ {
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Read() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Read) when we are not sure.
+ if (GetType() != typeof(StreamReader))
+ {
+ return base.ReadLineAsync();
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task<string> task = ReadLineAsyncInternal();
+ _asyncReadTask = task;
+
+ return task;
+ }
+
+ private async Task<string> ReadLineAsyncInternal()
+ {
+ if (_charPos == _charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
+ {
+ return null;
+ }
+
+ StringBuilder sb = null;
+
+ do
+ {
+ char[] tmpCharBuffer = _charBuffer;
+ int tmpCharLen = _charLen;
+ int tmpCharPos = _charPos;
+ int i = tmpCharPos;
+
+ do
+ {
+ char ch = tmpCharBuffer[i];
+
+ // Note the following common line feed chars:
+ // \n - UNIX \r\n - DOS \r - Mac
+ if (ch == '\r' || ch == '\n')
+ {
+ string s;
+
+ if (sb != null)
+ {
+ sb.Append(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
+ s = sb.ToString();
+ }
+ else
+ {
+ s = new string(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
+ }
+
+ _charPos = tmpCharPos = i + 1;
+
+ if (ch == '\r' && (tmpCharPos < tmpCharLen || (await ReadBufferAsync().ConfigureAwait(false)) > 0))
+ {
+ tmpCharPos = _charPos;
+ if (_charBuffer[tmpCharPos] == '\n')
+ {
+ _charPos = ++tmpCharPos;
+ }
+ }
+
+ return s;
+ }
+
+ i++;
+ } while (i < tmpCharLen);
+
+ i = tmpCharLen - tmpCharPos;
+ if (sb == null)
+ {
+ sb = new StringBuilder(i + 80);
+ }
+ sb.Append(tmpCharBuffer, tmpCharPos, i);
+ } while (await ReadBufferAsync().ConfigureAwait(false) > 0);
+
+ return sb.ToString();
+ }
+
+ public override Task<string> ReadToEndAsync()
+ {
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Read() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Read) when we are not sure.
+ if (GetType() != typeof(StreamReader))
+ {
+ return base.ReadToEndAsync();
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task<string> task = ReadToEndAsyncInternal();
+ _asyncReadTask = task;
+
+ return task;
+ }
+
+ private async Task<string> ReadToEndAsyncInternal()
+ {
+ // Call ReadBuffer, then pull data out of charBuffer.
+ StringBuilder sb = new StringBuilder(_charLen - _charPos);
+ do
+ {
+ int tmpCharPos = _charPos;
+ sb.Append(_charBuffer, tmpCharPos, _charLen - tmpCharPos);
+ _charPos = _charLen; // We consumed these characters
+ await ReadBufferAsync().ConfigureAwait(false);
+ } while (_charLen > 0);
+
+ return sb.ToString();
+ }
+
+ public override Task<int> ReadAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0 || count < 0)
+ {
+ throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Read() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Read) when we are not sure.
+ if (GetType() != typeof(StreamReader))
+ {
+ return base.ReadAsync(buffer, index, count);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task<int> task = ReadAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask();
+ _asyncReadTask = task;
+
+ return task;
+ }
+
+ public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ if (GetType() != typeof(StreamReader))
+ {
+ // Ensure we use existing overrides if a class already overrode existing overloads.
+ return base.ReadAsync(buffer, cancellationToken);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
+ }
+
+ return ReadAsyncInternal(buffer, cancellationToken);
+ }
+
+ internal override async ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
+ {
+ if (_charPos == _charLen && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
+ {
+ return 0;
+ }
+
+ int charsRead = 0;
+
+ // As a perf optimization, if we had exactly one buffer's worth of
+ // data read in, let's try writing directly to the user's buffer.
+ bool readToUserBuffer = false;
+
+ Byte[] tmpByteBuffer = _byteBuffer;
+ Stream tmpStream = _stream;
+
+ int count = buffer.Length;
+ while (count > 0)
+ {
+ // n is the characters available in _charBuffer
+ int n = _charLen - _charPos;
+
+ // charBuffer is empty, let's read from the stream
+ if (n == 0)
+ {
+ _charLen = 0;
+ _charPos = 0;
+
+ if (!_checkPreamble)
+ {
+ _byteLen = 0;
+ }
+
+ readToUserBuffer = count >= _maxCharsPerBuffer;
+
+ // We loop here so that we read in enough bytes to yield at least 1 char.
+ // We break out of the loop if the stream is blocked (EOF is reached).
+ do
+ {
+ Debug.Assert(n == 0);
+
+ if (_checkPreamble)
+ {
+ Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ int tmpBytePos = _bytePos;
+ int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos), cancellationToken).ConfigureAwait(false);
+ Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+
+ if (len == 0)
+ {
+ // EOF but we might have buffered bytes from previous
+ // attempts to detect preamble that needs to be decoded now
+ if (_byteLen > 0)
+ {
+ if (readToUserBuffer)
+ {
+ n = _decoder.GetChars(new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false);
+ _charLen = 0; // StreamReader's buffer is empty.
+ }
+ else
+ {
+ n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0);
+ _charLen += n; // Number of chars in StreamReader's buffer.
+ }
+ }
+
+ // How can part of the preamble yield any chars?
+ Debug.Assert(n == 0);
+
+ _isBlocked = true;
+ break;
+ }
+ else
+ {
+ _byteLen += len;
+ }
+ }
+ else
+ {
+ Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+
+ _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false);
+
+ Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+
+ if (_byteLen == 0) // EOF
+ {
+ _isBlocked = true;
+ break;
+ }
+ }
+
+ // _isBlocked == whether we read fewer bytes than we asked for.
+ // Note we must check it here because CompressBuffer or
+ // DetectEncoding will change _byteLen.
+ _isBlocked = (_byteLen < tmpByteBuffer.Length);
+
+ // Check for preamble before detect encoding. This is not to override the
+ // user supplied Encoding for the one we implicitly detect. The user could
+ // customize the encoding which we will loose, such as ThrowOnError on UTF8
+ // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble
+ // doesn't change the encoding or affect _maxCharsPerBuffer
+ if (IsPreamble())
+ {
+ continue;
+ }
+
+ // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it.
+ if (_detectEncoding && _byteLen >= 2)
+ {
+ DetectEncoding();
+ // DetectEncoding changes some buffer state. Recompute this.
+ readToUserBuffer = count >= _maxCharsPerBuffer;
+ }
+
+ Debug.Assert(n == 0);
+
+ _charPos = 0;
+ if (readToUserBuffer)
+ {
+ n += _decoder.GetChars(new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false);
+
+ // Why did the bytes yield no chars?
+ Debug.Assert(n > 0);
+
+ _charLen = 0; // StreamReader's buffer is empty.
+ }
+ else
+ {
+ n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0);
+
+ // Why did the bytes yield no chars?
+ Debug.Assert(n > 0);
+
+ _charLen += n; // Number of chars in StreamReader's buffer.
+ }
+ } while (n == 0);
+
+ if (n == 0)
+ {
+ break; // We're at EOF
+ }
+ } // if (n == 0)
+
+ // Got more chars in charBuffer than the user requested
+ if (n > count)
+ {
+ n = count;
+ }
+
+ if (!readToUserBuffer)
+ {
+ new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Span.Slice(charsRead));
+ _charPos += n;
+ }
+
+ charsRead += n;
+ count -= n;
+
+ // This function shouldn't block for an indefinite amount of time,
+ // or reading from a network stream won't work right. If we got
+ // fewer bytes than we requested, then we want to break right here.
+ if (_isBlocked)
+ {
+ break;
+ }
+ } // while (count > 0)
+
+ return charsRead;
+ }
+
+ public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0 || count < 0)
+ {
+ throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Read() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Read) when we are not sure.
+ if (GetType() != typeof(StreamReader))
+ {
+ return base.ReadBlockAsync(buffer, index, count);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task<int> task = base.ReadBlockAsync(buffer, index, count);
+ _asyncReadTask = task;
+
+ return task;
+ }
+
+ public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ if (GetType() != typeof(StreamReader))
+ {
+ // If a derived type may have overridden ReadBlockAsync(char[], ...) before this overload
+ // was introduced, defer to it.
+ return base.ReadBlockAsync(buffer, cancellationToken);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
+ }
+
+ ValueTask<int> vt = ReadBlockAsyncInternal(buffer, cancellationToken);
+ if (vt.IsCompletedSuccessfully)
+ {
+ return vt;
+ }
+
+ Task<int> t = vt.AsTask();
+ _asyncReadTask = t;
+ return new ValueTask<int>(t);
+ }
+
+ private async Task<int> ReadBufferAsync()
+ {
+ _charLen = 0;
+ _charPos = 0;
+ Byte[] tmpByteBuffer = _byteBuffer;
+ Stream tmpStream = _stream;
+
+ if (!_checkPreamble)
+ {
+ _byteLen = 0;
+ }
+ do
+ {
+ if (_checkPreamble)
+ {
+ Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
+ int tmpBytePos = _bytePos;
+ int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos)).ConfigureAwait(false);
+ Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
+
+ if (len == 0)
+ {
+ // EOF but we might have buffered bytes from previous
+ // attempt to detect preamble that needs to be decoded now
+ if (_byteLen > 0)
+ {
+ _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen);
+ // Need to zero out the _byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
+ _bytePos = 0; _byteLen = 0;
+ }
+
+ return _charLen;
+ }
+
+ _byteLen += len;
+ }
+ else
+ {
+ Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
+ _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer)).ConfigureAwait(false);
+ Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! Bug in stream class.");
+
+ if (_byteLen == 0) // We're at EOF
+ {
+ return _charLen;
+ }
+ }
+
+ // _isBlocked == whether we read fewer bytes than we asked for.
+ // Note we must check it here because CompressBuffer or
+ // DetectEncoding will change _byteLen.
+ _isBlocked = (_byteLen < tmpByteBuffer.Length);
+
+ // Check for preamble before detect encoding. This is not to override the
+ // user supplied Encoding for the one we implicitly detect. The user could
+ // customize the encoding which we will loose, such as ThrowOnError on UTF8
+ if (IsPreamble())
+ {
+ continue;
+ }
+
+ // If we're supposed to detect the encoding and haven't done so yet,
+ // do it. Note this may need to be called more than once.
+ if (_detectEncoding && _byteLen >= 2)
+ {
+ DetectEncoding();
+ }
+
+ _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen);
+ } while (_charLen == 0);
+
+ return _charLen;
+ }
+#endregion
+
+
+ // No data, class doesn't need to be serializable.
+ // Note this class is threadsafe.
+ private class NullStreamReader : StreamReader
+ {
+ // Instantiating Encoding causes unnecessary perf hit.
+ internal NullStreamReader()
+ {
+ Init(Stream.Null);
+ }
+
+ public override Stream BaseStream
+ {
+ get { return Stream.Null; }
+ }
+
+ public override Encoding CurrentEncoding
+ {
+ get { return Encoding.Unicode; }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ // Do nothing - this is essentially unclosable.
+ }
+
+ public override int Peek()
+ {
+ return -1;
+ }
+
+ public override int Read()
+ {
+ return -1;
+ }
+
+ [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems.
+ public override int Read(char[] buffer, int index, int count)
+ {
+ return 0;
+ }
+
+ public override string ReadLine()
+ {
+ return null;
+ }
+
+ public override string ReadToEnd()
+ {
+ return string.Empty;
+ }
+
+ internal override int ReadBuffer()
+ {
+ return 0;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs b/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs
new file mode 100644
index 0000000000..06d94d2385
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs
@@ -0,0 +1,996 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ // This class implements a TextWriter for writing characters to a Stream.
+ // This is designed for character output in a particular Encoding,
+ // whereas the Stream class is designed for byte input and output.
+ public class StreamWriter : TextWriter
+ {
+ // For UTF-8, the values of 1K for the default buffer size and 4K for the
+ // file stream buffer size are reasonable & give very reasonable
+ // performance for in terms of construction time for the StreamWriter and
+ // write perf. Note that for UTF-8, we end up allocating a 4K byte buffer,
+ // which means we take advantage of adaptive buffering code.
+ // The performance using UnicodeEncoding is acceptable.
+ private const int DefaultBufferSize = 1024; // char[]
+ private const int DefaultFileStreamBufferSize = 4096;
+ private const int MinBufferSize = 128;
+
+ private const int DontCopyOnWriteLineThreshold = 512;
+
+ // Bit bucket - Null has no backing store. Non closable.
+ public new static readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, true);
+
+ private Stream _stream;
+ private Encoding _encoding;
+ private Encoder _encoder;
+ private byte[] _byteBuffer;
+ private char[] _charBuffer;
+ private int _charPos;
+ private int _charLen;
+ private bool _autoFlush;
+ private bool _haveWrittenPreamble;
+ private bool _closable;
+
+ // We don't guarantee thread safety on StreamWriter, but we should at
+ // least prevent users from trying to write anything while an Async
+ // write from the same thread is in progress.
+ private Task _asyncWriteTask = Task.CompletedTask;
+
+ private void CheckAsyncTaskInProgress()
+ {
+ // We are not locking the access to _asyncWriteTask because this is not meant to guarantee thread safety.
+ // We are simply trying to deter calling any Write APIs while an async Write from the same thread is in progress.
+ if (!_asyncWriteTask.IsCompleted)
+ {
+ ThrowAsyncIOInProgress();
+ }
+ }
+
+ private static void ThrowAsyncIOInProgress() =>
+ throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
+
+ // The high level goal is to be tolerant of encoding errors when we read and very strict
+ // when we write. Hence, default StreamWriter encoding will throw on encoding error.
+ // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character
+ // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the
+ // internal StreamWriter's state to be irrecoverable as it would have buffered the
+ // illegal chars and any subsequent call to Flush() would hit the encoding error again.
+ // Even Close() will hit the exception as it would try to flush the unwritten data.
+ // Maybe we can add a DiscardBufferedData() method to get out of such situation (like
+ // StreamReader though for different reason). Either way, the buffered data will be lost!
+ private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM;
+
+
+ internal StreamWriter() : base(null)
+ { // Ask for CurrentCulture all the time
+ }
+
+ public StreamWriter(Stream stream)
+ : this(stream, UTF8NoBOM, DefaultBufferSize, false)
+ {
+ }
+
+ public StreamWriter(Stream stream, Encoding encoding)
+ : this(stream, encoding, DefaultBufferSize, false)
+ {
+ }
+
+ // Creates a new StreamWriter for the given stream. The
+ // character encoding is set by encoding and the buffer size,
+ // in number of 16-bit characters, is set by bufferSize.
+ //
+ public StreamWriter(Stream stream, Encoding encoding, int bufferSize)
+ : this(stream, encoding, bufferSize, false)
+ {
+ }
+
+ public StreamWriter(Stream stream, Encoding encoding, int bufferSize, bool leaveOpen)
+ : base(null) // Ask for CurrentCulture all the time
+ {
+ if (stream == null || encoding == null)
+ {
+ throw new ArgumentNullException(stream == null ? nameof(stream) : nameof(encoding));
+ }
+ if (!stream.CanWrite)
+ {
+ throw new ArgumentException(SR.Argument_StreamNotWritable);
+ }
+ if (bufferSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+ }
+
+ Init(stream, encoding, bufferSize, leaveOpen);
+ }
+
+ public StreamWriter(string path)
+ : this(path, false, UTF8NoBOM, DefaultBufferSize)
+ {
+ }
+
+ public StreamWriter(string path, bool append)
+ : this(path, append, UTF8NoBOM, DefaultBufferSize)
+ {
+ }
+
+ public StreamWriter(string path, bool append, Encoding encoding)
+ : this(path, append, encoding, DefaultBufferSize)
+ {
+ }
+
+ public StreamWriter(string path, bool append, Encoding encoding, int bufferSize)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+ if (encoding == null)
+ throw new ArgumentNullException(nameof(encoding));
+ if (path.Length == 0)
+ throw new ArgumentException(SR.Argument_EmptyPath);
+ if (bufferSize <= 0)
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+
+ Stream stream = new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read,
+ DefaultFileStreamBufferSize, FileOptions.SequentialScan);
+ Init(stream, encoding, bufferSize, shouldLeaveOpen: false);
+ }
+
+ private void Init(Stream streamArg, Encoding encodingArg, int bufferSize, bool shouldLeaveOpen)
+ {
+ _stream = streamArg;
+ _encoding = encodingArg;
+ _encoder = _encoding.GetEncoder();
+ if (bufferSize < MinBufferSize)
+ {
+ bufferSize = MinBufferSize;
+ }
+
+ _charBuffer = new char[bufferSize];
+ _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)];
+ _charLen = bufferSize;
+ // If we're appending to a Stream that already has data, don't write
+ // the preamble.
+ if (_stream.CanSeek && _stream.Position > 0)
+ {
+ _haveWrittenPreamble = true;
+ }
+
+ _closable = !shouldLeaveOpen;
+ }
+
+ public override void Close()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ // We need to flush any buffered data if we are being closed/disposed.
+ // Also, we never close the handles for stdout & friends. So we can safely
+ // write any buffered data to those streams even during finalization, which
+ // is generally the right thing to do.
+ if (_stream != null)
+ {
+ // Note: flush on the underlying stream can throw (ex., low disk space)
+ if (disposing /* || (LeaveOpen && stream is __ConsoleStream) */)
+ {
+ CheckAsyncTaskInProgress();
+
+ Flush(true, true);
+ }
+ }
+ }
+ finally
+ {
+ // Dispose of our resources if this StreamWriter is closable.
+ // Note: Console.Out and other such non closable streamwriters should be left alone
+ if (!LeaveOpen && _stream != null)
+ {
+ try
+ {
+ // Attempt to close the stream even if there was an IO error from Flushing.
+ // Note that Stream.Close() can potentially throw here (may or may not be
+ // due to the same Flush error). In this case, we still need to ensure
+ // cleaning up internal resources, hence the finally block.
+ if (disposing)
+ {
+ _stream.Close();
+ }
+ }
+ finally
+ {
+ _stream = null;
+ _byteBuffer = null;
+ _charBuffer = null;
+ _encoding = null;
+ _encoder = null;
+ _charLen = 0;
+ base.Dispose(disposing);
+ }
+ }
+ }
+ }
+
+ public override void Flush()
+ {
+ CheckAsyncTaskInProgress();
+
+ Flush(true, true);
+ }
+
+ private void Flush(bool flushStream, bool flushEncoder)
+ {
+ // flushEncoder should be true at the end of the file and if
+ // the user explicitly calls Flush (though not if AutoFlush is true).
+ // This is required to flush any dangling characters from our UTF-7
+ // and UTF-8 encoders.
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ // Perf boost for Flush on non-dirty writers.
+ if (_charPos == 0 && !flushStream && !flushEncoder)
+ {
+ return;
+ }
+
+ if (!_haveWrittenPreamble)
+ {
+ _haveWrittenPreamble = true;
+ ReadOnlySpan<byte> preamble = _encoding.Preamble;
+ if (preamble.Length > 0)
+ {
+ _stream.Write(preamble);
+ }
+ }
+
+ int count = _encoder.GetBytes(_charBuffer, 0, _charPos, _byteBuffer, 0, flushEncoder);
+ _charPos = 0;
+ if (count > 0)
+ {
+ _stream.Write(_byteBuffer, 0, count);
+ }
+ // By definition, calling Flush should flush the stream, but this is
+ // only necessary if we passed in true for flushStream. The Web
+ // Services guys have some perf tests where flushing needlessly hurts.
+ if (flushStream)
+ {
+ _stream.Flush();
+ }
+ }
+
+ public virtual bool AutoFlush
+ {
+ get { return _autoFlush; }
+
+ set
+ {
+ CheckAsyncTaskInProgress();
+
+ _autoFlush = value;
+ if (value)
+ {
+ Flush(true, false);
+ }
+ }
+ }
+
+ public virtual Stream BaseStream
+ {
+ get { return _stream; }
+ }
+
+ internal bool LeaveOpen
+ {
+ get { return !_closable; }
+ }
+
+ internal bool HaveWrittenPreamble
+ {
+ set { _haveWrittenPreamble = value; }
+ }
+
+ public override Encoding Encoding
+ {
+ get { return _encoding; }
+ }
+
+ public override void Write(char value)
+ {
+ CheckAsyncTaskInProgress();
+
+ if (_charPos == _charLen)
+ {
+ Flush(false, false);
+ }
+
+ _charBuffer[_charPos] = value;
+ _charPos++;
+ if (_autoFlush)
+ {
+ Flush(true, false);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
+ public override void Write(char[] buffer)
+ {
+ WriteSpan(buffer, appendNewLine: false);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
+ public override void Write(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ WriteSpan(buffer.AsSpan(index, count), appendNewLine: false);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
+ public override void Write(ReadOnlySpan<char> buffer)
+ {
+ if (GetType() == typeof(StreamWriter))
+ {
+ WriteSpan(buffer, appendNewLine: false);
+ }
+ else
+ {
+ // If a derived class may have overridden existing Write behavior,
+ // we need to make sure we use it.
+ base.Write(buffer);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe void WriteSpan(ReadOnlySpan<char> buffer, bool appendNewLine)
+ {
+ CheckAsyncTaskInProgress();
+
+ if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation
+ buffer.Length <= _charLen - _charPos)
+ {
+ // For very short buffers and when we don't need to worry about running out of space
+ // in the char buffer, just copy the chars individually.
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ _charBuffer[_charPos++] = buffer[i];
+ }
+ }
+ else
+ {
+ // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks.
+ // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are
+ // resulting in significant overhead (even when the if branch above is taken rather than this
+ // else) due to temporaries that need to be cleared. Given the use of unsafe code, we also
+ // make local copies of instance state to protect against potential concurrent misuse.
+
+ char[] charBuffer = _charBuffer;
+ if (charBuffer == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
+ fixed (char* dstPtr = &charBuffer[0])
+ {
+ char* srcPtr = bufferPtr;
+ int count = buffer.Length;
+ int dstPos = _charPos; // use a local copy of _charPos for safety
+ while (count > 0)
+ {
+ if (dstPos == charBuffer.Length)
+ {
+ Flush(false, false);
+ dstPos = 0;
+ }
+
+ int n = Math.Min(charBuffer.Length - dstPos, count);
+ int bytesToCopy = n * sizeof(char);
+
+ Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy);
+
+ _charPos += n;
+ dstPos += n;
+ srcPtr += n;
+ count -= n;
+ }
+ }
+ }
+
+ if (appendNewLine)
+ {
+ char[] coreNewLine = CoreNewLine;
+ for (int i = 0; i < coreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations
+ {
+ if (_charPos == _charLen)
+ {
+ Flush(false, false);
+ }
+
+ _charBuffer[_charPos] = coreNewLine[i];
+ _charPos++;
+ }
+ }
+
+ if (_autoFlush)
+ {
+ Flush(true, false);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
+ public override void Write(string value)
+ {
+ WriteSpan(value, appendNewLine: false);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
+ public override void WriteLine(string value)
+ {
+ CheckAsyncTaskInProgress();
+ WriteSpan(value, appendNewLine: true);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites
+ public override void WriteLine(ReadOnlySpan<char> value)
+ {
+ if (GetType() == typeof(StreamWriter))
+ {
+ CheckAsyncTaskInProgress();
+ WriteSpan(value, appendNewLine: true);
+ }
+ else
+ {
+ // If a derived class may have overridden existing WriteLine behavior,
+ // we need to make sure we use it.
+ base.WriteLine(value);
+ }
+ }
+
+ #region Task based Async APIs
+ public override Task WriteAsync(char value)
+ {
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Write() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Write) when we are not sure.
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.WriteAsync(value);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+
+ // We pass in private instance fields of this MarshalByRefObject-derived type as local params
+ // to ensure performant access inside the state machine that corresponds this async method.
+ // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
+ private static async Task WriteAsyncInternal(StreamWriter _this, char value,
+ char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
+ bool autoFlush, bool appendNewLine)
+ {
+ if (charPos == charLen)
+ {
+ await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ charBuffer[charPos] = value;
+ charPos++;
+
+ if (appendNewLine)
+ {
+ for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
+ {
+ if (charPos == charLen)
+ {
+ await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ charBuffer[charPos] = coreNewLine[i];
+ charPos++;
+ }
+ }
+
+ if (autoFlush)
+ {
+ await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ _this.CharPos_Prop = charPos;
+ }
+
+ public override Task WriteAsync(string value)
+ {
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Write() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Write) when we are not sure.
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.WriteAsync(value);
+ }
+
+ if (value != null)
+ {
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+ else
+ {
+ return Task.CompletedTask;
+ }
+ }
+
+ // We pass in private instance fields of this MarshalByRefObject-derived type as local params
+ // to ensure performant access inside the state machine that corresponds this async method.
+ // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
+ private static async Task WriteAsyncInternal(StreamWriter _this, string value,
+ char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
+ bool autoFlush, bool appendNewLine)
+ {
+ Debug.Assert(value != null);
+
+ int count = value.Length;
+ int index = 0;
+
+ while (count > 0)
+ {
+ if (charPos == charLen)
+ {
+ await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ int n = charLen - charPos;
+ if (n > count)
+ {
+ n = count;
+ }
+
+ Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code.");
+
+ value.CopyTo(index, charBuffer, charPos, n);
+
+ charPos += n;
+ index += n;
+ count -= n;
+ }
+
+ if (appendNewLine)
+ {
+ for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
+ {
+ if (charPos == charLen)
+ {
+ await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ charBuffer[charPos] = coreNewLine[i];
+ charPos++;
+ }
+ }
+
+ if (autoFlush)
+ {
+ await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ _this.CharPos_Prop = charPos;
+ }
+
+ public override Task WriteAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Write() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Write) when we are not sure.
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.WriteAsync(buffer, index, count);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+
+ public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ if (GetType() != typeof(StreamWriter))
+ {
+ // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it.
+ return base.WriteAsync(buffer, cancellationToken);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken);
+ _asyncWriteTask = task;
+ return task;
+ }
+
+ // We pass in private instance fields of this MarshalByRefObject-derived type as local params
+ // to ensure performant access inside the state machine that corresponds this async method.
+ // Fields that are written to must be assigned at the end of the method *and* before instance invocations.
+ private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source,
+ char[] charBuffer, int charPos, int charLen, char[] coreNewLine,
+ bool autoFlush, bool appendNewLine, CancellationToken cancellationToken)
+ {
+ int copied = 0;
+ while (copied < source.Length)
+ {
+ if (charPos == charLen)
+ {
+ await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ int n = Math.Min(charLen - charPos, source.Length - copied);
+ Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code.");
+
+ source.Span.Slice(copied, n).CopyTo(new Span<char>(charBuffer, charPos, n));
+ charPos += n;
+ copied += n;
+ }
+
+ if (appendNewLine)
+ {
+ for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy
+ {
+ if (charPos == charLen)
+ {
+ await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ charBuffer[charPos] = coreNewLine[i];
+ charPos++;
+ }
+ }
+
+ if (autoFlush)
+ {
+ await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false);
+ Debug.Assert(_this._charPos == 0);
+ charPos = 0;
+ }
+
+ _this.CharPos_Prop = charPos;
+ }
+
+ public override Task WriteLineAsync()
+ {
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Write() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Write) when we are not sure.
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.WriteLineAsync();
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+
+
+ public override Task WriteLineAsync(char value)
+ {
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Write() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Write) when we are not sure.
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.WriteLineAsync(value);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+
+
+ public override Task WriteLineAsync(string value)
+ {
+ if (value == null)
+ {
+ return WriteLineAsync();
+ }
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Write() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Write) when we are not sure.
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.WriteLineAsync(value);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+
+
+ public override Task WriteLineAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Write() which a subclass might have overridden.
+ // To be safe we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Write) when we are not sure.
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.WriteLineAsync(buffer, index, count);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+
+ public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.WriteLineAsync(buffer, cancellationToken);
+ }
+
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+
+
+ public override Task FlushAsync()
+ {
+ // If we have been inherited into a subclass, the following implementation could be incorrect
+ // since it does not call through to Flush() which a subclass might have overridden. To be safe
+ // we will only use this implementation in cases where we know it is safe to do so,
+ // and delegate to our base class (which will call into Flush) when we are not sure.
+ if (GetType() != typeof(StreamWriter))
+ {
+ return base.FlushAsync();
+ }
+
+ // flushEncoder should be true at the end of the file and if
+ // the user explicitly calls Flush (though not if AutoFlush is true).
+ // This is required to flush any dangling characters from our UTF-7
+ // and UTF-8 encoders.
+ if (_stream == null)
+ {
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed);
+ }
+
+ CheckAsyncTaskInProgress();
+
+ Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos);
+ _asyncWriteTask = task;
+
+ return task;
+ }
+
+ private int CharPos_Prop
+ {
+ set { _charPos = value; }
+ }
+
+ private bool HaveWrittenPreamble_Prop
+ {
+ set { _haveWrittenPreamble = value; }
+ }
+
+ private Task FlushAsyncInternal(bool flushStream, bool flushEncoder,
+ char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ // Perf boost for Flush on non-dirty writers.
+ if (sCharPos == 0 && !flushStream && !flushEncoder)
+ {
+ return Task.CompletedTask;
+ }
+
+ Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble,
+ _encoding, _encoder, _byteBuffer, _stream, cancellationToken);
+
+ _charPos = 0;
+ return flushTask;
+ }
+
+
+ // We pass in private instance fields of this MarshalByRefObject-derived type as local params
+ // to ensure performant access inside the state machine that corresponds this async method.
+ private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder,
+ char[] charBuffer, int charPos, bool haveWrittenPreamble,
+ Encoding encoding, Encoder encoder, Byte[] byteBuffer, Stream stream, CancellationToken cancellationToken)
+ {
+ if (!haveWrittenPreamble)
+ {
+ _this.HaveWrittenPreamble_Prop = true;
+ byte[] preamble = encoding.GetPreamble();
+ if (preamble.Length > 0)
+ {
+ await stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder);
+ if (count > 0)
+ {
+ await stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false);
+ }
+
+ // By definition, calling Flush should flush the stream, but this is
+ // only necessary if we passed in true for flushStream. The Web
+ // Services guys have some perf tests where flushing needlessly hurts.
+ if (flushStream)
+ {
+ await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+ #endregion
+ } // class StreamWriter
+} // namespace
diff --git a/src/System.Private.CoreLib/shared/System/IO/TextReader.cs b/src/System.Private.CoreLib/shared/System/IO/TextReader.cs
new file mode 100644
index 0000000000..eb94dd7594
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/TextReader.cs
@@ -0,0 +1,412 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Buffers;
+
+namespace System.IO
+{
+ // This abstract base class represents a reader that can read a sequential
+ // stream of characters. This is not intended for reading bytes -
+ // there are methods on the Stream class to read bytes.
+ // A subclass must minimally implement the Peek() and Read() methods.
+ //
+ // This class is intended for character input, not bytes.
+ // There are methods on the Stream class for reading bytes.
+ public abstract partial class TextReader : MarshalByRefObject, IDisposable
+ {
+ public static readonly TextReader Null = new NullTextReader();
+
+ protected TextReader() { }
+
+ public virtual void Close()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+
+ // Returns the next available character without actually reading it from
+ // the input stream. The current position of the TextReader is not changed by
+ // this operation. The returned value is -1 if no further characters are
+ // available.
+ //
+ // This default method simply returns -1.
+ //
+ public virtual int Peek()
+ {
+ return -1;
+ }
+
+ // Reads the next character from the input stream. The returned value is
+ // -1 if no further characters are available.
+ //
+ // This default method simply returns -1.
+ //
+ public virtual int Read()
+ {
+ return -1;
+ }
+
+ // Reads a block of characters. This method will read up to
+ // count characters from this TextReader into the
+ // buffer character array starting at position
+ // index. Returns the actual number of characters read.
+ //
+ public virtual int Read(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ int n;
+ for (n = 0; n < count; n++)
+ {
+ int ch = Read();
+ if (ch == -1) break;
+ buffer[index + n] = (char)ch;
+ }
+
+ return n;
+ }
+
+ // Reads a span of characters. This method will read up to
+ // count characters from this TextReader into the
+ // span of characters Returns the actual number of characters read.
+ //
+ public virtual int Read(Span<char> buffer)
+ {
+ char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
+
+ try
+ {
+ int numRead = Read(array, 0, buffer.Length);
+ if ((uint)numRead > buffer.Length)
+ {
+ throw new IOException(SR.IO_InvalidReadLength);
+ }
+ new Span<char>(array, 0, numRead).CopyTo(buffer);
+ return numRead;
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(array);
+ }
+ }
+
+ // Reads all characters from the current position to the end of the
+ // TextReader, and returns them as one string.
+ public virtual string ReadToEnd()
+ {
+ char[] chars = new char[4096];
+ int len;
+ StringBuilder sb = new StringBuilder(4096);
+ while ((len = Read(chars, 0, chars.Length)) != 0)
+ {
+ sb.Append(chars, 0, len);
+ }
+ return sb.ToString();
+ }
+
+ // Blocking version of read. Returns only when count
+ // characters have been read or the end of the file was reached.
+ //
+ public virtual int ReadBlock(char[] buffer, int index, int count)
+ {
+ int i, n = 0;
+ do
+ {
+ n += (i = Read(buffer, index + n, count - n));
+ } while (i > 0 && n < count);
+ return n;
+ }
+
+ // Blocking version of read for span of characters. Returns only when count
+ // characters have been read or the end of the file was reached.
+ //
+ public virtual int ReadBlock(Span<char> buffer)
+ {
+ char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
+
+ try
+ {
+ int numRead = ReadBlock(array, 0, buffer.Length);
+ if ((uint)numRead > buffer.Length)
+ {
+ throw new IOException(SR.IO_InvalidReadLength);
+ }
+ new Span<char>(array, 0, numRead).CopyTo(buffer);
+ return numRead;
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(array);
+ }
+ }
+
+ // Reads a line. A line is defined as a sequence of characters followed by
+ // a carriage return ('\r'), a line feed ('\n'), or a carriage return
+ // immediately followed by a line feed. The resulting string does not
+ // contain the terminating carriage return and/or line feed. The returned
+ // value is null if the end of the input stream has been reached.
+ //
+ public virtual string ReadLine()
+ {
+ StringBuilder sb = new StringBuilder();
+ while (true)
+ {
+ int ch = Read();
+ if (ch == -1) break;
+ if (ch == '\r' || ch == '\n')
+ {
+ if (ch == '\r' && Peek() == '\n')
+ {
+ Read();
+ }
+
+ return sb.ToString();
+ }
+ sb.Append((char)ch);
+ }
+ if (sb.Length > 0)
+ {
+ return sb.ToString();
+ }
+
+ return null;
+ }
+
+ #region Task based Async APIs
+ public virtual Task<string> ReadLineAsync()
+ {
+ return Task<String>.Factory.StartNew(state =>
+ {
+ return ((TextReader)state).ReadLine();
+ },
+ this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public async virtual Task<string> ReadToEndAsync()
+ {
+ var sb = new StringBuilder(4096);
+ char[] chars = ArrayPool<char>.Shared.Rent(4096);
+ try
+ {
+ int len;
+ while ((len = await ReadAsyncInternal(chars, default).ConfigureAwait(false)) != 0)
+ {
+ sb.Append(chars, 0, len);
+ }
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(chars);
+ }
+ return sb.ToString();
+ }
+
+ public virtual Task<int> ReadAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0 || count < 0)
+ {
+ throw new ArgumentOutOfRangeException((index < 0 ? nameof(index): nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ return ReadAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask();
+ }
+
+ public virtual ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+ new ValueTask<int>(MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ?
+ ReadAsync(array.Array, array.Offset, array.Count) :
+ Task<int>.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextReader, Memory<char>>)state;
+ return t.Item1.Read(t.Item2.Span);
+ }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
+
+ internal virtual ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
+ {
+ var tuple = new Tuple<TextReader, Memory<char>>(this, buffer);
+ return new ValueTask<int>(Task<int>.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextReader, Memory<char>>)state;
+ return t.Item1.Read(t.Item2.Span);
+ },
+ tuple, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
+ }
+
+ public virtual Task<int> ReadBlockAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0 || count < 0)
+ {
+ throw new ArgumentOutOfRangeException((index < 0 ? nameof(index): nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ return ReadBlockAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask();
+ }
+
+ public virtual ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+ new ValueTask<int>(MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ?
+ ReadBlockAsync(array.Array, array.Offset, array.Count) :
+ Task<int>.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextReader, Memory<char>>)state;
+ return t.Item1.ReadBlock(t.Item2.Span);
+ }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
+
+ internal async ValueTask<int> ReadBlockAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
+ {
+ int n = 0, i;
+ do
+ {
+ i = await ReadAsyncInternal(buffer.Slice(n), cancellationToken).ConfigureAwait(false);
+ n += i;
+ } while (i > 0 && n < buffer.Length);
+
+ return n;
+ }
+ #endregion
+
+ private sealed class NullTextReader : TextReader
+ {
+ public NullTextReader() { }
+
+ public override int Read(char[] buffer, int index, int count)
+ {
+ return 0;
+ }
+
+ public override string ReadLine()
+ {
+ return null;
+ }
+ }
+
+ public static TextReader Synchronized(TextReader reader)
+ {
+ if (reader == null)
+ throw new ArgumentNullException(nameof(reader));
+
+ return reader is SyncTextReader ? reader : new SyncTextReader(reader);
+ }
+
+ internal sealed class SyncTextReader : TextReader
+ {
+ internal readonly TextReader _in;
+
+ internal SyncTextReader(TextReader t)
+ {
+ _in = t;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Close() => _in.Close();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ protected override void Dispose(bool disposing)
+ {
+ // Explicitly pick up a potentially methodimpl'ed Dispose
+ if (disposing)
+ ((IDisposable)_in).Dispose();
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Peek() => _in.Peek();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Read() => _in.Read();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int Read(char[] buffer, int index, int count) => _in.Read(buffer, index, count);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override int ReadBlock(char[] buffer, int index, int count) => _in.ReadBlock(buffer, index, count);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override string ReadLine() => _in.ReadLine();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override string ReadToEnd() => _in.ReadToEnd();
+
+ //
+ // On SyncTextReader all APIs should run synchronously, even the async ones.
+ //
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task<string> ReadLineAsync() => Task.FromResult(ReadLine());
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task<string> ReadToEndAsync() => Task.FromResult(ReadToEnd());
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - index < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ return Task.FromResult(ReadBlock(buffer, index, count));
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task<int> ReadAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - index < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ return Task.FromResult(Read(buffer, index, count));
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/TextWriter.cs b/src/System.Private.CoreLib/shared/System/IO/TextWriter.cs
new file mode 100644
index 0000000000..48e702be67
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/TextWriter.cs
@@ -0,0 +1,870 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System.Threading;
+using System.Globalization;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Buffers;
+
+namespace System.IO
+{
+ // This abstract base class represents a writer that can write a sequential
+ // stream of characters. A subclass must minimally implement the
+ // Write(char) method.
+ //
+ // This class is intended for character output, not bytes.
+ // There are methods on the Stream class for writing bytes.
+ public abstract partial class TextWriter : MarshalByRefObject, IDisposable
+ {
+ public static readonly TextWriter Null = new NullTextWriter();
+
+ // We don't want to allocate on every TextWriter creation, so cache the char array.
+ private static readonly char[] s_coreNewLine = Environment.NewLine.ToCharArray();
+
+ /// <summary>
+ /// This is the 'NewLine' property expressed as a char[].
+ /// It is exposed to subclasses as a protected field for read-only
+ /// purposes. You should only modify it by using the 'NewLine' property.
+ /// In particular you should never modify the elements of the array
+ /// as they are shared among many instances of TextWriter.
+ /// </summary>
+ protected char[] CoreNewLine = s_coreNewLine;
+ private string CoreNewLineStr = Environment.NewLine;
+
+ // Can be null - if so, ask for the Thread's CurrentCulture every time.
+ private IFormatProvider _internalFormatProvider;
+
+ protected TextWriter()
+ {
+ _internalFormatProvider = null; // Ask for CurrentCulture all the time.
+ }
+
+ protected TextWriter(IFormatProvider formatProvider)
+ {
+ _internalFormatProvider = formatProvider;
+ }
+
+ public virtual IFormatProvider FormatProvider
+ {
+ get
+ {
+ if (_internalFormatProvider == null)
+ {
+ return CultureInfo.CurrentCulture;
+ }
+ else
+ {
+ return _internalFormatProvider;
+ }
+ }
+ }
+
+ public virtual void Close()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ // Clears all buffers for this TextWriter and causes any buffered data to be
+ // written to the underlying device. This default method is empty, but
+ // descendant classes can override the method to provide the appropriate
+ // functionality.
+ public virtual void Flush()
+ {
+ }
+
+ public abstract Encoding Encoding
+ {
+ get;
+ }
+
+ /// <summary>
+ /// Returns the line terminator string used by this TextWriter. The default line
+ /// terminator string is Environment.NewLine, which is platform specific.
+ /// On Windows this is a carriage return followed by a line feed ("\r\n").
+ /// On OSX and Linux this is a line feed ("\n").
+ /// </summary>
+ /// <remarks>
+ /// The line terminator string is written to the text stream whenever one of the
+ /// WriteLine methods are called. In order for text written by
+ /// the TextWriter to be readable by a TextReader, only one of the following line
+ /// terminator strings should be used: "\r", "\n", or "\r\n".
+ /// </remarks>
+ public virtual string NewLine
+ {
+ get { return CoreNewLineStr; }
+ set
+ {
+ if (value == null)
+ {
+ value = Environment.NewLine;
+ }
+
+ CoreNewLineStr = value;
+ CoreNewLine = value.ToCharArray();
+ }
+ }
+
+
+ // Writes a character to the text stream. This default method is empty,
+ // but descendant classes can override the method to provide the
+ // appropriate functionality.
+ //
+ public virtual void Write(char value)
+ {
+ }
+
+ // Writes a character array to the text stream. This default method calls
+ // Write(char) for each of the characters in the character array.
+ // If the character array is null, nothing is written.
+ //
+ public virtual void Write(char[] buffer)
+ {
+ if (buffer != null)
+ {
+ Write(buffer, 0, buffer.Length);
+ }
+ }
+
+ // Writes a range of a character array to the text stream. This method will
+ // write count characters of data into this TextWriter from the
+ // buffer character array starting at position index.
+ //
+ public virtual void Write(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ }
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+
+ for (int i = 0; i < count; i++) Write(buffer[index + i]);
+ }
+
+ // Writes a span of characters to the text stream.
+ //
+ public virtual void Write(ReadOnlySpan<char> buffer)
+ {
+ char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
+
+ try
+ {
+ buffer.CopyTo(new Span<char>(array));
+ Write(array, 0, buffer.Length);
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(array);
+ }
+ }
+
+ // Writes the text representation of a boolean to the text stream. This
+ // method outputs either Boolean.TrueString or Boolean.FalseString.
+ //
+ public virtual void Write(bool value)
+ {
+ Write(value ? "True" : "False");
+ }
+
+ // Writes the text representation of an integer to the text stream. The
+ // text representation of the given value is produced by calling the
+ // Int32.ToString() method.
+ //
+ public virtual void Write(int value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of an integer to the text stream. The
+ // text representation of the given value is produced by calling the
+ // UInt32.ToString() method.
+ //
+ [CLSCompliant(false)]
+ public virtual void Write(uint value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of a long to the text stream. The
+ // text representation of the given value is produced by calling the
+ // Int64.ToString() method.
+ //
+ public virtual void Write(long value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of an unsigned long to the text
+ // stream. The text representation of the given value is produced
+ // by calling the UInt64.ToString() method.
+ //
+ [CLSCompliant(false)]
+ public virtual void Write(ulong value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of a float to the text stream. The
+ // text representation of the given value is produced by calling the
+ // Float.toString(float) method.
+ //
+ public virtual void Write(float value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes the text representation of a double to the text stream. The
+ // text representation of the given value is produced by calling the
+ // Double.toString(double) method.
+ //
+ public virtual void Write(double value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ public virtual void Write(decimal value)
+ {
+ Write(value.ToString(FormatProvider));
+ }
+
+ // Writes a string to the text stream. If the given string is null, nothing
+ // is written to the text stream.
+ //
+ public virtual void Write(string value)
+ {
+ if (value != null)
+ {
+ Write(value.ToCharArray());
+ }
+ }
+
+ // Writes the text representation of an object to the text stream. If the
+ // given object is null, nothing is written to the text stream.
+ // Otherwise, the object's ToString method is called to produce the
+ // string representation, and the resulting string is then written to the
+ // output stream.
+ //
+ public virtual void Write(object value)
+ {
+ if (value != null)
+ {
+ IFormattable f = value as IFormattable;
+ if (f != null)
+ {
+ Write(f.ToString(null, FormatProvider));
+ }
+ else
+ Write(value.ToString());
+ }
+ }
+
+ // Writes out a formatted string. Uses the same semantics as
+ // String.Format.
+ //
+ public virtual void Write(string format, object arg0)
+ {
+ Write(string.Format(FormatProvider, format, arg0));
+ }
+
+ // Writes out a formatted string. Uses the same semantics as
+ // String.Format.
+ //
+ public virtual void Write(string format, object arg0, object arg1)
+ {
+ Write(string.Format(FormatProvider, format, arg0, arg1));
+ }
+
+ // Writes out a formatted string. Uses the same semantics as
+ // String.Format.
+ //
+ public virtual void Write(string format, object arg0, object arg1, object arg2)
+ {
+ Write(string.Format(FormatProvider, format, arg0, arg1, arg2));
+ }
+
+ // Writes out a formatted string. Uses the same semantics as
+ // String.Format.
+ //
+ public virtual void Write(string format, params object[] arg)
+ {
+ Write(string.Format(FormatProvider, format, arg));
+ }
+
+
+ // Writes a line terminator to the text stream. The default line terminator
+ // is Environment.NewLine, but this value can be changed by setting the NewLine property.
+ //
+ public virtual void WriteLine()
+ {
+ Write(CoreNewLine);
+ }
+
+ // Writes a character followed by a line terminator to the text stream.
+ //
+ public virtual void WriteLine(char value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes an array of characters followed by a line terminator to the text
+ // stream.
+ //
+ public virtual void WriteLine(char[] buffer)
+ {
+ Write(buffer);
+ WriteLine();
+ }
+
+ // Writes a range of a character array followed by a line terminator to the
+ // text stream.
+ //
+ public virtual void WriteLine(char[] buffer, int index, int count)
+ {
+ Write(buffer, index, count);
+ WriteLine();
+ }
+
+ public virtual void WriteLine(ReadOnlySpan<char> buffer)
+ {
+ char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
+
+ try
+ {
+ buffer.CopyTo(new Span<char>(array));
+ WriteLine(array, 0, buffer.Length);
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(array);
+ }
+ }
+
+ // Writes the text representation of a boolean followed by a line
+ // terminator to the text stream.
+ //
+ public virtual void WriteLine(bool value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of an integer followed by a line
+ // terminator to the text stream.
+ //
+ public virtual void WriteLine(int value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of an unsigned integer followed by
+ // a line terminator to the text stream.
+ //
+ [CLSCompliant(false)]
+ public virtual void WriteLine(uint value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of a long followed by a line terminator
+ // to the text stream.
+ //
+ public virtual void WriteLine(long value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of an unsigned long followed by
+ // a line terminator to the text stream.
+ //
+ [CLSCompliant(false)]
+ public virtual void WriteLine(ulong value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of a float followed by a line terminator
+ // to the text stream.
+ //
+ public virtual void WriteLine(float value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes the text representation of a double followed by a line terminator
+ // to the text stream.
+ //
+ public virtual void WriteLine(double value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ public virtual void WriteLine(decimal value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ // Writes a string followed by a line terminator to the text stream.
+ //
+ public virtual void WriteLine(string value)
+ {
+ if (value != null)
+ {
+ Write(value);
+ }
+ Write(CoreNewLineStr);
+ }
+
+ // Writes the text representation of an object followed by a line
+ // terminator to the text stream.
+ //
+ public virtual void WriteLine(object value)
+ {
+ if (value == null)
+ {
+ WriteLine();
+ }
+ else
+ {
+ // Call WriteLine(value.ToString), not Write(Object), WriteLine().
+ // This makes calls to WriteLine(Object) atomic.
+ IFormattable f = value as IFormattable;
+ if (f != null)
+ {
+ WriteLine(f.ToString(null, FormatProvider));
+ }
+ else
+ {
+ WriteLine(value.ToString());
+ }
+ }
+ }
+
+ // Writes out a formatted string and a new line. Uses the same
+ // semantics as String.Format.
+ //
+ public virtual void WriteLine(string format, object arg0)
+ {
+ WriteLine(string.Format(FormatProvider, format, arg0));
+ }
+
+ // Writes out a formatted string and a new line. Uses the same
+ // semantics as String.Format.
+ //
+ public virtual void WriteLine(string format, object arg0, object arg1)
+ {
+ WriteLine(string.Format(FormatProvider, format, arg0, arg1));
+ }
+
+ // Writes out a formatted string and a new line. Uses the same
+ // semantics as String.Format.
+ //
+ public virtual void WriteLine(string format, object arg0, object arg1, object arg2)
+ {
+ WriteLine(string.Format(FormatProvider, format, arg0, arg1, arg2));
+ }
+
+ // Writes out a formatted string and a new line. Uses the same
+ // semantics as String.Format.
+ //
+ public virtual void WriteLine(string format, params object[] arg)
+ {
+ WriteLine(string.Format(FormatProvider, format, arg));
+ }
+
+ #region Task based Async APIs
+ public virtual Task WriteAsync(char value)
+ {
+ var tuple = new Tuple<TextWriter, char>(this, value);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, char>)state;
+ t.Item1.Write(t.Item2);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public virtual Task WriteAsync(string value)
+ {
+ var tuple = new Tuple<TextWriter, string>(this, value);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, string>)state;
+ t.Item1.Write(t.Item2);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public Task WriteAsync(char[] buffer)
+ {
+ if (buffer == null)
+ {
+ return Task.CompletedTask;
+ }
+
+ return WriteAsync(buffer, 0, buffer.Length);
+ }
+
+ public virtual Task WriteAsync(char[] buffer, int index, int count)
+ {
+ var tuple = new Tuple<TextWriter, char[], int, int>(this, buffer, index, count);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, char[], int, int>)state;
+ t.Item1.Write(t.Item2, t.Item3, t.Item4);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public virtual Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+ MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ?
+ WriteAsync(array.Array, array.Offset, array.Count) :
+ Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, ReadOnlyMemory<char>>)state;
+ t.Item1.Write(t.Item2.Span);
+ }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+
+ public virtual Task WriteLineAsync(char value)
+ {
+ var tuple = new Tuple<TextWriter, char>(this, value);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, char>)state;
+ t.Item1.WriteLine(t.Item2);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public virtual Task WriteLineAsync(string value)
+ {
+ var tuple = new Tuple<TextWriter, string>(this, value);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, string>)state;
+ t.Item1.WriteLine(t.Item2);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public Task WriteLineAsync(char[] buffer)
+ {
+ if (buffer == null)
+ {
+ return WriteLineAsync();
+ }
+
+ return WriteLineAsync(buffer, 0, buffer.Length);
+ }
+
+ public virtual Task WriteLineAsync(char[] buffer, int index, int count)
+ {
+ var tuple = new Tuple<TextWriter, char[], int, int>(this, buffer, index, count);
+ return Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, char[], int, int>)state;
+ t.Item1.WriteLine(t.Item2, t.Item3, t.Item4);
+ },
+ tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public virtual Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default(CancellationToken)) =>
+ MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ?
+ WriteLineAsync(array.Array, array.Offset, array.Count) :
+ Task.Factory.StartNew(state =>
+ {
+ var t = (Tuple<TextWriter, ReadOnlyMemory<char>>)state;
+ t.Item1.WriteLine(t.Item2.Span);
+ }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+
+ public virtual Task WriteLineAsync()
+ {
+ return WriteAsync(CoreNewLine);
+ }
+
+ public virtual Task FlushAsync()
+ {
+ return Task.Factory.StartNew(state =>
+ {
+ ((TextWriter)state).Flush();
+ },
+ this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+ #endregion
+
+ private sealed class NullTextWriter : TextWriter
+ {
+ internal NullTextWriter() : base(CultureInfo.InvariantCulture)
+ {
+ }
+
+ public override Encoding Encoding
+ {
+ get
+ {
+ return Encoding.Unicode;
+ }
+ }
+
+ public override void Write(char[] buffer, int index, int count)
+ {
+ }
+
+ public override void Write(string value)
+ {
+ }
+
+ // Not strictly necessary, but for perf reasons
+ public override void WriteLine()
+ {
+ }
+
+ // Not strictly necessary, but for perf reasons
+ public override void WriteLine(string value)
+ {
+ }
+
+ public override void WriteLine(object value)
+ {
+ }
+
+ public override void Write(char value)
+ {
+ }
+ }
+
+ public static TextWriter Synchronized(TextWriter writer)
+ {
+ if (writer == null)
+ throw new ArgumentNullException(nameof(writer));
+
+ return writer is SyncTextWriter ? writer : new SyncTextWriter(writer);
+ }
+
+ internal sealed class SyncTextWriter : TextWriter, IDisposable
+ {
+ private readonly TextWriter _out;
+
+ internal SyncTextWriter(TextWriter t) : base(t.FormatProvider)
+ {
+ _out = t;
+ }
+
+ public override Encoding Encoding => _out.Encoding;
+
+ public override IFormatProvider FormatProvider => _out.FormatProvider;
+
+ public override string NewLine
+ {
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ get { return _out.NewLine; }
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ set { _out.NewLine = value; }
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Close() => _out.Close();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ protected override void Dispose(bool disposing)
+ {
+ // Explicitly pick up a potentially methodimpl'ed Dispose
+ if (disposing)
+ ((IDisposable)_out).Dispose();
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Flush() => _out.Flush();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(char value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(char[] buffer) => _out.Write(buffer);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(char[] buffer, int index, int count) => _out.Write(buffer, index, count);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(bool value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(int value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(uint value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(long value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(ulong value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(float value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(double value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(Decimal value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(object value) => _out.Write(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string format, object arg0) => _out.Write(format, arg0);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string format, object arg0, object arg1) => _out.Write(format, arg0, arg1);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string format, object arg0, object arg1, object arg2) => _out.Write(format, arg0, arg1, arg2);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void Write(string format, object[] arg) => _out.Write(format, arg);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine() => _out.WriteLine();
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(char value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(decimal value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(char[] buffer) => _out.WriteLine(buffer);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(char[] buffer, int index, int count) => _out.WriteLine(buffer, index, count);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(bool value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(int value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(uint value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(long value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(ulong value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(float value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(double value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(object value) => _out.WriteLine(value);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string format, object arg0) => _out.WriteLine(format, arg0);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string format, object arg0, object arg1) => _out.WriteLine(format, arg0, arg1);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string format, object arg0, object arg1, object arg2) => _out.WriteLine(format, arg0, arg1, arg2);
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override void WriteLine(string format, object[] arg) => _out.WriteLine(format, arg);
+
+ //
+ // On SyncTextWriter all APIs should run synchronously, even the async ones.
+ //
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteAsync(char value)
+ {
+ Write(value);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteAsync(string value)
+ {
+ Write(value);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteAsync(char[] buffer, int index, int count)
+ {
+ Write(buffer, index, count);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteLineAsync(char value)
+ {
+ WriteLine(value);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteLineAsync(string value)
+ {
+ WriteLine(value);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task WriteLineAsync(char[] buffer, int index, int count)
+ {
+ WriteLine(buffer, index, count);
+ return Task.CompletedTask;
+ }
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public override Task FlushAsync()
+ {
+ Flush();
+ return Task.CompletedTask;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryAccessor.cs b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryAccessor.cs
new file mode 100644
index 0000000000..1247ad6d5d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryAccessor.cs
@@ -0,0 +1,670 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+** Purpose: Provides a fast, AV free, cross-language way of
+** accessing unmanaged memory in a random fashion.
+**
+**
+===========================================================*/
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Internal.Runtime.CompilerServices;
+
+namespace System.IO
+{
+ /// Perf notes: ReadXXX, WriteXXX (for basic types) acquire and release the
+ /// SafeBuffer pointer rather than relying on generic Read(T) from SafeBuffer because
+ /// this gives better throughput; benchmarks showed about 12-15% better.
+ public class UnmanagedMemoryAccessor : IDisposable
+ {
+ private SafeBuffer _buffer;
+ private long _offset;
+ private long _capacity;
+ private FileAccess _access;
+ private bool _isOpen;
+ private bool _canRead;
+ private bool _canWrite;
+
+ protected UnmanagedMemoryAccessor()
+ {
+ _isOpen = false;
+ }
+
+ public UnmanagedMemoryAccessor(SafeBuffer buffer, long offset, long capacity)
+ {
+ Initialize(buffer, offset, capacity, FileAccess.Read);
+ }
+
+ public UnmanagedMemoryAccessor(SafeBuffer buffer, long offset, long capacity, FileAccess access)
+ {
+ Initialize(buffer, offset, capacity, access);
+ }
+
+ protected void Initialize(SafeBuffer buffer, long offset, long capacity, FileAccess access)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (capacity < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.ByteLength < (ulong)(offset + capacity))
+ {
+ throw new ArgumentException(SR.Argument_OffsetAndCapacityOutOfBounds);
+ }
+ if (access < FileAccess.Read || access > FileAccess.ReadWrite)
+ {
+ throw new ArgumentOutOfRangeException(nameof(access));
+ }
+
+ if (_isOpen)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_CalledTwice);
+ }
+
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ buffer.AcquirePointer(ref pointer);
+ if (((byte*)((long)pointer + offset + capacity)) < pointer)
+ {
+ throw new ArgumentException(SR.Argument_UnmanagedMemAccessorWrapAround);
+ }
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ buffer.ReleasePointer();
+ }
+ }
+ }
+
+ _offset = offset;
+ _buffer = buffer;
+ _capacity = capacity;
+ _access = access;
+ _isOpen = true;
+ _canRead = (_access & FileAccess.Read) != 0;
+ _canWrite = (_access & FileAccess.Write) != 0;
+ }
+
+ public long Capacity => _capacity;
+
+ public bool CanRead => _isOpen && _canRead;
+
+ public bool CanWrite => _isOpen && _canWrite;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ _isOpen = false;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected bool IsOpen => _isOpen;
+
+ // ************** Read Methods ****************/
+
+ public bool ReadBoolean(long position) => ReadByte(position) != 0;
+
+ public byte ReadByte(long position)
+ {
+ EnsureSafeToRead(position, sizeof(byte));
+
+ byte result;
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ result = *((byte*)(pointer + _offset + position));
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ return result;
+ }
+
+ public char ReadChar(long position) => unchecked((char)ReadInt16(position));
+
+ public short ReadInt16(long position)
+ {
+ EnsureSafeToRead(position, sizeof(short));
+
+ short result;
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ result = Unsafe.ReadUnaligned<short>(pointer + _offset + position);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ return result;
+ }
+
+ public int ReadInt32(long position)
+ {
+ EnsureSafeToRead(position, sizeof(int));
+
+ int result;
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ result = Unsafe.ReadUnaligned<int>(pointer + _offset + position);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ return result;
+ }
+
+ public long ReadInt64(long position)
+ {
+ EnsureSafeToRead(position, sizeof(long));
+
+ long result;
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ result = Unsafe.ReadUnaligned<long>(pointer + _offset + position);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ return result;
+ }
+
+ public decimal ReadDecimal(long position)
+ {
+ const int ScaleMask = 0x00FF0000;
+ const int SignMask = unchecked((int)0x80000000);
+
+ EnsureSafeToRead(position, sizeof(decimal));
+
+ int lo, mid, hi, flags;
+
+ unsafe
+ {
+ byte* pointer = null;
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ pointer += (_offset + position);
+
+ lo = Unsafe.ReadUnaligned<int>(pointer);
+ mid = Unsafe.ReadUnaligned<int>(pointer + 4);
+ hi = Unsafe.ReadUnaligned<int>(pointer + 8);
+ flags = Unsafe.ReadUnaligned<int>(pointer + 12);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+
+ // Check for invalid Decimal values
+ if (!((flags & ~(SignMask | ScaleMask)) == 0 && (flags & ScaleMask) <= (28 << 16)))
+ {
+ throw new ArgumentException(SR.Arg_BadDecimal); // Throw same Exception type as Decimal(int[]) ctor for compat
+ }
+
+ bool isNegative = (flags & SignMask) != 0;
+ byte scale = (byte)(flags >> 16);
+
+ return new decimal(lo, mid, hi, isNegative, scale);
+ }
+
+ public float ReadSingle(long position) => BitConverter.Int32BitsToSingle(ReadInt32(position));
+
+ public double ReadDouble(long position) => BitConverter.Int64BitsToDouble(ReadInt64(position));
+
+ [CLSCompliant(false)]
+ public sbyte ReadSByte(long position) => unchecked((sbyte)ReadByte(position));
+
+ [CLSCompliant(false)]
+ public ushort ReadUInt16(long position) => unchecked((ushort)ReadInt16(position));
+
+ [CLSCompliant(false)]
+ public uint ReadUInt32(long position) => unchecked((uint)ReadInt32(position));
+
+ [CLSCompliant(false)]
+ public ulong ReadUInt64(long position) => unchecked((ulong)ReadInt64(position));
+
+ // Reads a struct of type T from unmanaged memory, into the reference pointed to by ref value.
+ // Note: this method is not safe, since it overwrites the contents of a structure, it can be
+ // used to modify the private members of a struct.
+ // This method is most performant when used with medium to large sized structs
+ // (larger than 8 bytes -- though this is number is JIT and architecture dependent). As
+ // such, it is best to use the ReadXXX methods for small standard types such as ints, longs,
+ // bools, etc.
+ public void Read<T>(long position, out T structure) where T : struct
+ {
+ if (position < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (!_isOpen)
+ {
+ throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed);
+ }
+ if (!_canRead)
+ {
+ throw new NotSupportedException(SR.NotSupported_Reading);
+ }
+
+ uint sizeOfT = SafeBuffer.SizeOf<T>();
+ if (position > _capacity - sizeOfT)
+ {
+ if (position >= _capacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired);
+ }
+ else
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_NotEnoughBytesToRead, typeof(T)), nameof(position));
+ }
+ }
+
+ structure = _buffer.Read<T>((ulong)(_offset + position));
+ }
+
+ // Reads 'count' structs of type T from unmanaged memory, into 'array' starting at 'offset'.
+ // Note: this method is not safe, since it overwrites the contents of structures, it can
+ // be used to modify the private members of a struct.
+ public int ReadArray<T>(long position, T[] array, int offset, int count) where T : struct
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (array.Length - offset < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+ if (!_isOpen)
+ {
+ throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed);
+ }
+ if (!_canRead)
+ {
+ throw new NotSupportedException(SR.NotSupported_Reading);
+ }
+ if (position < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ uint sizeOfT = SafeBuffer.AlignedSizeOf<T>();
+
+ // only check position and ask for fewer Ts if count is too big
+ if (position >= _capacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired);
+ }
+
+ int n = count;
+ long spaceLeft = _capacity - position;
+ if (spaceLeft < 0)
+ {
+ n = 0;
+ }
+ else
+ {
+ ulong spaceNeeded = (ulong)(sizeOfT * count);
+ if ((ulong)spaceLeft < spaceNeeded)
+ {
+ n = (int)(spaceLeft / sizeOfT);
+ }
+ }
+
+ _buffer.ReadArray<T>((ulong)(_offset + position), array, offset, n);
+
+ return n;
+ }
+
+ // ************** Write Methods ****************/
+
+ public void Write(long position, bool value) => Write(position, (byte)(value ? 1 : 0));
+
+ public void Write(long position, byte value)
+ {
+ EnsureSafeToWrite(position, sizeof(byte));
+
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ *((byte*)(pointer + _offset + position)) = value;
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ }
+
+ public void Write(long position, char value) => Write(position, unchecked((short)value));
+
+ public void Write(long position, short value)
+ {
+ EnsureSafeToWrite(position, sizeof(short));
+
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ Unsafe.WriteUnaligned<short>(pointer + _offset + position, value);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ }
+
+ public void Write(long position, int value)
+ {
+ EnsureSafeToWrite(position, sizeof(int));
+
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ Unsafe.WriteUnaligned<int>(pointer + _offset + position, value);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ }
+
+ public void Write(long position, long value)
+ {
+ EnsureSafeToWrite(position, sizeof(long));
+
+ unsafe
+ {
+ byte* pointer = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ Unsafe.WriteUnaligned<long>(pointer + _offset + position, value);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ }
+
+ public void Write(long position, decimal value)
+ {
+ EnsureSafeToWrite(position, sizeof(decimal));
+
+ unsafe
+ {
+ int* valuePtr = (int*)(&value);
+ int flags = *valuePtr;
+ int hi = *(valuePtr + 1);
+ int lo = *(valuePtr + 2);
+ int mid = *(valuePtr + 3);
+
+ byte* pointer = null;
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ pointer += (_offset + position);
+
+ Unsafe.WriteUnaligned<int>(pointer, lo);
+ Unsafe.WriteUnaligned<int>(pointer + 4, mid);
+ Unsafe.WriteUnaligned<int>(pointer + 8, hi);
+ Unsafe.WriteUnaligned<int>(pointer + 12, flags);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ }
+
+ public void Write(long position, float value) => Write(position, BitConverter.SingleToInt32Bits(value));
+
+ public void Write(long position, double value) => Write(position, BitConverter.DoubleToInt64Bits(value));
+
+ [CLSCompliant(false)]
+ public void Write(long position, sbyte value) => Write(position, unchecked((byte)value));
+
+ [CLSCompliant(false)]
+ public void Write(long position, ushort value) => Write(position, unchecked((short)value));
+
+ [CLSCompliant(false)]
+ public void Write(long position, uint value) => Write(position, unchecked((int)value));
+
+ [CLSCompliant(false)]
+ public void Write(long position, ulong value) => Write(position, unchecked((long)value));
+
+ // Writes the struct pointed to by ref value into unmanaged memory. Note that this method
+ // is most performant when used with medium to large sized structs (larger than 8 bytes
+ // though this is number is JIT and architecture dependent). As such, it is best to use
+ // the WriteX methods for small standard types such as ints, longs, bools, etc.
+ public void Write<T>(long position, ref T structure) where T : struct
+ {
+ if (position < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (!_isOpen)
+ {
+ throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed);
+ }
+ if (!_canWrite)
+ {
+ throw new NotSupportedException(SR.NotSupported_Writing);
+ }
+
+ uint sizeOfT = SafeBuffer.SizeOf<T>();
+ if (position > _capacity - sizeOfT)
+ {
+ if (position >= _capacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired);
+ }
+ else
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_NotEnoughBytesToWrite, typeof(T)), nameof(position));
+ }
+ }
+
+ _buffer.Write<T>((ulong)(_offset + position), structure);
+ }
+
+ // Writes 'count' structs of type T from 'array' (starting at 'offset') into unmanaged memory.
+ public void WriteArray<T>(long position, T[] array, int offset, int count) where T : struct
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (array.Length - offset < count)
+ {
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+ }
+ if (position < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (position >= Capacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired);
+ }
+
+ if (!_isOpen)
+ {
+ throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed);
+ }
+ if (!_canWrite)
+ {
+ throw new NotSupportedException(SR.NotSupported_Writing);
+ }
+
+ _buffer.WriteArray<T>((ulong)(_offset + position), array, offset, count);
+ }
+
+ private void EnsureSafeToRead(long position, int sizeOfType)
+ {
+ if (!_isOpen)
+ {
+ throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed);
+ }
+ if (!_canRead)
+ {
+ throw new NotSupportedException(SR.NotSupported_Reading);
+ }
+ if (position < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (position > _capacity - sizeOfType)
+ {
+ if (position >= _capacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired);
+ }
+ else
+ {
+ throw new ArgumentException(SR.Argument_NotEnoughBytesToRead, nameof(position));
+ }
+ }
+ }
+
+ private void EnsureSafeToWrite(long position, int sizeOfType)
+ {
+ if (!_isOpen)
+ {
+ throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed);
+ }
+ if (!_canWrite)
+ {
+ throw new NotSupportedException(SR.NotSupported_Writing);
+ }
+ if (position < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (position > _capacity - sizeOfType)
+ {
+ if (position >= _capacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired);
+ }
+ else
+ {
+ throw new ArgumentException(SR.Argument_NotEnoughBytesToWrite, nameof(position));
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs
new file mode 100644
index 0000000000..2bcb16f24e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs
@@ -0,0 +1,882 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ /*
+ * This class is used to access a contiguous block of memory, likely outside
+ * the GC heap (or pinned in place in the GC heap, but a MemoryStream may
+ * make more sense in those cases). It's great if you have a pointer and
+ * a length for a section of memory mapped in by someone else and you don't
+ * want to copy this into the GC heap. UnmanagedMemoryStream assumes these
+ * two things:
+ *
+ * 1) All the memory in the specified block is readable or writable,
+ * depending on the values you pass to the constructor.
+ * 2) The lifetime of the block of memory is at least as long as the lifetime
+ * of the UnmanagedMemoryStream.
+ * 3) You clean up the memory when appropriate. The UnmanagedMemoryStream
+ * currently will do NOTHING to free this memory.
+ * 4) All calls to Write and WriteByte may not be threadsafe currently.
+ *
+ * It may become necessary to add in some sort of
+ * DeallocationMode enum, specifying whether we unmap a section of memory,
+ * call free, run a user-provided delegate to free the memory, etc.
+ * We'll suggest user write a subclass of UnmanagedMemoryStream that uses
+ * a SafeHandle subclass to hold onto the memory.
+ *
+ */
+
+ /// <summary>
+ /// Stream over a memory pointer or over a SafeBuffer
+ /// </summary>
+ public class UnmanagedMemoryStream : Stream
+ {
+ private SafeBuffer _buffer;
+ private unsafe byte* _mem;
+ private long _length;
+ private long _capacity;
+ private long _position;
+ private long _offset;
+ private FileAccess _access;
+ private bool _isOpen;
+ private Task<Int32> _lastReadTask; // The last successful task returned from ReadAsync
+
+ /// <summary>
+ /// Creates a closed stream.
+ /// </summary>
+ // Needed for subclasses that need to map a file, etc.
+ protected UnmanagedMemoryStream()
+ {
+ unsafe
+ {
+ _mem = null;
+ }
+ _isOpen = false;
+ }
+
+ /// <summary>
+ /// Creates a stream over a SafeBuffer.
+ /// </summary>
+ /// <param name="buffer"></param>
+ /// <param name="offset"></param>
+ /// <param name="length"></param>
+ public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length)
+ {
+ Initialize(buffer, offset, length, FileAccess.Read);
+ }
+
+ /// <summary>
+ /// Creates a stream over a SafeBuffer.
+ /// </summary>
+ public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access)
+ {
+ Initialize(buffer, offset, length, access);
+ }
+
+ /// <summary>
+ /// Subclasses must call this method (or the other overload) to properly initialize all instance fields.
+ /// </summary>
+ /// <param name="buffer"></param>
+ /// <param name="offset"></param>
+ /// <param name="length"></param>
+ /// <param name="access"></param>
+ protected void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (buffer.ByteLength < (ulong)(offset + length))
+ {
+ throw new ArgumentException(SR.Argument_InvalidSafeBufferOffLen);
+ }
+ if (access < FileAccess.Read || access > FileAccess.ReadWrite)
+ {
+ throw new ArgumentOutOfRangeException(nameof(access));
+ }
+
+ if (_isOpen)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_CalledTwice);
+ }
+
+ // check for wraparound
+ unsafe
+ {
+ byte* pointer = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ buffer.AcquirePointer(ref pointer);
+ if ((pointer + offset + length) < pointer)
+ {
+ throw new ArgumentException(SR.ArgumentOutOfRange_UnmanagedMemStreamWrapAround);
+ }
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ buffer.ReleasePointer();
+ }
+ }
+ }
+
+ _offset = offset;
+ _buffer = buffer;
+ _length = length;
+ _capacity = length;
+ _access = access;
+ _isOpen = true;
+ }
+
+ /// <summary>
+ /// Creates a stream over a byte*.
+ /// </summary>
+ [CLSCompliant(false)]
+ public unsafe UnmanagedMemoryStream(byte* pointer, long length)
+ {
+ Initialize(pointer, length, length, FileAccess.Read);
+ }
+
+ /// <summary>
+ /// Creates a stream over a byte*.
+ /// </summary>
+ [CLSCompliant(false)]
+ public unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access)
+ {
+ Initialize(pointer, length, capacity, access);
+ }
+
+ /// <summary>
+ /// Subclasses must call this method (or the other overload) to properly initialize all instance fields.
+ /// </summary>
+ [CLSCompliant(false)]
+ protected unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access)
+ {
+ if (pointer == null)
+ throw new ArgumentNullException(nameof(pointer));
+ if (length < 0 || capacity < 0)
+ throw new ArgumentOutOfRangeException((length < 0) ? nameof(length) : nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (length > capacity)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
+ // Check for wraparound.
+ if (((byte*)((long)pointer + capacity)) < pointer)
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_UnmanagedMemStreamWrapAround);
+ if (access < FileAccess.Read || access > FileAccess.ReadWrite)
+ throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum);
+ if (_isOpen)
+ throw new InvalidOperationException(SR.InvalidOperation_CalledTwice);
+
+ _mem = pointer;
+ _offset = 0;
+ _length = length;
+ _capacity = capacity;
+ _access = access;
+ _isOpen = true;
+ }
+
+ /// <summary>
+ /// Returns true if the stream can be read; otherwise returns false.
+ /// </summary>
+ public override bool CanRead
+ {
+ get { return _isOpen && (_access & FileAccess.Read) != 0; }
+ }
+
+ /// <summary>
+ /// Returns true if the stream can seek; otherwise returns false.
+ /// </summary>
+ public override bool CanSeek
+ {
+ get { return _isOpen; }
+ }
+
+ /// <summary>
+ /// Returns true if the stream can be written to; otherwise returns false.
+ /// </summary>
+ public override bool CanWrite
+ {
+ get { return _isOpen && (_access & FileAccess.Write) != 0; }
+ }
+
+ /// <summary>
+ /// Closes the stream. The stream's memory needs to be dealt with separately.
+ /// </summary>
+ /// <param name="disposing"></param>
+ protected override void Dispose(bool disposing)
+ {
+ _isOpen = false;
+ unsafe { _mem = null; }
+
+ // Stream allocates WaitHandles for async calls. So for correctness
+ // call base.Dispose(disposing) for better perf, avoiding waiting
+ // for the finalizers to run on those types.
+ base.Dispose(disposing);
+ }
+
+ private void EnsureNotClosed()
+ {
+ if (!_isOpen)
+ throw Error.GetStreamIsClosed();
+ }
+
+ private void EnsureReadable()
+ {
+ if (!CanRead)
+ throw Error.GetReadNotSupported();
+ }
+
+ private void EnsureWriteable()
+ {
+ if (!CanWrite)
+ throw Error.GetWriteNotSupported();
+ }
+
+ /// <summary>
+ /// Since it's a memory stream, this method does nothing.
+ /// </summary>
+ public override void Flush()
+ {
+ EnsureNotClosed();
+ }
+
+ /// <summary>
+ /// Since it's a memory stream, this method does nothing specific.
+ /// </summary>
+ /// <param name="cancellationToken"></param>
+ /// <returns></returns>
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ try
+ {
+ Flush();
+ return Task.CompletedTask;
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+
+ /// <summary>
+ /// Number of bytes in the stream.
+ /// </summary>
+ public override long Length
+ {
+ get
+ {
+ EnsureNotClosed();
+ return Interlocked.Read(ref _length);
+ }
+ }
+
+ /// <summary>
+ /// Number of bytes that can be written to the stream.
+ /// </summary>
+ public long Capacity
+ {
+ get
+ {
+ EnsureNotClosed();
+ return _capacity;
+ }
+ }
+
+ /// <summary>
+ /// ReadByte will read byte at the Position in the stream
+ /// </summary>
+ public override long Position
+ {
+ get
+ {
+ if (!CanSeek) throw Error.GetStreamIsClosed();
+ return Interlocked.Read(ref _position);
+ }
+ set
+ {
+ if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (!CanSeek) throw Error.GetStreamIsClosed();
+
+ Interlocked.Exchange(ref _position, value);
+ }
+ }
+
+ /// <summary>
+ /// Pointer to memory at the current Position in the stream.
+ /// </summary>
+ [CLSCompliant(false)]
+ public unsafe byte* PositionPointer
+ {
+ get
+ {
+ if (_buffer != null)
+ throw new NotSupportedException(SR.NotSupported_UmsSafeBuffer);
+
+ EnsureNotClosed();
+
+ // Use a temp to avoid a race
+ long pos = Interlocked.Read(ref _position);
+ if (pos > _capacity)
+ throw new IndexOutOfRangeException(SR.IndexOutOfRange_UMSPosition);
+ byte* ptr = _mem + pos;
+ return ptr;
+ }
+ set
+ {
+ if (_buffer != null)
+ throw new NotSupportedException(SR.NotSupported_UmsSafeBuffer);
+
+ EnsureNotClosed();
+
+ if (value < _mem)
+ throw new IOException(SR.IO_SeekBeforeBegin);
+ long newPosition = (long)value - (long)_mem;
+ if (newPosition < 0)
+ throw new ArgumentOutOfRangeException("offset", SR.ArgumentOutOfRange_UnmanagedMemStreamLength);
+
+ Interlocked.Exchange(ref _position, newPosition);
+ }
+ }
+
+ /// <summary>
+ /// Reads bytes from stream and puts them into the buffer
+ /// </summary>
+ /// <param name="buffer">Buffer to read the bytes to.</param>
+ /// <param name="offset">Starting index in the buffer.</param>
+ /// <param name="count">Maximum number of bytes to read.</param>
+ /// <returns>Number of bytes actually read.</returns>
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ return ReadCore(new Span<byte>(buffer, offset, count));
+ }
+
+ public override int Read(Span<byte> buffer)
+ {
+ if (GetType() == typeof(UnmanagedMemoryStream))
+ {
+ return ReadCore(buffer);
+ }
+ else
+ {
+ // UnmanagedMemoryStream is not sealed, and a derived type may have overridden Read(byte[], int, int) prior
+ // to this Read(Span<byte>) overload being introduced. In that case, this Read(Span<byte>) overload
+ // should use the behavior of Read(byte[],int,int) overload.
+ return base.Read(buffer);
+ }
+ }
+
+ internal int ReadCore(Span<byte> buffer)
+ {
+ EnsureNotClosed();
+ EnsureReadable();
+
+ // Use a local variable to avoid a race where another thread
+ // changes our position after we decide we can read some bytes.
+ long pos = Interlocked.Read(ref _position);
+ long len = Interlocked.Read(ref _length);
+ long n = Math.Min(len - pos, buffer.Length);
+ if (n <= 0)
+ {
+ return 0;
+ }
+
+ int nInt = (int)n; // Safe because n <= count, which is an Int32
+ if (nInt < 0)
+ {
+ return 0; // _position could be beyond EOF
+ }
+ Debug.Assert(pos + nInt >= 0, "_position + n >= 0"); // len is less than 2^63 -1.
+
+ unsafe
+ {
+ fixed (byte* pBuffer = &MemoryMarshal.GetReference(buffer))
+ {
+ if (_buffer != null)
+ {
+ byte* pointer = null;
+
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ Buffer.Memcpy(pBuffer, pointer + pos + _offset, nInt);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ else
+ {
+ Buffer.Memcpy(pBuffer, _mem + pos, nInt);
+ }
+ }
+ }
+ Interlocked.Exchange(ref _position, pos + n);
+ return nInt;
+ }
+
+ /// <summary>
+ /// Reads bytes from stream and puts them into the buffer
+ /// </summary>
+ /// <param name="buffer">Buffer to read the bytes to.</param>
+ /// <param name="offset">Starting index in the buffer.</param>
+ /// <param name="count">Maximum number of bytes to read.</param>
+ /// <param name="cancellationToken">Token that can be used to cancel this operation.</param>
+ /// <returns>Task that can be used to access the number of bytes actually read.</returns>
+ public override Task<Int32> ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled<Int32>(cancellationToken);
+
+ try
+ {
+ Int32 n = Read(buffer, offset, count);
+ Task<Int32> t = _lastReadTask;
+ return (t != null && t.Result == n) ? t : (_lastReadTask = Task.FromResult<Int32>(n));
+ }
+ catch (Exception ex)
+ {
+ Debug.Assert(!(ex is OperationCanceledException));
+ return Task.FromException<Int32>(ex);
+ }
+ }
+
+ /// <summary>
+ /// Reads bytes from stream and puts them into the buffer
+ /// </summary>
+ /// <param name="buffer">Buffer to read the bytes to.</param>
+ /// <param name="cancellationToken">Token that can be used to cancel this operation.</param>
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
+ }
+
+ try
+ {
+ // ReadAsync(Memory<byte>,...) needs to delegate to an existing virtual to do the work, in case an existing derived type
+ // has changed or augmented the logic associated with reads. If the Memory wraps an array, we could delegate to
+ // ReadAsync(byte[], ...), but that would defeat part of the purpose, as ReadAsync(byte[], ...) often needs to allocate
+ // a Task<int> for the return value, so we want to delegate to one of the synchronous methods. We could always
+ // delegate to the Read(Span<byte>) method, and that's the most efficient solution when dealing with a concrete
+ // UnmanagedMemoryStream, but if we're dealing with a type derived from UnmanagedMemoryStream, Read(Span<byte>) will end up delegating
+ // to Read(byte[], ...), which requires it to get a byte[] from ArrayPool and copy the data. So, we special-case the
+ // very common case of the Memory<byte> wrapping an array: if it does, we delegate to Read(byte[], ...) with it,
+ // as that will be efficient in both cases, and we fall back to Read(Span<byte>) if the Memory<byte> wrapped something
+ // else; if this is a concrete UnmanagedMemoryStream, that'll be efficient, and only in the case where the Memory<byte> wrapped
+ // something other than an array and this is an UnmanagedMemoryStream-derived type that doesn't override Read(Span<byte>) will
+ // it then fall back to doing the ArrayPool/copy behavior.
+ return new ValueTask<int>(
+ MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> destinationArray) ?
+ Read(destinationArray.Array, destinationArray.Offset, destinationArray.Count) :
+ Read(buffer.Span));
+ }
+ catch (Exception ex)
+ {
+ return new ValueTask<int>(Task.FromException<int>(ex));
+ }
+ }
+
+ /// <summary>
+ /// Returns the byte at the stream current Position and advances the Position.
+ /// </summary>
+ /// <returns></returns>
+ public override int ReadByte()
+ {
+ EnsureNotClosed();
+ EnsureReadable();
+
+ long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
+ long len = Interlocked.Read(ref _length);
+ if (pos >= len)
+ return -1;
+ Interlocked.Exchange(ref _position, pos + 1);
+ int result;
+ if (_buffer != null)
+ {
+ unsafe
+ {
+ byte* pointer = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ result = *(pointer + pos + _offset);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ }
+ else
+ {
+ unsafe
+ {
+ result = _mem[pos];
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Advanced the Position to specific location in the stream.
+ /// </summary>
+ /// <param name="offset">Offset from the loc parameter.</param>
+ /// <param name="loc">Origin for the offset parameter.</param>
+ /// <returns></returns>
+ public override long Seek(long offset, SeekOrigin loc)
+ {
+ EnsureNotClosed();
+
+ switch (loc)
+ {
+ case SeekOrigin.Begin:
+ if (offset < 0)
+ throw new IOException(SR.IO_SeekBeforeBegin);
+ Interlocked.Exchange(ref _position, offset);
+ break;
+
+ case SeekOrigin.Current:
+ long pos = Interlocked.Read(ref _position);
+ if (offset + pos < 0)
+ throw new IOException(SR.IO_SeekBeforeBegin);
+ Interlocked.Exchange(ref _position, offset + pos);
+ break;
+
+ case SeekOrigin.End:
+ long len = Interlocked.Read(ref _length);
+ if (len + offset < 0)
+ throw new IOException(SR.IO_SeekBeforeBegin);
+ Interlocked.Exchange(ref _position, len + offset);
+ break;
+
+ default:
+ throw new ArgumentException(SR.Argument_InvalidSeekOrigin);
+ }
+
+ long finalPos = Interlocked.Read(ref _position);
+ Debug.Assert(finalPos >= 0, "_position >= 0");
+ return finalPos;
+ }
+
+ /// <summary>
+ /// Sets the Length of the stream.
+ /// </summary>
+ /// <param name="value"></param>
+ public override void SetLength(long value)
+ {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (_buffer != null)
+ throw new NotSupportedException(SR.NotSupported_UmsSafeBuffer);
+
+ EnsureNotClosed();
+ EnsureWriteable();
+
+ if (value > _capacity)
+ throw new IOException(SR.IO_FixedCapacity);
+
+ long pos = Interlocked.Read(ref _position);
+ long len = Interlocked.Read(ref _length);
+ if (value > len)
+ {
+ unsafe
+ {
+ Buffer.ZeroMemory(_mem + len, value - len);
+ }
+ }
+ Interlocked.Exchange(ref _length, value);
+ if (pos > value)
+ {
+ Interlocked.Exchange(ref _position, value);
+ }
+ }
+
+ /// <summary>
+ /// Writes buffer into the stream
+ /// </summary>
+ /// <param name="buffer">Buffer that will be written.</param>
+ /// <param name="offset">Starting index in the buffer.</param>
+ /// <param name="count">Number of bytes to write.</param>
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ WriteCore(new Span<byte>(buffer, offset, count));
+ }
+
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ if (GetType() == typeof(UnmanagedMemoryStream))
+ {
+ WriteCore(buffer);
+ }
+ else
+ {
+ // UnmanagedMemoryStream is not sealed, and a derived type may have overridden Write(byte[], int, int) prior
+ // to this Write(Span<byte>) overload being introduced. In that case, this Write(Span<byte>) overload
+ // should use the behavior of Write(byte[],int,int) overload.
+ base.Write(buffer);
+ }
+ }
+
+ internal unsafe void WriteCore(ReadOnlySpan<byte> buffer)
+ {
+ EnsureNotClosed();
+ EnsureWriteable();
+
+ long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
+ long len = Interlocked.Read(ref _length);
+ long n = pos + buffer.Length;
+ // Check for overflow
+ if (n < 0)
+ {
+ throw new IOException(SR.IO_StreamTooLong);
+ }
+
+ if (n > _capacity)
+ {
+ throw new NotSupportedException(SR.IO_FixedCapacity);
+ }
+
+ if (_buffer == null)
+ {
+ // Check to see whether we are now expanding the stream and must
+ // zero any memory in the middle.
+ if (pos > len)
+ {
+ Buffer.ZeroMemory(_mem + len, pos - len);
+ }
+
+ // set length after zeroing memory to avoid race condition of accessing unzeroed memory
+ if (n > len)
+ {
+ Interlocked.Exchange(ref _length, n);
+ }
+ }
+
+ fixed (byte* pBuffer = &MemoryMarshal.GetReference(buffer))
+ {
+ if (_buffer != null)
+ {
+ long bytesLeft = _capacity - pos;
+ if (bytesLeft < buffer.Length)
+ {
+ throw new ArgumentException(SR.Arg_BufferTooSmall);
+ }
+
+ byte* pointer = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ Buffer.Memcpy(pointer + pos + _offset, pBuffer, buffer.Length);
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ else
+ {
+ Buffer.Memcpy(_mem + pos, pBuffer, buffer.Length);
+ }
+ }
+
+ Interlocked.Exchange(ref _position, n);
+ return;
+ }
+
+ /// <summary>
+ /// Writes buffer into the stream. The operation completes synchronously.
+ /// </summary>
+ /// <param name="buffer">Buffer that will be written.</param>
+ /// <param name="offset">Starting index in the buffer.</param>
+ /// <param name="count">Number of bytes to write.</param>
+ /// <param name="cancellationToken">Token that can be used to cancel the operation.</param>
+ /// <returns>Task that can be awaited </returns>
+ public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (buffer.Length - offset < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ if (cancellationToken.IsCancellationRequested)
+ return Task.FromCanceled(cancellationToken);
+
+ try
+ {
+ Write(buffer, offset, count);
+ return Task.CompletedTask;
+ }
+ catch (Exception ex)
+ {
+ Debug.Assert(!(ex is OperationCanceledException));
+ return Task.FromException(ex);
+ }
+ }
+
+ /// <summary>
+ /// Writes buffer into the stream. The operation completes synchronously.
+ /// </summary>
+ /// <param name="buffer">Buffer that will be written.</param>
+ /// <param name="cancellationToken">Token that can be used to cancel the operation.</param>
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask(Task.FromCanceled(cancellationToken));
+ }
+
+ try
+ {
+ // See corresponding comment in ReadAsync for why we don't just always use Write(ReadOnlySpan<byte>).
+ // Unlike ReadAsync, we could delegate to WriteAsync(byte[], ...) here, but we don't for consistency.
+ if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> sourceArray))
+ {
+ Write(sourceArray.Array, sourceArray.Offset, sourceArray.Count);
+ }
+ else
+ {
+ Write(buffer.Span);
+ }
+ return default;
+ }
+ catch (Exception ex)
+ {
+ return new ValueTask(Task.FromException(ex));
+ }
+ }
+
+ /// <summary>
+ /// Writes a byte to the stream and advances the current Position.
+ /// </summary>
+ /// <param name="value"></param>
+ public override void WriteByte(byte value)
+ {
+ EnsureNotClosed();
+ EnsureWriteable();
+
+ long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
+ long len = Interlocked.Read(ref _length);
+ long n = pos + 1;
+ if (pos >= len)
+ {
+ // Check for overflow
+ if (n < 0)
+ throw new IOException(SR.IO_StreamTooLong);
+
+ if (n > _capacity)
+ throw new NotSupportedException(SR.IO_FixedCapacity);
+
+ // Check to see whether we are now expanding the stream and must
+ // zero any memory in the middle.
+ // don't do if created from SafeBuffer
+ if (_buffer == null)
+ {
+ if (pos > len)
+ {
+ unsafe
+ {
+ Buffer.ZeroMemory(_mem + len, pos - len);
+ }
+ }
+
+ // set length after zeroing memory to avoid race condition of accessing unzeroed memory
+ Interlocked.Exchange(ref _length, n);
+ }
+ }
+
+ if (_buffer != null)
+ {
+ unsafe
+ {
+ byte* pointer = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ _buffer.AcquirePointer(ref pointer);
+ *(pointer + pos + _offset) = value;
+ }
+ finally
+ {
+ if (pointer != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+ }
+ else
+ {
+ unsafe
+ {
+ _mem[pos] = value;
+ }
+ }
+ Interlocked.Exchange(ref _position, n);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs
new file mode 100644
index 0000000000..65a33961db
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs
@@ -0,0 +1,226 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+** Purpose: Create a Memorystream over an UnmanagedMemoryStream
+**
+===========================================================*/
+
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ // Needed for backwards compatibility with V1.x usages of the
+ // ResourceManager, where a MemoryStream is now returned as an
+ // UnmanagedMemoryStream from ResourceReader.
+ internal sealed class UnmanagedMemoryStreamWrapper : MemoryStream
+ {
+ private UnmanagedMemoryStream _unmanagedStream;
+
+ internal UnmanagedMemoryStreamWrapper(UnmanagedMemoryStream stream)
+ {
+ _unmanagedStream = stream;
+ }
+
+ public override bool CanRead
+ {
+ get { return _unmanagedStream.CanRead; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return _unmanagedStream.CanSeek; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return _unmanagedStream.CanWrite; }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ if (disposing)
+ _unmanagedStream.Dispose();
+ }
+ finally
+ {
+ base.Dispose(disposing);
+ }
+ }
+
+ public override void Flush()
+ {
+ _unmanagedStream.Flush();
+ }
+
+ public override byte[] GetBuffer()
+ {
+ throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer);
+ }
+
+ public override bool TryGetBuffer(out ArraySegment<byte> buffer)
+ {
+ buffer = default(ArraySegment<byte>);
+ return false;
+ }
+
+ public override int Capacity
+ {
+ get
+ {
+ return (int)_unmanagedStream.Capacity;
+ }
+ set
+ {
+ throw new IOException(SR.IO_FixedCapacity);
+ }
+ }
+
+ public override long Length
+ {
+ get
+ {
+ return _unmanagedStream.Length;
+ }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ return _unmanagedStream.Position;
+ }
+ set
+ {
+ _unmanagedStream.Position = value;
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return _unmanagedStream.Read(buffer, offset, count);
+ }
+
+ public override int Read(Span<byte> buffer)
+ {
+ return _unmanagedStream.Read(buffer);
+ }
+
+ public override int ReadByte()
+ {
+ return _unmanagedStream.ReadByte();
+ }
+
+ public override long Seek(long offset, SeekOrigin loc)
+ {
+ return _unmanagedStream.Seek(offset, loc);
+ }
+
+ public unsafe override byte[] ToArray()
+ {
+ byte[] buffer = new byte[_unmanagedStream.Length];
+ _unmanagedStream.Read(buffer, 0, (int)_unmanagedStream.Length);
+ return buffer;
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ _unmanagedStream.Write(buffer, offset, count);
+ }
+
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ _unmanagedStream.Write(buffer);
+ }
+
+ public override void WriteByte(byte value)
+ {
+ _unmanagedStream.WriteByte(value);
+ }
+
+ // Writes this MemoryStream to another stream.
+ public unsafe override void WriteTo(Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream), SR.ArgumentNull_Stream);
+
+ byte[] buffer = ToArray();
+
+ stream.Write(buffer, 0, buffer.Length);
+ }
+
+ public override void SetLength(Int64 value)
+ {
+ // This was probably meant to call _unmanagedStream.SetLength(value), but it was forgotten in V.4.0.
+ // Now this results in a call to the base which touches the underlying array which is never actually used.
+ // We cannot fix it due to compat now, but we should fix this at the next SxS release oportunity.
+ base.SetLength(value);
+ }
+
+
+ public override Task CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
+ {
+ // The parameter checks must be in sync with the base version:
+ if (destination == null)
+ throw new ArgumentNullException(nameof(destination));
+
+ if (bufferSize <= 0)
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+
+ if (!CanRead && !CanWrite)
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed);
+
+ if (!destination.CanRead && !destination.CanWrite)
+ throw new ObjectDisposedException(nameof(destination), SR.ObjectDisposed_StreamClosed);
+
+ if (!CanRead)
+ throw new NotSupportedException(SR.NotSupported_UnreadableStream);
+
+ if (!destination.CanWrite)
+ throw new NotSupportedException(SR.NotSupported_UnwritableStream);
+
+
+ return _unmanagedStream.CopyToAsync(destination, bufferSize, cancellationToken);
+ }
+
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ return _unmanagedStream.FlushAsync(cancellationToken);
+ }
+
+
+ public override Task<Int32> ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
+ {
+ return _unmanagedStream.ReadAsync(buffer, offset, count, cancellationToken);
+ }
+
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return _unmanagedStream.ReadAsync(buffer, cancellationToken);
+ }
+
+
+ public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
+ {
+ return _unmanagedStream.WriteAsync(buffer, offset, count, cancellationToken);
+ }
+
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return _unmanagedStream.WriteAsync(buffer, cancellationToken);
+ }
+ } // class UnmanagedMemoryStreamWrapper
+} // namespace
+
diff --git a/src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs b/src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs
new file mode 100644
index 0000000000..7888d8d61b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs
@@ -0,0 +1,95 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace System.IO
+{
+ /// <summary>
+ /// Provides static methods for converting from Win32 errors codes to exceptions, HRESULTS and error messages.
+ /// </summary>
+ internal static class Win32Marshal
+ {
+ /// <summary>
+ /// Converts, resetting it, the last Win32 error into a corresponding <see cref="Exception"/> object, optionally
+ /// including the specified path in the error message.
+ /// </summary>
+ internal static Exception GetExceptionForLastWin32Error(string path = "")
+ => GetExceptionForWin32Error(Marshal.GetLastWin32Error(), path);
+
+ /// <summary>
+ /// Converts the specified Win32 error into a corresponding <see cref="Exception"/> object, optionally
+ /// including the specified path in the error message.
+ /// </summary>
+ internal static Exception GetExceptionForWin32Error(int errorCode, string path = "")
+ {
+ switch (errorCode)
+ {
+ case Interop.Errors.ERROR_FILE_NOT_FOUND:
+ return new FileNotFoundException(
+ string.IsNullOrEmpty(path) ? SR.IO_FileNotFound : SR.Format(SR.IO_FileNotFound_FileName, path), path);
+ case Interop.Errors.ERROR_PATH_NOT_FOUND:
+ return new DirectoryNotFoundException(
+ string.IsNullOrEmpty(path) ? SR.IO_PathNotFound_NoPathName : SR.Format(SR.IO_PathNotFound_Path, path));
+ case Interop.Errors.ERROR_ACCESS_DENIED:
+ return new UnauthorizedAccessException(
+ string.IsNullOrEmpty(path) ? SR.UnauthorizedAccess_IODenied_NoPathName : SR.Format(SR.UnauthorizedAccess_IODenied_Path, path));
+ case Interop.Errors.ERROR_ALREADY_EXISTS:
+ if (string.IsNullOrEmpty(path))
+ goto default;
+ return new IOException(SR.Format(SR.IO_AlreadyExists_Name, path), MakeHRFromErrorCode(errorCode));
+ case Interop.Errors.ERROR_FILENAME_EXCED_RANGE:
+ return new PathTooLongException(
+ string.IsNullOrEmpty(path) ? SR.IO_PathTooLong : SR.Format(SR.IO_PathTooLong_Path, path));
+ case Interop.Errors.ERROR_SHARING_VIOLATION:
+ return new IOException(
+ string.IsNullOrEmpty(path) ? SR.IO_SharingViolation_NoFileName : SR.Format(SR.IO_SharingViolation_File, path),
+ MakeHRFromErrorCode(errorCode));
+ case Interop.Errors.ERROR_FILE_EXISTS:
+ if (string.IsNullOrEmpty(path))
+ goto default;
+ return new IOException(SR.Format(SR.IO_FileExists_Name, path), MakeHRFromErrorCode(errorCode));
+ case Interop.Errors.ERROR_OPERATION_ABORTED:
+ return new OperationCanceledException();
+ case Interop.Errors.ERROR_INVALID_PARAMETER:
+ default:
+ return new IOException(
+ string.IsNullOrEmpty(path) ? GetMessage(errorCode) : $"{GetMessage(errorCode)} : '{path}'",
+ MakeHRFromErrorCode(errorCode));
+ }
+ }
+
+ /// <summary>
+ /// If not already an HRESULT, returns an HRESULT for the specified Win32 error code.
+ /// </summary>
+ internal static int MakeHRFromErrorCode(int errorCode)
+ {
+ // Don't convert it if it is already an HRESULT
+ if ((0xFFFF0000 & errorCode) != 0)
+ return errorCode;
+
+ return unchecked(((int)0x80070000) | errorCode);
+ }
+
+ /// <summary>
+ /// Returns a Win32 error code for the specified HRESULT if it came from FACILITY_WIN32
+ /// If not, returns the HRESULT unchanged
+ /// </summary>
+ internal static int TryMakeWin32ErrorCodeFromHR(int hr)
+ {
+ if ((0xFFFF0000 & hr) == 0x80070000)
+ {
+ // Win32 error, Win32Marshal.GetExceptionForWin32Error expects the Win32 format
+ hr &= 0x0000FFFF;
+ }
+
+ return hr;
+ }
+
+ /// <summary>
+ /// Returns a string message for the specified Win32 error code.
+ /// </summary>
+ internal static string GetMessage(int errorCode) => Interop.Kernel32.GetMessage(errorCode);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IObservable.cs b/src/System.Private.CoreLib/shared/System/IObservable.cs
new file mode 100644
index 0000000000..aabb0b8fb4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IObservable.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public interface IObservable<out T>
+ {
+ IDisposable Subscribe(IObserver<T> observer);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IObserver.cs b/src/System.Private.CoreLib/shared/System/IObserver.cs
new file mode 100644
index 0000000000..39e123de8d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IObserver.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public interface IObserver<in T>
+ {
+ void OnNext(T value);
+ void OnError(Exception error);
+ void OnCompleted();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IProgress.cs b/src/System.Private.CoreLib/shared/System/IProgress.cs
new file mode 100644
index 0000000000..724c7bdce9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IProgress.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ /// <summary>Defines a provider for progress updates.</summary>
+ /// <typeparam name="T">The type of progress update value.</typeparam>
+ public interface IProgress<in T>
+ {
+ /// <summary>Reports a progress update.</summary>
+ /// <param name="value">The value of the updated progress.</param>
+ void Report(T value);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ISpanFormattable.cs b/src/System.Private.CoreLib/shared/System/ISpanFormattable.cs
new file mode 100644
index 0000000000..df46b5bcd1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ISpanFormattable.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ internal interface ISpanFormattable
+ {
+ bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IndexOutOfRangeException.cs b/src/System.Private.CoreLib/shared/System/IndexOutOfRangeException.cs
new file mode 100644
index 0000000000..b6d93ef568
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IndexOutOfRangeException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for invalid array indices.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class IndexOutOfRangeException : SystemException
+ {
+ public IndexOutOfRangeException()
+ : base(SR.Arg_IndexOutOfRangeException)
+ {
+ HResult = HResults.COR_E_INDEXOUTOFRANGE;
+ }
+
+ public IndexOutOfRangeException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_INDEXOUTOFRANGE;
+ }
+
+ public IndexOutOfRangeException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_INDEXOUTOFRANGE;
+ }
+
+ internal IndexOutOfRangeException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/InsufficientExecutionStackException.cs b/src/System.Private.CoreLib/shared/System/InsufficientExecutionStackException.cs
new file mode 100644
index 0000000000..4822028f85
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/InsufficientExecutionStackException.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class InsufficientExecutionStackException : SystemException
+ {
+ public InsufficientExecutionStackException()
+ : base(SR.Arg_InsufficientExecutionStackException)
+ {
+ HResult = HResults.COR_E_INSUFFICIENTEXECUTIONSTACK;
+ }
+
+ public InsufficientExecutionStackException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_INSUFFICIENTEXECUTIONSTACK;
+ }
+
+ public InsufficientExecutionStackException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_INSUFFICIENTEXECUTIONSTACK;
+ }
+
+ internal InsufficientExecutionStackException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Int16.cs b/src/System.Private.CoreLib/shared/System/Int16.cs
new file mode 100644
index 0000000000..fecc87e9fe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Int16.cs
@@ -0,0 +1,314 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Int16 : IComparable, IConvertible, IFormattable, IComparable<Int16>, IEquatable<Int16>, ISpanFormattable
+ {
+ private short m_value; // Do not rename (binary serialization)
+
+ public const short MaxValue = (short)0x7FFF;
+ public const short MinValue = unchecked((short)0x8000);
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type Int16, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+
+ if (value is Int16)
+ {
+ return m_value - ((Int16)value).m_value;
+ }
+
+ throw new ArgumentException(SR.Arg_MustBeInt16);
+ }
+
+ public int CompareTo(Int16 value)
+ {
+ return m_value - value;
+ }
+
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is Int16))
+ {
+ return false;
+ }
+ return m_value == ((Int16)obj).m_value;
+ }
+
+ [NonVersionable]
+ public bool Equals(Int16 obj)
+ {
+ return m_value == obj;
+ }
+
+ // Returns a HashCode for the Int16
+ public override int GetHashCode()
+ {
+ return ((int)((ushort)m_value) | (((int)m_value) << 16));
+ }
+
+
+ public override String ToString()
+ {
+ return Number.FormatInt32(m_value, null, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatInt32(m_value, null, provider);
+ }
+
+ public String ToString(String format)
+ {
+ return ToString(format, null);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ if (m_value < 0 && format != null && format.Length > 0 && (format[0] == 'X' || format[0] == 'x'))
+ {
+ uint temp = (uint)(m_value & 0x0000FFFF);
+ return Number.FormatUInt32(temp, format, provider);
+ }
+
+ return Number.FormatInt32(m_value, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ if (m_value < 0 && format.Length > 0 && (format[0] == 'X' || format[0] == 'x'))
+ {
+ uint temp = (uint)(m_value & 0x0000FFFF);
+ return Number.TryFormatUInt32(temp, format, provider, destination, out charsWritten);
+ }
+ return Number.TryFormatInt32(m_value, format, provider, destination, out charsWritten);
+ }
+
+ public static short Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static short Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static short Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static short Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static short Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Parse(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ private static short Parse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info)
+ {
+ int i = 0;
+ try
+ {
+ i = Number.ParseInt32(s, style, info);
+ }
+ catch (OverflowException e)
+ {
+ throw new OverflowException(SR.Overflow_Int16, e);
+ }
+
+ // We need this check here since we don't allow signs to specified in hex numbers. So we fixup the result
+ // for negative numbers
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ { // We are parsing a hexadecimal number
+ if ((i < 0) || (i > UInt16.MaxValue))
+ {
+ throw new OverflowException(SR.Overflow_Int16);
+ }
+ return (short)i;
+ }
+
+ if (i < MinValue || i > MaxValue) throw new OverflowException(SR.Overflow_Int16);
+ return (short)i;
+ }
+
+ public static bool TryParse(String s, out Int16 result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, out short result)
+ {
+ return TryParse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(String s, NumberStyles style, IFormatProvider provider, out Int16 result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out short result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return TryParse(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ private static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out Int16 result)
+ {
+ result = 0;
+ int i;
+ if (!Number.TryParseInt32(s, style, info, out i))
+ {
+ return false;
+ }
+
+ // We need this check here since we don't allow signs to specified in hex numbers. So we fixup the result
+ // for negative numbers
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ { // We are parsing a hexadecimal number
+ if ((i < 0) || i > UInt16.MaxValue)
+ {
+ return false;
+ }
+ result = (Int16)i;
+ return true;
+ }
+
+ if (i < MinValue || i > MaxValue)
+ {
+ return false;
+ }
+ result = (Int16)i;
+ return true;
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Int16;
+ }
+
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(m_value);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Int16", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Int32.cs b/src/System.Private.CoreLib/shared/System/Int32.cs
new file mode 100644
index 0000000000..b573e950e4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Int32.cs
@@ -0,0 +1,269 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Int32 : IComparable, IConvertible, IFormattable, IComparable<Int32>, IEquatable<Int32>, ISpanFormattable
+ {
+ private int m_value; // Do not rename (binary serialization)
+
+ public const int MaxValue = 0x7fffffff;
+ public const int MinValue = unchecked((int)0x80000000);
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns :
+ // 0 if the values are equal
+ // Negative number if _value is less than value
+ // Positive number if _value is more than value
+ // null is considered to be less than any instance, hence returns positive number
+ // If object is not of type Int32, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (value is Int32)
+ {
+ // NOTE: Cannot use return (_value - value) as this causes a wrap
+ // around in cases where _value - value > MaxValue.
+ int i = (int)value;
+ if (m_value < i) return -1;
+ if (m_value > i) return 1;
+ return 0;
+ }
+ throw new ArgumentException(SR.Arg_MustBeInt32);
+ }
+
+ public int CompareTo(int value)
+ {
+ // NOTE: Cannot use return (_value - value) as this causes a wrap
+ // around in cases where _value - value > MaxValue.
+ if (m_value < value) return -1;
+ if (m_value > value) return 1;
+ return 0;
+ }
+
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is Int32))
+ {
+ return false;
+ }
+ return m_value == ((Int32)obj).m_value;
+ }
+
+ [NonVersionable]
+ public bool Equals(Int32 obj)
+ {
+ return m_value == obj;
+ }
+
+ // The absolute value of the int contained.
+ public override int GetHashCode()
+ {
+ return m_value;
+ }
+
+ public override String ToString()
+ {
+ return Number.FormatInt32(m_value, null, null);
+ }
+
+ public String ToString(String format)
+ {
+ return Number.FormatInt32(m_value, format, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatInt32(m_value, null, provider);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return Number.FormatInt32(m_value, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ return Number.TryFormatInt32(m_value, format, provider, destination, out charsWritten);
+ }
+
+ public static int Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static int Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseInt32(s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ // Parses an integer from a String in the given style. If
+ // a NumberFormatInfo isn't specified, the current culture's
+ // NumberFormatInfo is assumed.
+ //
+ public static int Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseInt32(s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
+ }
+
+ // Parses an integer from a String in the given style. If
+ // a NumberFormatInfo isn't specified, the current culture's
+ // NumberFormatInfo is assumed.
+ //
+ public static int Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseInt32(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static int Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Number.ParseInt32(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ // Parses an integer from a String. Returns false rather
+ // than throwing exceptin if input is invalid
+ //
+ public static bool TryParse(String s, out Int32 result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, out int result)
+ {
+ return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ // Parses an integer from a String in the given style. Returns false rather
+ // than throwing exceptin if input is invalid
+ //
+ public static bool TryParse(String s, NumberStyles style, IFormatProvider provider, out Int32 result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out int result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Int32;
+ }
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(m_value);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Int32", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Int64.cs b/src/System.Private.CoreLib/shared/System/Int64.cs
new file mode 100644
index 0000000000..0bcca87309
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Int64.cs
@@ -0,0 +1,257 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Int64 : IComparable, IConvertible, IFormattable, IComparable<Int64>, IEquatable<Int64>, ISpanFormattable
+ {
+ private long m_value; // Do not rename (binary serialization)
+
+ public const long MaxValue = 0x7fffffffffffffffL;
+ public const long MinValue = unchecked((long)0x8000000000000000L);
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type Int64, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (value is Int64)
+ {
+ // Need to use compare because subtraction will wrap
+ // to positive for very large neg numbers, etc.
+ long i = (long)value;
+ if (m_value < i) return -1;
+ if (m_value > i) return 1;
+ return 0;
+ }
+ throw new ArgumentException(SR.Arg_MustBeInt64);
+ }
+
+ public int CompareTo(Int64 value)
+ {
+ // Need to use compare because subtraction will wrap
+ // to positive for very large neg numbers, etc.
+ if (m_value < value) return -1;
+ if (m_value > value) return 1;
+ return 0;
+ }
+
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is Int64))
+ {
+ return false;
+ }
+ return m_value == ((Int64)obj).m_value;
+ }
+
+ [NonVersionable]
+ public bool Equals(Int64 obj)
+ {
+ return m_value == obj;
+ }
+
+ // The value of the lower 32 bits XORed with the uppper 32 bits.
+ public override int GetHashCode()
+ {
+ return (unchecked((int)((long)m_value)) ^ (int)(m_value >> 32));
+ }
+
+ public override String ToString()
+ {
+ return Number.FormatInt64(m_value, null, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatInt64(m_value, null, provider);
+ }
+
+ public String ToString(String format)
+ {
+ return Number.FormatInt64(m_value, format, null);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return Number.FormatInt64(m_value, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ return Number.TryFormatInt64(m_value, format, provider, destination, out charsWritten);
+ }
+
+ public static long Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static long Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseInt64(s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static long Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseInt64(s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
+ }
+
+
+ // Parses a long from a String in the given style. If
+ // a NumberFormatInfo isn't specified, the current culture's
+ // NumberFormatInfo is assumed.
+ //
+ public static long Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseInt64(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static long Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Number.ParseInt64(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static Boolean TryParse(String s, out Int64 result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return Number.TryParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, out long result)
+ {
+ return Number.TryParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static Boolean TryParse(String s, NumberStyles style, IFormatProvider provider, out Int64 result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out long result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Int64;
+ }
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(m_value);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Int64", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/IntPtr.cs b/src/System.Private.CoreLib/shared/System/IntPtr.cs
new file mode 100644
index 0000000000..44638ddd8e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/IntPtr.cs
@@ -0,0 +1,235 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Runtime.Versioning;
+
+#if BIT64
+using nint = System.Int64;
+#else
+using nint = System.Int32;
+#endif
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct IntPtr : IEquatable<IntPtr>, ISerializable
+ {
+ // WARNING: We allow diagnostic tools to directly inspect this member (_value).
+ // See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details.
+ // Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools.
+ // Get in touch with the diagnostics team if you have questions.
+ private unsafe void* _value; // Do not rename (binary serialization)
+
+ [Intrinsic]
+ public static readonly IntPtr Zero;
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe IntPtr(int value)
+ {
+ _value = (void*)value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe IntPtr(long value)
+ {
+#if BIT64
+ _value = (void*)value;
+#else
+ _value = (void*)checked((int)value);
+#endif
+ }
+
+ [CLSCompliant(false)]
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe IntPtr(void* value)
+ {
+ _value = value;
+ }
+
+ private unsafe IntPtr(SerializationInfo info, StreamingContext context)
+ {
+ long l = info.GetInt64("value");
+
+ if (Size == 4 && (l > int.MaxValue || l < int.MinValue))
+ throw new ArgumentException(SR.Serialization_InvalidPtrValue);
+
+ _value = (void*)l;
+ }
+
+ unsafe void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ throw new ArgumentNullException(nameof(info));
+
+ info.AddValue("value", ToInt64());
+ }
+
+ public unsafe override bool Equals(Object obj)
+ {
+ if (obj is IntPtr)
+ {
+ return (_value == ((IntPtr)obj)._value);
+ }
+ return false;
+ }
+
+ unsafe bool IEquatable<IntPtr>.Equals(IntPtr other)
+ {
+ return _value == other._value;
+ }
+
+ public unsafe override int GetHashCode()
+ {
+#if BIT64
+ long l = (long)_value;
+ return (unchecked((int)l) ^ (int)(l >> 32));
+#else
+ return unchecked((int)_value);
+#endif
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe int ToInt32()
+ {
+#if BIT64
+ long l = (long)_value;
+ return checked((int)l);
+#else
+ return (int)_value;
+#endif
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe long ToInt64()
+ {
+ return (nint)_value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator IntPtr(int value)
+ {
+ return new IntPtr(value);
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator IntPtr(long value)
+ {
+ return new IntPtr(value);
+ }
+
+ [CLSCompliant(false)]
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator IntPtr(void* value)
+ {
+ return new IntPtr(value);
+ }
+
+ [CLSCompliant(false)]
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator void* (IntPtr value)
+ {
+ return value._value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator int(IntPtr value)
+ {
+#if BIT64
+ long l = (long)value._value;
+ return checked((int)l);
+#else
+ return (int)value._value;
+#endif
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator long(IntPtr value)
+ {
+ return (nint)value._value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe bool operator ==(IntPtr value1, IntPtr value2)
+ {
+ return value1._value == value2._value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe bool operator !=(IntPtr value1, IntPtr value2)
+ {
+ return value1._value != value2._value;
+ }
+
+ [NonVersionable]
+ public static IntPtr Add(IntPtr pointer, int offset)
+ {
+ return pointer + offset;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe IntPtr operator +(IntPtr pointer, int offset)
+ {
+ return new IntPtr((nint)pointer._value + offset);
+ }
+
+ [NonVersionable]
+ public static IntPtr Subtract(IntPtr pointer, int offset)
+ {
+ return pointer - offset;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe IntPtr operator -(IntPtr pointer, int offset)
+ {
+ return new IntPtr((nint)pointer._value - offset);
+ }
+
+ public static int Size
+ {
+ [Intrinsic]
+ [NonVersionable]
+ get
+ {
+ return sizeof(nint);
+ }
+ }
+
+ [CLSCompliant(false)]
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe void* ToPointer()
+ {
+ return _value;
+ }
+
+ public unsafe override string ToString()
+ {
+ return ((nint)_value).ToString(CultureInfo.InvariantCulture);
+ }
+
+ public unsafe string ToString(string format)
+ {
+ return ((nint)_value).ToString(format, CultureInfo.InvariantCulture);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/InvalidCastException.cs b/src/System.Private.CoreLib/shared/System/InvalidCastException.cs
new file mode 100644
index 0000000000..055643278a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/InvalidCastException.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+** Purpose: Exception class for invalid cast conditions!
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class InvalidCastException : SystemException
+ {
+ public InvalidCastException()
+ : base(SR.Arg_InvalidCastException)
+ {
+ HResult = HResults.COR_E_INVALIDCAST;
+ }
+
+ public InvalidCastException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_INVALIDCAST;
+ }
+
+ public InvalidCastException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_INVALIDCAST;
+ }
+
+ public InvalidCastException(String message, int errorCode)
+ : base(message)
+ {
+ HResult = errorCode;
+ }
+
+ protected InvalidCastException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/InvalidOperationException.cs b/src/System.Private.CoreLib/shared/System/InvalidOperationException.cs
new file mode 100644
index 0000000000..62c222af40
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/InvalidOperationException.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for denoting an object was in a state that
+** made calling a method illegal.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class InvalidOperationException : SystemException
+ {
+ public InvalidOperationException()
+ : base(SR.Arg_InvalidOperationException)
+ {
+ HResult = HResults.COR_E_INVALIDOPERATION;
+ }
+
+ public InvalidOperationException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_INVALIDOPERATION;
+ }
+
+ public InvalidOperationException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_INVALIDOPERATION;
+ }
+
+ protected InvalidOperationException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/InvalidProgramException.cs b/src/System.Private.CoreLib/shared/System/InvalidProgramException.cs
new file mode 100644
index 0000000000..c8047c548b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/InvalidProgramException.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: The exception class for programs with invalid IL or bad metadata.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class InvalidProgramException : SystemException
+ {
+ public InvalidProgramException()
+ : base(SR.InvalidProgram_Default)
+ {
+ HResult = HResults.COR_E_INVALIDPROGRAM;
+ }
+
+ public InvalidProgramException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_INVALIDPROGRAM;
+ }
+
+ public InvalidProgramException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_INVALIDPROGRAM;
+ }
+
+ internal InvalidProgramException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/InvalidTimeZoneException.cs b/src/System.Private.CoreLib/shared/System/InvalidTimeZoneException.cs
new file mode 100644
index 0000000000..25b155e8d1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/InvalidTimeZoneException.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class InvalidTimeZoneException : Exception
+ {
+ public InvalidTimeZoneException()
+ {
+ }
+
+ public InvalidTimeZoneException(String message)
+ : base(message)
+ {
+ }
+
+ public InvalidTimeZoneException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ protected InvalidTimeZoneException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Lazy.cs b/src/System.Private.CoreLib/shared/System/Lazy.cs
new file mode 100644
index 0000000000..6410c2e285
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Lazy.cs
@@ -0,0 +1,548 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// --------------------------------------------------------------------------------------
+//
+// A class that provides a simple, lightweight implementation of lazy initialization,
+// obviating the need for a developer to implement a custom, thread-safe lazy initialization
+// solution.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+#pragma warning disable 0420
+
+using System.Diagnostics;
+using System.Runtime.ExceptionServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System
+{
+ internal enum LazyState
+ {
+ NoneViaConstructor = 0,
+ NoneViaFactory = 1,
+ NoneException = 2,
+
+ PublicationOnlyViaConstructor = 3,
+ PublicationOnlyViaFactory = 4,
+ PublicationOnlyWait = 5,
+ PublicationOnlyException = 6,
+
+ ExecutionAndPublicationViaConstructor = 7,
+ ExecutionAndPublicationViaFactory = 8,
+ ExecutionAndPublicationException = 9,
+ }
+
+ /// <summary>
+ /// LazyHelper serves multiples purposes
+ /// - minimizing code size of Lazy&lt;T&gt; by implementing as much of the code that is not generic
+ /// this reduces generic code bloat, making faster class initialization
+ /// - contains singleton objects that are used to handle threading primitives for PublicationOnly mode
+ /// - allows for instantiation for ExecutionAndPublication so as to create an object for locking on
+ /// - holds exception information.
+ /// </summary>
+ internal class LazyHelper
+ {
+ internal readonly static LazyHelper NoneViaConstructor = new LazyHelper(LazyState.NoneViaConstructor);
+ internal readonly static LazyHelper NoneViaFactory = new LazyHelper(LazyState.NoneViaFactory);
+ internal readonly static LazyHelper PublicationOnlyViaConstructor = new LazyHelper(LazyState.PublicationOnlyViaConstructor);
+ internal readonly static LazyHelper PublicationOnlyViaFactory = new LazyHelper(LazyState.PublicationOnlyViaFactory);
+ internal readonly static LazyHelper PublicationOnlyWaitForOtherThreadToPublish = new LazyHelper(LazyState.PublicationOnlyWait);
+
+ internal LazyState State { get; }
+
+ private readonly ExceptionDispatchInfo _exceptionDispatch;
+
+ /// <summary>
+ /// Constructor that defines the state
+ /// </summary>
+ internal LazyHelper(LazyState state)
+ {
+ State = state;
+ }
+
+ /// <summary>
+ /// Constructor used for exceptions
+ /// </summary>
+ internal LazyHelper(LazyThreadSafetyMode mode, Exception exception)
+ {
+ switch(mode)
+ {
+ case LazyThreadSafetyMode.ExecutionAndPublication:
+ State = LazyState.ExecutionAndPublicationException;
+ break;
+
+ case LazyThreadSafetyMode.None:
+ State = LazyState.NoneException;
+ break;
+
+ case LazyThreadSafetyMode.PublicationOnly:
+ State = LazyState.PublicationOnlyException;
+ break;
+
+ default:
+ Debug.Fail("internal constructor, this should never occur");
+ break;
+ }
+
+ _exceptionDispatch = ExceptionDispatchInfo.Capture(exception);
+ }
+
+ internal void ThrowException()
+ {
+ Debug.Assert(_exceptionDispatch != null, "execution path is invalid");
+
+ _exceptionDispatch.Throw();
+ }
+
+ private LazyThreadSafetyMode GetMode()
+ {
+ switch (State)
+ {
+ case LazyState.NoneViaConstructor:
+ case LazyState.NoneViaFactory:
+ case LazyState.NoneException:
+ return LazyThreadSafetyMode.None;
+
+ case LazyState.PublicationOnlyViaConstructor:
+ case LazyState.PublicationOnlyViaFactory:
+ case LazyState.PublicationOnlyWait:
+ case LazyState.PublicationOnlyException:
+ return LazyThreadSafetyMode.PublicationOnly;
+
+ case LazyState.ExecutionAndPublicationViaConstructor:
+ case LazyState.ExecutionAndPublicationViaFactory:
+ case LazyState.ExecutionAndPublicationException:
+ return LazyThreadSafetyMode.ExecutionAndPublication;
+
+ default:
+ Debug.Fail("Invalid logic; State should always have a valid value");
+ return default(LazyThreadSafetyMode);
+ }
+ }
+
+ internal static LazyThreadSafetyMode? GetMode(LazyHelper state)
+ {
+ if (state == null)
+ return null; // we don't know the mode anymore
+ return state.GetMode();
+ }
+
+ internal static bool GetIsValueFaulted(LazyHelper state) => state?._exceptionDispatch != null;
+
+ internal static LazyHelper Create(LazyThreadSafetyMode mode, bool useDefaultConstructor)
+ {
+ switch (mode)
+ {
+ case LazyThreadSafetyMode.None:
+ return useDefaultConstructor ? NoneViaConstructor : NoneViaFactory;
+
+ case LazyThreadSafetyMode.PublicationOnly:
+ return useDefaultConstructor ? PublicationOnlyViaConstructor : PublicationOnlyViaFactory;
+
+ case LazyThreadSafetyMode.ExecutionAndPublication:
+ // we need to create an object for ExecutionAndPublication because we use Monitor-based locking
+ var state = useDefaultConstructor ? LazyState.ExecutionAndPublicationViaConstructor : LazyState.ExecutionAndPublicationViaFactory;
+ return new LazyHelper(state);
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(mode), SR.Lazy_ctor_ModeInvalid);
+ }
+ }
+
+ internal static object CreateViaDefaultConstructor(Type type)
+ {
+ try
+ {
+ return Activator.CreateInstance(type);
+ }
+ catch (MissingMethodException)
+ {
+ throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT);
+ }
+ }
+
+ internal static LazyThreadSafetyMode GetModeFromIsThreadSafe(bool isThreadSafe)
+ {
+ return isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None;
+ }
+ }
+
+ /// <summary>
+ /// Provides support for lazy initialization.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of element being lazily initialized.</typeparam>
+ /// <remarks>
+ /// <para>
+ /// By default, all public and protected members of <see cref="Lazy{T}"/> are thread-safe and may be used
+ /// concurrently from multiple threads. These thread-safety guarantees may be removed optionally and per instance
+ /// using parameters to the type's constructors.
+ /// </para>
+ /// </remarks>
+ [DebuggerTypeProxy(typeof(LazyDebugView<>))]
+ [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
+ public class Lazy<T>
+ {
+ private static T CreateViaDefaultConstructor()
+ {
+ return (T)LazyHelper.CreateViaDefaultConstructor(typeof(T));
+ }
+
+ // _state, a volatile reference, is set to null after _value has been set
+ private volatile LazyHelper _state;
+
+ // we ensure that _factory when finished is set to null to allow garbage collector to clean up
+ // any referenced items
+ private Func<T> _factory;
+
+ // _value eventually stores the lazily created value. It is valid when _state = null.
+ private T _value;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that
+ /// uses <typeparamref name="T"/>'s default constructor for lazy initialization.
+ /// </summary>
+ /// <remarks>
+ /// An instance created with this constructor may be used concurrently from multiple threads.
+ /// </remarks>
+ public Lazy()
+ : this(null, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:true)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that
+ /// uses a pre-initialized specified value.
+ /// </summary>
+ /// <remarks>
+ /// An instance created with this constructor should be usable by multiple threads
+ // concurrently.
+ /// </remarks>
+ public Lazy(T value)
+ {
+ _value = value;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that uses a
+ /// specified initialization function.
+ /// </summary>
+ /// <param name="valueFactory">
+ /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is
+ /// needed.
+ /// </param>
+ /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is a null
+ /// reference (Nothing in Visual Basic).</exception>
+ /// <remarks>
+ /// An instance created with this constructor may be used concurrently from multiple threads.
+ /// </remarks>
+ public Lazy(Func<T> valueFactory)
+ : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:false)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/>
+ /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
+ /// </summary>
+ /// <param name="isThreadSafe">true if this instance should be usable by multiple threads concurrently; false if the instance will only be used by one thread at a time.
+ /// </param>
+ public Lazy(bool isThreadSafe) :
+ this(null, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:true)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/>
+ /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
+ /// </summary>
+ /// <param name="mode">The lazy thread-safety mode</param>
+ /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid valuee</exception>
+ public Lazy(LazyThreadSafetyMode mode) :
+ this(null, mode, useDefaultConstructor:true)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class
+ /// that uses a specified initialization function and a specified thread-safety mode.
+ /// </summary>
+ /// <param name="valueFactory">
+ /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
+ /// </param>
+ /// <param name="isThreadSafe">true if this instance should be usable by multiple threads concurrently; false if the instance will only be used by one thread at a time.
+ /// </param>
+ /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
+ /// a null reference (Nothing in Visual Basic).</exception>
+ public Lazy(Func<T> valueFactory, bool isThreadSafe) :
+ this(valueFactory, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:false)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class
+ /// that uses a specified initialization function and a specified thread-safety mode.
+ /// </summary>
+ /// <param name="valueFactory">
+ /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
+ /// </param>
+ /// <param name="mode">The lazy thread-safety mode.</param>
+ /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
+ /// a null reference (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid value.</exception>
+ public Lazy(Func<T> valueFactory, LazyThreadSafetyMode mode)
+ : this(valueFactory, mode, useDefaultConstructor:false)
+ {
+ }
+
+ private Lazy(Func<T> valueFactory, LazyThreadSafetyMode mode, bool useDefaultConstructor)
+ {
+ if (valueFactory == null && !useDefaultConstructor)
+ throw new ArgumentNullException(nameof(valueFactory));
+
+ _factory = valueFactory;
+ _state = LazyHelper.Create(mode, useDefaultConstructor);
+ }
+
+ private void ViaConstructor()
+ {
+ _value = CreateViaDefaultConstructor();
+ _state = null; // volatile write, must occur after setting _value
+ }
+
+ private void ViaFactory(LazyThreadSafetyMode mode)
+ {
+ try
+ {
+ Func<T> factory = _factory;
+ if (factory == null)
+ throw new InvalidOperationException(SR.Lazy_Value_RecursiveCallsToValue);
+ _factory = null;
+
+ _value = factory();
+ _state = null; // volatile write, must occur after setting _value
+ }
+ catch (Exception exception)
+ {
+ _state = new LazyHelper(mode, exception);
+ throw;
+ }
+ }
+
+ private void ExecutionAndPublication(LazyHelper executionAndPublication, bool useDefaultConstructor)
+ {
+ lock (executionAndPublication)
+ {
+ // it's possible for multiple calls to have piled up behind the lock, so we need to check
+ // to see if the ExecutionAndPublication object is still the current implementation.
+ if (ReferenceEquals(_state, executionAndPublication))
+ {
+ if (useDefaultConstructor)
+ {
+ ViaConstructor();
+ }
+ else
+ {
+ ViaFactory(LazyThreadSafetyMode.ExecutionAndPublication);
+ }
+ }
+ }
+ }
+
+ private void PublicationOnly(LazyHelper publicationOnly, T possibleValue)
+ {
+ LazyHelper previous = Interlocked.CompareExchange(ref _state, LazyHelper.PublicationOnlyWaitForOtherThreadToPublish, publicationOnly);
+ if (previous == publicationOnly)
+ {
+ _factory = null;
+ _value = possibleValue;
+ _state = null; // volatile write, must occur after setting _value
+ }
+ }
+
+ private void PublicationOnlyViaConstructor(LazyHelper initializer)
+ {
+ PublicationOnly(initializer, CreateViaDefaultConstructor());
+ }
+
+ private void PublicationOnlyViaFactory(LazyHelper initializer)
+ {
+ Func<T> factory = _factory;
+ if (factory == null)
+ {
+ PublicationOnlyWaitForOtherThreadToPublish();
+ }
+ else
+ {
+ PublicationOnly(initializer, factory());
+ }
+ }
+
+ private void PublicationOnlyWaitForOtherThreadToPublish()
+ {
+ var spinWait = new SpinWait();
+ while (!ReferenceEquals(_state, null))
+ {
+ // We get here when PublicationOnly temporarily sets _state to LazyHelper.PublicationOnlyWaitForOtherThreadToPublish.
+ // This temporary state should be quickly followed by _state being set to null.
+ spinWait.SpinOnce();
+ }
+ }
+
+ private T CreateValue()
+ {
+ // we have to create a copy of state here, and use the copy exclusively from here on in
+ // so as to ensure thread safety.
+ var state = _state;
+ if (state != null)
+ {
+ switch (state.State)
+ {
+ case LazyState.NoneViaConstructor:
+ ViaConstructor();
+ break;
+
+ case LazyState.NoneViaFactory:
+ ViaFactory(LazyThreadSafetyMode.None);
+ break;
+
+ case LazyState.PublicationOnlyViaConstructor:
+ PublicationOnlyViaConstructor(state);
+ break;
+
+ case LazyState.PublicationOnlyViaFactory:
+ PublicationOnlyViaFactory(state);
+ break;
+
+ case LazyState.PublicationOnlyWait:
+ PublicationOnlyWaitForOtherThreadToPublish();
+ break;
+
+ case LazyState.ExecutionAndPublicationViaConstructor:
+ ExecutionAndPublication(state, useDefaultConstructor:true);
+ break;
+
+ case LazyState.ExecutionAndPublicationViaFactory:
+ ExecutionAndPublication(state, useDefaultConstructor:false);
+ break;
+
+ default:
+ state.ThrowException();
+ break;
+ }
+ }
+ return Value;
+ }
+
+ /// <summary>Creates and returns a string representation of this instance.</summary>
+ /// <returns>The result of calling <see cref="System.Object.ToString"/> on the <see
+ /// cref="Value"/>.</returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <see cref="Value"/> is null.
+ /// </exception>
+ public override string ToString()
+ {
+ return IsValueCreated ? Value.ToString() : SR.Lazy_ToString_ValueNotCreated;
+ }
+
+ /// <summary>Gets the value of the Lazy&lt;T&gt; for debugging display purposes.</summary>
+ internal T ValueForDebugDisplay
+ {
+ get
+ {
+ if (!IsValueCreated)
+ {
+ return default(T);
+ }
+ return _value;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance may be used concurrently from multiple threads.
+ /// </summary>
+ internal LazyThreadSafetyMode? Mode => LazyHelper.GetMode(_state);
+
+ /// <summary>
+ /// Gets whether the value creation is faulted or not
+ /// </summary>
+ internal bool IsValueFaulted => LazyHelper.GetIsValueFaulted(_state);
+
+ /// <summary>Gets a value indicating whether the <see cref="T:System.Lazy{T}"/> has been initialized.
+ /// </summary>
+ /// <value>true if the <see cref="T:System.Lazy{T}"/> instance has been initialized;
+ /// otherwise, false.</value>
+ /// <remarks>
+ /// The initialization of a <see cref="T:System.Lazy{T}"/> instance may result in either
+ /// a value being produced or an exception being thrown. If an exception goes unhandled during initialization,
+ /// <see cref="IsValueCreated"/> will return false.
+ /// </remarks>
+ public bool IsValueCreated => _state == null;
+
+ /// <summary>Gets the lazily initialized value of the current <see
+ /// cref="T:System.Threading.Lazy{T}"/>.</summary>
+ /// <value>The lazily initialized value of the current <see
+ /// cref="T:System.Threading.Lazy{T}"/>.</value>
+ /// <exception cref="T:System.MissingMemberException">
+ /// The <see cref="T:System.Threading.Lazy{T}"/> was initialized to use the default constructor
+ /// of the type being lazily initialized, and that type does not have a public, parameterless constructor.
+ /// </exception>
+ /// <exception cref="T:System.MemberAccessException">
+ /// The <see cref="T:System.Threading.Lazy{T}"/> was initialized to use the default constructor
+ /// of the type being lazily initialized, and permissions to access the constructor were missing.
+ /// </exception>
+ /// <exception cref="T:System.InvalidOperationException">
+ /// The <see cref="T:System.Threading.Lazy{T}"/> was constructed with the <see cref="T:System.Threading.LazyThreadSafetyMode.ExecutionAndPublication"/> or
+ /// <see cref="T:System.Threading.LazyThreadSafetyMode.None"/> and the initialization function attempted to access <see cref="Value"/> on this instance.
+ /// </exception>
+ /// <remarks>
+ /// If <see cref="IsValueCreated"/> is false, accessing <see cref="Value"/> will force initialization.
+ /// Please <see cref="System.Threading.LazyThreadSafetyMode"> for more information on how <see cref="T:System.Threading.Lazy{T}"/> will behave if an exception is thrown
+ /// from initialization delegate.
+ /// </remarks>
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ public T Value => _state == null ? _value : CreateValue();
+ }
+
+ /// <summary>A debugger view of the Lazy&lt;T&gt; to surface additional debugging properties and
+ /// to ensure that the Lazy&lt;T&gt; does not become initialized if it was not already.</summary>
+ internal sealed class LazyDebugView<T>
+ {
+ //The Lazy object being viewed.
+ private readonly Lazy<T> _lazy;
+
+ /// <summary>Constructs a new debugger view object for the provided Lazy object.</summary>
+ /// <param name="lazy">A Lazy object to browse in the debugger.</param>
+ public LazyDebugView(Lazy<T> lazy)
+ {
+ _lazy = lazy;
+ }
+
+ /// <summary>Returns whether the Lazy object is initialized or not.</summary>
+ public bool IsValueCreated
+ {
+ get { return _lazy.IsValueCreated; }
+ }
+
+ /// <summary>Returns the value of the Lazy object.</summary>
+ public T Value
+ {
+ get
+ { return _lazy.ValueForDebugDisplay; }
+ }
+
+ /// <summary>Returns the execution mode of the Lazy object</summary>
+ public LazyThreadSafetyMode? Mode
+ {
+ get { return _lazy.Mode; }
+ }
+
+ /// <summary>Returns the execution mode of the Lazy object</summary>
+ public bool IsValueFaulted
+ {
+ get { return _lazy.IsValueFaulted; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MarshalByRefObject.cs b/src/System.Private.CoreLib/shared/System/MarshalByRefObject.cs
new file mode 100644
index 0000000000..390b728329
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MarshalByRefObject.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public abstract class MarshalByRefObject
+ {
+ protected MarshalByRefObject()
+ {
+ }
+
+ public object GetLifetimeService()
+ {
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_Remoting);
+ }
+
+ public virtual object InitializeLifetimeService()
+ {
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_Remoting);
+ }
+
+ protected MarshalByRefObject MemberwiseClone(bool cloneIdentity)
+ {
+ MarshalByRefObject mbr = (MarshalByRefObject)base.MemberwiseClone();
+ return mbr;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Marvin.cs b/src/System.Private.CoreLib/shared/System/Marvin.cs
new file mode 100644
index 0000000000..e51ad9c552
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Marvin.cs
@@ -0,0 +1,139 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Internal.Runtime.CompilerServices;
+
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
+namespace System
+{
+ internal static class Marvin
+ {
+ /// <summary>
+ /// Compute a Marvin hash and collapse it into a 32-bit hash.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ComputeHash32(ReadOnlySpan<byte> data, ulong seed) => ComputeHash32(ref MemoryMarshal.GetReference(data), data.Length, seed);
+
+ /// <summary>
+ /// Compute a Marvin hash and collapse it into a 32-bit hash.
+ /// </summary>
+ public static int ComputeHash32(ref byte data, int count, ulong seed)
+ {
+ nuint ucount = (nuint)count;
+ uint p0 = (uint)seed;
+ uint p1 = (uint)(seed >> 32);
+
+ nuint byteOffset = 0;
+
+ while (ucount >= 8)
+ {
+ p0 += Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref data, byteOffset));
+ Block(ref p0, ref p1);
+
+ p0 += Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref data, byteOffset + 4));
+ Block(ref p0, ref p1);
+
+ byteOffset += 8;
+ ucount -= 8;
+ }
+
+ switch (ucount)
+ {
+ case 4:
+ p0 += Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref data, byteOffset));
+ Block(ref p0, ref p1);
+ goto case 0;
+
+ case 0:
+ p0 += 0x80u;
+ break;
+
+ case 5:
+ p0 += Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref data, byteOffset));
+ byteOffset += 4;
+ Block(ref p0, ref p1);
+ goto case 1;
+
+ case 1:
+ p0 += 0x8000u | Unsafe.AddByteOffset(ref data, byteOffset);
+ break;
+
+ case 6:
+ p0 += Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref data, byteOffset));
+ byteOffset += 4;
+ Block(ref p0, ref p1);
+ goto case 2;
+
+ case 2:
+ p0 += 0x800000u | Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref data, byteOffset));
+ break;
+
+ case 7:
+ p0 += Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref data, byteOffset));
+ byteOffset += 4;
+ Block(ref p0, ref p1);
+ goto case 3;
+
+ case 3:
+ p0 += 0x80000000u | (((uint)(Unsafe.AddByteOffset(ref data, byteOffset + 2))) << 16)| (uint)(Unsafe.ReadUnaligned<ushort>(ref Unsafe.AddByteOffset(ref data, byteOffset)));
+ break;
+
+ default:
+ Debug.Fail("Should not get here.");
+ break;
+ }
+
+ Block(ref p0, ref p1);
+ Block(ref p0, ref p1);
+
+ return (int)(p1 ^ p0);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Block(ref uint rp0, ref uint rp1)
+ {
+ uint p0 = rp0;
+ uint p1 = rp1;
+
+ p1 ^= p0;
+ p0 = _rotl(p0, 20);
+
+ p0 += p1;
+ p1 = _rotl(p1, 9);
+
+ p1 ^= p0;
+ p0 = _rotl(p0, 27);
+
+ p0 += p1;
+ p1 = _rotl(p1, 19);
+
+ rp0 = p0;
+ rp1 = p1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint _rotl(uint value, int shift)
+ {
+ // This is expected to be optimized into a single rol (or ror with negated shift value) instruction
+ return (value << shift) | (value >> (32 - shift));
+ }
+
+ public static ulong DefaultSeed { get; } = GenerateSeed();
+
+ private static unsafe ulong GenerateSeed()
+ {
+ ulong seed;
+ Interop.GetRandomBytes((byte*)&seed, sizeof(ulong));
+ return seed;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Math.cs b/src/System.Private.CoreLib/shared/System/Math.cs
new file mode 100644
index 0000000000..a175103f81
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Math.cs
@@ -0,0 +1,825 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Some floating-point math operations
+**
+**
+===========================================================*/
+
+//This class contains only static members and doesn't require serialization.
+
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ public static partial class Math
+ {
+ public const double E = 2.7182818284590452354;
+
+ public const double PI = 3.14159265358979323846;
+
+ private const int maxRoundingDigits = 15;
+
+ private static double doubleRoundLimit = 1e16d;
+
+ // This table is required for the Round function which can specify the number of digits to round to
+ private static double[] roundPower10Double = new double[] {
+ 1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8,
+ 1E9, 1E10, 1E11, 1E12, 1E13, 1E14, 1E15
+ };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static short Abs(short value)
+ {
+ if (value < 0)
+ {
+ value = (short)-value;
+ if (value < 0)
+ {
+ ThrowAbsOverflow();
+ }
+ }
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Abs(int value)
+ {
+ if (value < 0)
+ {
+ value = -value;
+ if (value < 0)
+ {
+ ThrowAbsOverflow();
+ }
+ }
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static long Abs(long value)
+ {
+ if (value < 0)
+ {
+ value = -value;
+ if (value < 0)
+ {
+ ThrowAbsOverflow();
+ }
+ }
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [CLSCompliant(false)]
+ public static sbyte Abs(sbyte value)
+ {
+ if (value < 0)
+ {
+ value = (sbyte)-value;
+ if (value < 0)
+ {
+ ThrowAbsOverflow();
+ }
+ }
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Abs(decimal value)
+ {
+ return decimal.Abs(ref value);
+ }
+
+ [StackTraceHidden]
+ private static void ThrowAbsOverflow()
+ {
+ throw new OverflowException(SR.Overflow_NegateTwosCompNum);
+ }
+
+ public static long BigMul(int a, int b)
+ {
+ return ((long)a) * b;
+ }
+
+ public static int DivRem(int a, int b, out int result)
+ {
+ // TODO https://github.com/dotnet/coreclr/issues/3439:
+ // Restore to using % and / when the JIT is able to eliminate one of the idivs.
+ // In the meantime, a * and - is measurably faster than an extra /.
+
+ int div = a / b;
+ result = a - (div * b);
+ return div;
+ }
+
+ public static long DivRem(long a, long b, out long result)
+ {
+ // TODO https://github.com/dotnet/coreclr/issues/3439:
+ // Restore to using % and / when the JIT is able to eliminate one of the idivs.
+ // In the meantime, a * and - is measurably faster than an extra /.
+
+ long div = a / b;
+ result = a - (div * b);
+ return div;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Ceiling(decimal d)
+ {
+ return decimal.Ceiling(d);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte Clamp(byte value, byte min, byte max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Clamp(decimal value, decimal min, decimal max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double Clamp(double value, double min, double max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static short Clamp(short value, short min, short max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Clamp(int value, int min, int max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static long Clamp(long value, long min, long max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static sbyte Clamp(sbyte value, sbyte min, sbyte max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Clamp(float value, float min, float max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort Clamp(ushort value, ushort min, ushort max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [CLSCompliant(false)]
+ public static uint Clamp(uint value, uint min, uint max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [CLSCompliant(false)]
+ public static ulong Clamp(ulong value, ulong min, ulong max)
+ {
+ if (min > max)
+ {
+ ThrowMinMaxException(min, max);
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Floor(decimal d)
+ {
+ return decimal.Floor(d);
+ }
+
+ public static double IEEERemainder(double x, double y)
+ {
+ if (double.IsNaN(x))
+ {
+ return x; // IEEE 754-2008: NaN payload must be preserved
+ }
+
+ if (double.IsNaN(y))
+ {
+ return y; // IEEE 754-2008: NaN payload must be preserved
+ }
+
+ var regularMod = x % y;
+
+ if (double.IsNaN(regularMod))
+ {
+ return double.NaN;
+ }
+
+ if ((regularMod == 0) && double.IsNegative(x))
+ {
+ return double.NegativeZero;
+ }
+
+ var alternativeResult = (regularMod - (Abs(y) * Sign(x)));
+
+ if (Abs(alternativeResult) == Abs(regularMod))
+ {
+ var divisionResult = x / y;
+ var roundedResult = Round(divisionResult);
+
+ if (Abs(roundedResult) > Abs(divisionResult))
+ {
+ return alternativeResult;
+ }
+ else
+ {
+ return regularMod;
+ }
+ }
+
+ if (Abs(alternativeResult) < Abs(regularMod))
+ {
+ return alternativeResult;
+ }
+ else
+ {
+ return regularMod;
+ }
+ }
+
+ public static double Log(double a, double newBase)
+ {
+ if (double.IsNaN(a))
+ {
+ return a; // IEEE 754-2008: NaN payload must be preserved
+ }
+
+ if (double.IsNaN(newBase))
+ {
+ return newBase; // IEEE 754-2008: NaN payload must be preserved
+ }
+
+ if (newBase == 1)
+ {
+ return double.NaN;
+ }
+
+ if ((a != 1) && ((newBase == 0) || double.IsPositiveInfinity(newBase)))
+ {
+ return double.NaN;
+ }
+
+ return (Log(a) / Log(newBase));
+ }
+
+ [NonVersionable]
+ public static byte Max(byte val1, byte val2)
+ {
+ return (val1 >= val2) ? val1 : val2;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Max(decimal val1, decimal val2)
+ {
+ return decimal.Max(ref val1, ref val2);
+ }
+
+ public static double Max(double val1, double val2)
+ {
+ if (val1 > val2)
+ {
+ return val1;
+ }
+
+ if (double.IsNaN(val1))
+ {
+ return val1;
+ }
+
+ return val2;
+ }
+
+ [NonVersionable]
+ public static short Max(short val1, short val2)
+ {
+ return (val1 >= val2) ? val1 : val2;
+ }
+
+ [NonVersionable]
+ public static int Max(int val1, int val2)
+ {
+ return (val1 >= val2) ? val1 : val2;
+ }
+
+ [NonVersionable]
+ public static long Max(long val1, long val2)
+ {
+ return (val1 >= val2) ? val1 : val2;
+ }
+
+ [CLSCompliant(false)]
+ [NonVersionable]
+ public static sbyte Max(sbyte val1, sbyte val2)
+ {
+ return (val1 >= val2) ? val1 : val2;
+ }
+
+ public static float Max(float val1, float val2)
+ {
+ if (val1 > val2)
+ {
+ return val1;
+ }
+
+ if (float.IsNaN(val1))
+ {
+ return val1;
+ }
+
+ return val2;
+ }
+
+ [CLSCompliant(false)]
+ [NonVersionable]
+ public static ushort Max(ushort val1, ushort val2)
+ {
+ return (val1 >= val2) ? val1 : val2;
+ }
+
+ [CLSCompliant(false)]
+ [NonVersionable]
+ public static uint Max(uint val1, uint val2)
+ {
+ return (val1 >= val2) ? val1 : val2;
+ }
+
+ [CLSCompliant(false)]
+ [NonVersionable]
+ public static ulong Max(ulong val1, ulong val2)
+ {
+ return (val1 >= val2) ? val1 : val2;
+ }
+
+ [NonVersionable]
+ public static byte Min(byte val1, byte val2)
+ {
+ return (val1 <= val2) ? val1 : val2;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Min(decimal val1, decimal val2)
+ {
+ return decimal.Min(ref val1, ref val2);
+ }
+
+ public static double Min(double val1, double val2)
+ {
+ if (val1 < val2)
+ {
+ return val1;
+ }
+
+ if (double.IsNaN(val1))
+ {
+ return val1;
+ }
+
+ return val2;
+ }
+
+ [NonVersionable]
+ public static short Min(short val1, short val2)
+ {
+ return (val1 <= val2) ? val1 : val2;
+ }
+
+ [NonVersionable]
+ public static int Min(int val1, int val2)
+ {
+ return (val1 <= val2) ? val1 : val2;
+ }
+
+ [NonVersionable]
+ public static long Min(long val1, long val2)
+ {
+ return (val1 <= val2) ? val1 : val2;
+ }
+
+ [CLSCompliant(false)]
+ [NonVersionable]
+ public static sbyte Min(sbyte val1, sbyte val2)
+ {
+ return (val1 <= val2) ? val1 : val2;
+ }
+
+ public static float Min(float val1, float val2)
+ {
+ if (val1 < val2)
+ {
+ return val1;
+ }
+
+ if (float.IsNaN(val1))
+ {
+ return val1;
+ }
+
+ return val2;
+ }
+
+ [CLSCompliant(false)]
+ [NonVersionable]
+ public static ushort Min(ushort val1, ushort val2)
+ {
+ return (val1 <= val2) ? val1 : val2;
+ }
+
+ [CLSCompliant(false)]
+ [NonVersionable]
+ public static uint Min(uint val1, uint val2)
+ {
+ return (val1 <= val2) ? val1 : val2;
+ }
+
+ [CLSCompliant(false)]
+ [NonVersionable]
+ public static ulong Min(ulong val1, ulong val2)
+ {
+ return (val1 <= val2) ? val1 : val2;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Round(decimal d)
+ {
+ return decimal.Round(d, 0);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Round(decimal d, int decimals)
+ {
+ return decimal.Round(d, decimals);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Round(decimal d, MidpointRounding mode)
+ {
+ return decimal.Round(d, 0, mode);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Round(decimal d, int decimals, MidpointRounding mode)
+ {
+ return decimal.Round(d, decimals, mode);
+ }
+
+ [Intrinsic]
+ public static double Round(double a)
+ {
+ // ************************************************************************************
+ // IMPORTANT: Do not change this implementation without also updating Math.Round(double),
+ // FloatingPointUtils::round(double), and FloatingPointUtils::round(float)
+ // ************************************************************************************
+
+ // If the number has no fractional part do nothing
+ // This shortcut is necessary to workaround precision loss in borderline cases on some platforms
+
+ if (a == (double)((long)a))
+ {
+ return a;
+ }
+
+ // We had a number that was equally close to 2 integers.
+ // We need to return the even one.
+
+ double flrTempVal = Floor(a + 0.5);
+
+ if ((a == (Floor(a) + 0.5)) && (FMod(flrTempVal, 2.0) != 0))
+ {
+ flrTempVal -= 1.0;
+ }
+
+ return copysign(flrTempVal, a);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double Round(double value, int digits)
+ {
+ return Round(value, digits, MidpointRounding.ToEven);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double Round(double value, MidpointRounding mode)
+ {
+ return Round(value, 0, mode);
+ }
+
+ public static unsafe double Round(double value, int digits, MidpointRounding mode)
+ {
+ if ((digits < 0) || (digits > maxRoundingDigits))
+ {
+ throw new ArgumentOutOfRangeException(nameof(digits), SR.ArgumentOutOfRange_RoundingDigits);
+ }
+
+ if (mode < MidpointRounding.ToEven || mode > MidpointRounding.AwayFromZero)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode));
+ }
+
+ if (Abs(value) < doubleRoundLimit)
+ {
+ var power10 = roundPower10Double[digits];
+
+ value *= power10;
+
+ if (mode == MidpointRounding.AwayFromZero)
+ {
+ var fraction = ModF(value, &value);
+
+ if (Abs(fraction) >= 0.5)
+ {
+ value += Sign(fraction);
+ }
+ }
+ else
+ {
+ value = Round(value);
+ }
+
+ value /= power10;
+ }
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Sign(decimal value)
+ {
+ return decimal.Sign(ref value);
+ }
+
+ public static int Sign(double value)
+ {
+ if (value < 0)
+ {
+ return -1;
+ }
+ else if (value > 0)
+ {
+ return 1;
+ }
+ else if (value == 0)
+ {
+ return 0;
+ }
+
+ throw new ArithmeticException(SR.Arithmetic_NaN);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Sign(short value)
+ {
+ return Sign((int)value);
+ }
+
+ public static int Sign(int value)
+ {
+ return unchecked(value >> 31 | (int)((uint)-value >> 31));
+ }
+
+ public static int Sign(long value)
+ {
+ return unchecked((int)(value >> 63 | (long)((ulong)-value >> 63)));
+ }
+
+ [CLSCompliant(false)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Sign(sbyte value)
+ {
+ return Sign((int)value);
+ }
+
+ public static int Sign(float value)
+ {
+ if (value < 0)
+ {
+ return -1;
+ }
+ else if (value > 0)
+ {
+ return 1;
+ }
+ else if (value == 0)
+ {
+ return 0;
+ }
+
+ throw new ArithmeticException(SR.Arithmetic_NaN);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static decimal Truncate(decimal d)
+ {
+ return decimal.Truncate(d);
+ }
+
+ public static unsafe double Truncate(double d)
+ {
+ ModF(d, &d);
+ return d;
+ }
+
+ private static unsafe double copysign(double x, double y)
+ {
+ var xbits = BitConverter.DoubleToInt64Bits(x);
+ var ybits = BitConverter.DoubleToInt64Bits(y);
+
+ // If the sign bits of x and y are not the same,
+ // flip the sign bit of x and return the new value;
+ // otherwise, just return x
+
+ if (((xbits ^ ybits) >> 63) != 0)
+ {
+ return BitConverter.Int64BitsToDouble(xbits ^ long.MinValue);
+ }
+
+ return x;
+ }
+
+ private static void ThrowMinMaxException<T>(T min, T max)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_MinMaxValue, min, max));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MathF.cs b/src/System.Private.CoreLib/shared/System/MathF.cs
new file mode 100644
index 0000000000..5b7e48b062
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MathF.cs
@@ -0,0 +1,235 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Purpose: Some single-precision floating-point math operations
+**
+===========================================================*/
+
+//This class contains only static members and doesn't require serialization.
+
+using System.Runtime;
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ public static partial class MathF
+ {
+ public const float E = 2.71828183f;
+
+ public const float PI = 3.14159265f;
+
+ private const int maxRoundingDigits = 6;
+
+ // This table is required for the Round function which can specify the number of digits to round to
+ private static float[] roundPower10Single = new float[] {
+ 1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, 1e6f
+ };
+
+ private static float singleRoundLimit = 1e8f;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Abs(float x)
+ {
+ return Math.Abs(x);
+ }
+
+ public static float IEEERemainder(float x, float y)
+ {
+ if (float.IsNaN(x))
+ {
+ return x; // IEEE 754-2008: NaN payload must be preserved
+ }
+
+ if (float.IsNaN(y))
+ {
+ return y; // IEEE 754-2008: NaN payload must be preserved
+ }
+
+ var regularMod = x % y;
+
+ if (float.IsNaN(regularMod))
+ {
+ return float.NaN;
+ }
+
+ if ((regularMod == 0) && float.IsNegative(x))
+ {
+ return float.NegativeZero;
+ }
+
+ var alternativeResult = (regularMod - (Abs(y) * Sign(x)));
+
+ if (Abs(alternativeResult) == Abs(regularMod))
+ {
+ var divisionResult = x / y;
+ var roundedResult = Round(divisionResult);
+
+ if (Abs(roundedResult) > Abs(divisionResult))
+ {
+ return alternativeResult;
+ }
+ else
+ {
+ return regularMod;
+ }
+ }
+
+ if (Abs(alternativeResult) < Abs(regularMod))
+ {
+ return alternativeResult;
+ }
+ else
+ {
+ return regularMod;
+ }
+ }
+
+ public static float Log(float x, float y)
+ {
+ if (float.IsNaN(x))
+ {
+ return x; // IEEE 754-2008: NaN payload must be preserved
+ }
+
+ if (float.IsNaN(y))
+ {
+ return y; // IEEE 754-2008: NaN payload must be preserved
+ }
+
+ if (y == 1)
+ {
+ return float.NaN;
+ }
+
+ if ((x != 1) && ((y == 0) || float.IsPositiveInfinity(y)))
+ {
+ return float.NaN;
+ }
+
+ return Log(x) / Log(y);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Max(float x, float y)
+ {
+ return Math.Max(x, y);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Min(float x, float y)
+ {
+ return Math.Min(x, y);
+ }
+
+ [Intrinsic]
+ public static float Round(float x)
+ {
+ // ************************************************************************************
+ // IMPORTANT: Do not change this implementation without also updating Math.Round(double),
+ // FloatingPointUtils::round(double), and FloatingPointUtils::round(float)
+ // ************************************************************************************
+
+ // If the number has no fractional part do nothing
+ // This shortcut is necessary to workaround precision loss in borderline cases on some platforms
+
+ if (x == (float)((int)x))
+ {
+ return x;
+ }
+
+ // We had a number that was equally close to 2 integers.
+ // We need to return the even one.
+
+ float flrTempVal = Floor(x + 0.5f);
+
+ if ((x == (Floor(x) + 0.5f)) && (FMod(flrTempVal, 2.0f) != 0))
+ {
+ flrTempVal -= 1.0f;
+ }
+
+ return CopySign(flrTempVal, x);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Round(float x, int digits)
+ {
+ return Round(x, digits, MidpointRounding.ToEven);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Round(float x, MidpointRounding mode)
+ {
+ return Round(x, 0, mode);
+ }
+
+ public static unsafe float Round(float x, int digits, MidpointRounding mode)
+ {
+ if ((digits < 0) || (digits > maxRoundingDigits))
+ {
+ throw new ArgumentOutOfRangeException(nameof(digits), SR.ArgumentOutOfRange_RoundingDigits);
+ }
+
+ if (mode < MidpointRounding.ToEven || mode > MidpointRounding.AwayFromZero)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidEnum, mode, nameof(MidpointRounding)), nameof(mode));
+ }
+
+ if (Abs(x) < singleRoundLimit)
+ {
+ var power10 = roundPower10Single[digits];
+
+ x *= power10;
+
+ if (mode == MidpointRounding.AwayFromZero)
+ {
+ var fraction = ModF(x, &x);
+
+ if (Abs(fraction) >= 0.5f)
+ {
+ x += Sign(fraction);
+ }
+ }
+ else
+ {
+ x = Round(x);
+ }
+
+ x /= power10;
+ }
+
+ return x;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Sign(float x)
+ {
+ return Math.Sign(x);
+ }
+
+ public static unsafe float Truncate(float x)
+ {
+ ModF(x, &x);
+ return x;
+ }
+
+ private static unsafe float CopySign(float x, float y)
+ {
+ var xbits = BitConverter.SingleToInt32Bits(x);
+ var ybits = BitConverter.SingleToInt32Bits(y);
+
+ // If the sign bits of x and y are not the same,
+ // flip the sign bit of x and return the new value;
+ // otherwise, just return x
+
+ if (((xbits ^ ybits) >> 31) != 0)
+ {
+ return BitConverter.Int32BitsToSingle(xbits ^ int.MinValue);
+ }
+
+ return x;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MemberAccessException.cs b/src/System.Private.CoreLib/shared/System/MemberAccessException.cs
new file mode 100644
index 0000000000..dfea52dbed
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MemberAccessException.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////////
+// MemberAccessException
+// Thrown when we try accessing a member that we cannot
+// access, due to it being removed, private or something similar.
+////////////////////////////////////////////////////////////////////////////////
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The MemberAccessException is thrown when trying to access a class
+ // member fails.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class MemberAccessException : SystemException
+ {
+ // Creates a new MemberAccessException with its message string set to
+ // the empty string, its HRESULT set to COR_E_MEMBERACCESS,
+ // and its ExceptionInfo reference set to null.
+ public MemberAccessException()
+ : base(SR.Arg_AccessException)
+ {
+ HResult = HResults.COR_E_MEMBERACCESS;
+ }
+
+ // Creates a new MemberAccessException with its message string set to
+ // message, its HRESULT set to COR_E_ACCESS,
+ // and its ExceptionInfo reference set to null.
+ //
+ public MemberAccessException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_MEMBERACCESS;
+ }
+
+ public MemberAccessException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_MEMBERACCESS;
+ }
+
+ protected MemberAccessException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Memory.cs b/src/System.Private.CoreLib/shared/System/Memory.cs
new file mode 100644
index 0000000000..0abe3634ae
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Memory.cs
@@ -0,0 +1,441 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute;
+using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
+#if !FEATURE_PORTABLE_SPAN
+using Internal.Runtime.CompilerServices;
+#endif // FEATURE_PORTABLE_SPAN
+
+namespace System
+{
+ /// <summary>
+ /// Memory represents a contiguous region of arbitrary memory similar to <see cref="Span{T}"/>.
+ /// Unlike <see cref="Span{T}"/>, it is not a byref-like type.
+ /// </summary>
+ [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ public readonly struct Memory<T>
+ {
+ // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout,
+ // as code uses Unsafe.As to cast between them.
+
+ // The highest order bit of _index is used to discern whether _object is an array/string or an owned memory
+ // if (_index >> 31) == 1, object _object is an MemoryManager<T>
+ // else, object _object is a T[] or a string.
+ // if (_length >> 31) == 1, _object is a pre-pinned array, so Pin() will not allocate a new GCHandle
+ // else, Pin() needs to allocate a new GCHandle to pin the object.
+ // It can only be a string if the Memory<T> was created by
+ // using unsafe / marshaling code to reinterpret a ReadOnlyMemory<char> wrapped around a string as
+ // a Memory<T>.
+ private readonly object _object;
+ private readonly int _index;
+ private readonly int _length;
+
+ private const int RemoveFlagsBitMask = 0x7FFFFFFF;
+
+ /// <summary>
+ /// Creates a new memory over the entirety of the target array.
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Memory(T[] array)
+ {
+ if (array == null)
+ {
+ this = default;
+ return; // returns default
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+
+ _object = array;
+ _index = 0;
+ _length = array.Length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Memory(T[] array, int start)
+ {
+ if (array == null)
+ {
+ if (start != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ this = default;
+ return; // returns default
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+ if ((uint)start > (uint)array.Length)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _object = array;
+ _index = start;
+ _length = array.Length - start;
+ }
+
+ /// <summary>
+ /// Creates a new memory over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <param name="length">The number of items in the memory.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Memory(T[] array, int start, int length)
+ {
+ if (array == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ this = default;
+ return; // returns default
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+ if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _object = array;
+ _index = start;
+ _length = length;
+ }
+
+ /// <summary>
+ /// Creates a new memory from a memory manager that provides specific method implementations beginning
+ /// at 0 index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="manager">The memory manager.</param>
+ /// <param name="length">The number of items in the memory.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="length"/> is negative.
+ /// </exception>
+ /// <remarks>For internal infrastructure only</remarks>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Memory(MemoryManager<T> manager, int length)
+ {
+ Debug.Assert(manager != null);
+
+ if (length < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _object = manager;
+ _index = (1 << 31); // Mark as MemoryManager type
+ // Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
+ _length = length;
+ }
+
+ /// <summary>
+ /// Creates a new memory from a memory manager that provides specific method implementations beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="manager">The memory manager.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <param name="length">The number of items in the memory.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or <paramref name="length"/> is negative.
+ /// </exception>
+ /// <remarks>For internal infrastructure only</remarks>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Memory(MemoryManager<T> manager, int start, int length)
+ {
+ Debug.Assert(manager != null);
+
+ if (length < 0 || start < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _object = manager;
+ _index = start | (1 << 31); // Mark as MemoryManager type
+ // Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
+ _length = length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Memory(object obj, int start, int length)
+ {
+ // No validation performed; caller must provide any necessary validation.
+ _object = obj;
+ _index = start;
+ _length = length;
+ }
+
+ /// <summary>
+ /// Defines an implicit conversion of an array to a <see cref="Memory{T}"/>
+ /// </summary>
+ public static implicit operator Memory<T>(T[] array) => new Memory<T>(array);
+
+ /// <summary>
+ /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Memory{T}"/>
+ /// </summary>
+ public static implicit operator Memory<T>(ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count);
+
+ /// <summary>
+ /// Defines an implicit conversion of a <see cref="Memory{T}"/> to a <see cref="ReadOnlyMemory{T}"/>
+ /// </summary>
+ public static implicit operator ReadOnlyMemory<T>(Memory<T> memory) =>
+ Unsafe.As<Memory<T>, ReadOnlyMemory<T>>(ref memory);
+
+ /// <summary>
+ /// Returns an empty <see cref="Memory{T}"/>
+ /// </summary>
+ public static Memory<T> Empty => default;
+
+ /// <summary>
+ /// The number of items in the memory.
+ /// </summary>
+ public int Length => _length & RemoveFlagsBitMask;
+
+ /// <summary>
+ /// Returns true if Length is 0.
+ /// </summary>
+ public bool IsEmpty => (_length & RemoveFlagsBitMask) == 0;
+
+ /// <summary>
+ /// For <see cref="Memory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory.
+ /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements.
+ /// </summary>
+ public override string ToString()
+ {
+ if (typeof(T) == typeof(char))
+ {
+ return (_object is string str) ? str.Substring(_index, _length & RemoveFlagsBitMask) : Span.ToString();
+ }
+ return string.Format("System.Memory<{0}>[{1}]", typeof(T).Name, _length & RemoveFlagsBitMask);
+ }
+
+ /// <summary>
+ /// Forms a slice out of the given memory, beginning at 'start'.
+ /// </summary>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Memory<T> Slice(int start)
+ {
+ // Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not.
+ int capturedLength = _length;
+ int actualLength = capturedLength & RemoveFlagsBitMask;
+ if ((uint)start > (uint)actualLength)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ }
+
+ // It is expected for (capturedLength - start) to be negative if the memory is already pre-pinned.
+ return new Memory<T>(_object, _index + start, capturedLength - start);
+ }
+
+ /// <summary>
+ /// Forms a slice out of the given memory, beginning at 'start', of given length
+ /// </summary>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <param name="length">The desired length for the slice (exclusive).</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Memory<T> Slice(int start, int length)
+ {
+ // Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not.
+ int capturedLength = _length;
+ int actualLength = capturedLength & RemoveFlagsBitMask;
+ if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start))
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ }
+
+ // Set the high-bit to match the this._length high bit (1 for pre-pinned, 0 for unpinned).
+ return new Memory<T>(_object, _index + start, length | (capturedLength & ~RemoveFlagsBitMask));
+ }
+
+ /// <summary>
+ /// Returns a span from the memory.
+ /// </summary>
+ public Span<T> Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ if (_index < 0)
+ {
+ Debug.Assert(_length >= 0);
+ Debug.Assert(_object != null);
+ return ((MemoryManager<T>)_object).GetSpan().Slice(_index & RemoveFlagsBitMask, _length);
+ }
+ else if (typeof(T) == typeof(char) && _object is string s)
+ {
+ Debug.Assert(_length >= 0);
+ // This is dangerous, returning a writable span for a string that should be immutable.
+ // However, we need to handle the case where a ReadOnlyMemory<char> was created from a string
+ // and then cast to a Memory<T>. Such a cast can only be done with unsafe or marshaling code,
+ // in which case that's the dangerous operation performed by the dev, and we're just following
+ // suit here to make it work as best as possible.
+#if FEATURE_PORTABLE_SPAN
+ return new Span<T>(Unsafe.As<Pinnable<T>>(s), MemoryExtensions.StringAdjustment, s.Length).Slice(_index, _length);
+#else
+ return new Span<T>(ref Unsafe.As<char, T>(ref s.GetRawStringData()), s.Length).Slice(_index, _length);
+#endif // FEATURE_PORTABLE_SPAN
+ }
+ else if (_object != null)
+ {
+ return new Span<T>((T[])_object, _index, _length & RemoveFlagsBitMask);
+ }
+ else
+ {
+ return default;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies the contents of the memory into the destination. If the source
+ /// and destination overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.
+ ///
+ /// <param name="destination">The Memory to copy items into.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when the destination is shorter than the source.
+ /// </exception>
+ /// </summary>
+ public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
+
+ /// <summary>
+ /// Copies the contents of the memory into the destination. If the source
+ /// and destination overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.
+ ///
+ /// <returns>If the destination is shorter than the source, this method
+ /// return false and no data is written to the destination.</returns>
+ /// </summary>
+ /// <param name="destination">The span to copy items into.</param>
+ public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
+
+ /// <summary>
+ /// Creates a handle for the memory.
+ /// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
+ /// is disposed, enabling taking and using the memory's address.
+ /// <exception cref="System.ArgumentException">
+ /// An instance with nonprimitive (non-blittable) members cannot be pinned.
+ /// </exception>
+ /// </summary>
+ public unsafe MemoryHandle Pin()
+ {
+ if (_index < 0)
+ {
+ Debug.Assert(_object != null);
+ return ((MemoryManager<T>)_object).Pin((_index & RemoveFlagsBitMask));
+ }
+ else if (typeof(T) == typeof(char) && _object is string s)
+ {
+ // This case can only happen if a ReadOnlyMemory<char> was created around a string
+ // and then that was cast to a Memory<char> using unsafe / marshaling code. This needs
+ // to work, however, so that code that uses a single Memory<char> field to store either
+ // a readable ReadOnlyMemory<char> or a writable Memory<char> can still be pinned and
+ // used for interop purposes.
+ GCHandle handle = GCHandle.Alloc(s, GCHandleType.Pinned);
+#if FEATURE_PORTABLE_SPAN
+ void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
+#else
+ void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref s.GetRawStringData()), _index);
+#endif // FEATURE_PORTABLE_SPAN
+ return new MemoryHandle(pointer, handle);
+ }
+ else if (_object is T[] array)
+ {
+ // Array is already pre-pinned
+ if (_length < 0)
+ {
+#if FEATURE_PORTABLE_SPAN
+ void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref MemoryMarshal.GetReference<T>(array)), _index);
+#else
+ void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
+#endif // FEATURE_PORTABLE_SPAN
+ return new MemoryHandle(pointer);
+ }
+ else
+ {
+ GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned);
+#if FEATURE_PORTABLE_SPAN
+ void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
+#else
+ void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
+#endif // FEATURE_PORTABLE_SPAN
+ return new MemoryHandle(pointer, handle);
+ }
+ }
+ return default;
+ }
+
+ /// <summary>
+ /// Copies the contents from the memory into a new array. This heap
+ /// allocates, so should generally be avoided, however it is sometimes
+ /// necessary to bridge the gap with APIs written in terms of arrays.
+ /// </summary>
+ public T[] ToArray() => Span.ToArray();
+
+ /// <summary>
+ /// Determines whether the specified object is equal to the current object.
+ /// Returns true if the object is Memory or ReadOnlyMemory and if both objects point to the same array and have the same length.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj)
+ {
+ if (obj is ReadOnlyMemory<T>)
+ {
+ return ((ReadOnlyMemory<T>)obj).Equals(this);
+ }
+ else if (obj is Memory<T> memory)
+ {
+ return Equals(memory);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Returns true if the memory points to the same array and has the same length. Note that
+ /// this does *not* check to see if the *contents* are equal.
+ /// </summary>
+ public bool Equals(Memory<T> other)
+ {
+ return
+ _object == other._object &&
+ _index == other._index &&
+ _length == other._length;
+ }
+
+ /// <summary>
+ /// Serves as the default hash function.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode()
+ {
+ return _object != null ? CombineHashCodes(_object.GetHashCode(), _index.GetHashCode(), _length.GetHashCode()) : 0;
+ }
+
+ private static int CombineHashCodes(int left, int right)
+ {
+ return ((left << 5) + left) ^ right;
+ }
+
+ private static int CombineHashCodes(int h1, int h2, int h3)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2), h3);
+ }
+
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MemoryDebugView.cs b/src/System.Private.CoreLib/shared/System/MemoryDebugView.cs
new file mode 100644
index 0000000000..f56a67c636
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MemoryDebugView.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ internal sealed class MemoryDebugView<T>
+ {
+ private readonly ReadOnlyMemory<T> _memory;
+
+ public MemoryDebugView(Memory<T> memory)
+ {
+ _memory = memory;
+ }
+
+ public MemoryDebugView(ReadOnlyMemory<T> memory)
+ {
+ _memory = memory;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items => _memory.ToArray();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs
new file mode 100644
index 0000000000..a9a755aa80
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs
@@ -0,0 +1,487 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ /// <summary>
+ /// Extension methods for Span{T}, Memory{T}, and friends.
+ /// </summary>
+ public static partial class MemoryExtensions
+ {
+ /// <summary>
+ /// Returns a value indicating whether the specified <paramref name="value"/> occurs within the <paramref name="span"/>.
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The value to seek within the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ /// </summary>
+ public static bool Contains(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ return (IndexOf(span, value, comparisonType) >= 0);
+ }
+
+ /// <summary>
+ /// Determines whether this <paramref name="span"/> and the specified <paramref name="other"/> span have the same characters
+ /// when compared using the specified <paramref name="comparisonType"/> option.
+ /// <param name="span">The source span.</param>
+ /// <param name="other">The value to compare with the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="other"/> are compared.</param>
+ /// </summary>
+ public static bool Equals(this ReadOnlySpan<char> span, ReadOnlySpan<char> other, StringComparison comparisonType)
+ {
+ string.CheckStringComparison(comparisonType);
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return (CultureInfo.CurrentCulture.CompareInfo.CompareOptionNone(span, other) == 0);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return (CultureInfo.CurrentCulture.CompareInfo.CompareOptionIgnoreCase(span, other) == 0);
+
+ case StringComparison.InvariantCulture:
+ return (CompareInfo.Invariant.CompareOptionNone(span, other) == 0);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return (CompareInfo.Invariant.CompareOptionIgnoreCase(span, other) == 0);
+
+ case StringComparison.Ordinal:
+ return EqualsOrdinal(span, other);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return EqualsOrdinalIgnoreCase(span, other);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool EqualsOrdinal(this ReadOnlySpan<char> span, ReadOnlySpan<char> value)
+ {
+ if (span.Length != value.Length)
+ return false;
+ if (value.Length == 0) // span.Length == value.Length == 0
+ return true;
+ return span.SequenceEqual(value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool EqualsOrdinalIgnoreCase(this ReadOnlySpan<char> span, ReadOnlySpan<char> value)
+ {
+ if (span.Length != value.Length)
+ return false;
+ if (value.Length == 0) // span.Length == value.Length == 0
+ return true;
+ return (CompareInfo.CompareOrdinalIgnoreCase(span, value) == 0);
+ }
+
+ // TODO https://github.com/dotnet/corefx/issues/27526
+ internal static bool Contains(this ReadOnlySpan<char> source, char value)
+ {
+ for (int i = 0; i < source.Length; i++)
+ {
+ if (source[i] == value)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Compares the specified <paramref name="span"/> and <paramref name="other"/> using the specified <paramref name="comparisonType"/>,
+ /// and returns an integer that indicates their relative position in the sort order.
+ /// <param name="span">The source span.</param>
+ /// <param name="other">The value to compare with the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="other"/> are compared.</param>
+ /// </summary>
+ public static int CompareTo(this ReadOnlySpan<char> span, ReadOnlySpan<char> other, StringComparison comparisonType)
+ {
+ string.CheckStringComparison(comparisonType);
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return CultureInfo.CurrentCulture.CompareInfo.CompareOptionNone(span, other);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.CompareOptionIgnoreCase(span, other);
+
+ case StringComparison.InvariantCulture:
+ return CompareInfo.Invariant.CompareOptionNone(span, other);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.CompareOptionIgnoreCase(span, other);
+
+ case StringComparison.Ordinal:
+ if (span.Length == 0 || other.Length == 0)
+ return span.Length - other.Length;
+ return string.CompareOrdinal(span, other);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return CompareInfo.CompareOrdinalIgnoreCase(span, other);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return 0;
+ }
+
+ /// <summary>
+ /// Reports the zero-based index of the first occurrence of the specified <paramref name="value"/> in the current <paramref name="span"/>.
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The value to seek within the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ /// </summary>
+ public static int IndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ string.CheckStringComparison(comparisonType);
+
+ if (value.Length == 0)
+ {
+ return 0;
+ }
+
+ if (span.Length == 0)
+ {
+ return -1;
+ }
+
+ if (GlobalizationMode.Invariant)
+ {
+ return CompareInfo.InvariantIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.IndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.IndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ case StringComparison.OrdinalIgnoreCase:
+ return CompareInfo.Invariant.IndexOfOrdinal(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return -1;
+ }
+
+ /// <summary>
+ /// Copies the characters from the source span into the destination, converting each character to lowercase,
+ /// using the casing rules of the specified culture.
+ /// </summary>
+ /// <param name="source">The source span.</param>
+ /// <param name="destination">The destination span which contains the transformed characters.</param>
+ /// <param name="culture">An object that supplies culture-specific casing rules.</param>
+ /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.</remarks>
+ /// <exception cref="System.ArgumentNullException">
+ /// Thrown when <paramref name="culture"/> is null.
+ /// </exception>
+ public static int ToLower(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
+ {
+ if (culture == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
+
+ // Assuming that changing case does not affect length
+ if (destination.Length < source.Length)
+ return -1;
+
+ if (GlobalizationMode.Invariant)
+ TextInfo.ToLowerAsciiInvariant(source, destination);
+ else
+ culture.TextInfo.ChangeCase(source, destination, toUpper: false);
+ return source.Length;
+ }
+
+ /// <summary>
+ /// Copies the characters from the source span into the destination, converting each character to lowercase,
+ /// using the casing rules of the invariant culture.
+ /// </summary>
+ /// <param name="source">The source span.</param>
+ /// <param name="destination">The destination span which contains the transformed characters.</param>
+ /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.</remarks>
+ public static int ToLowerInvariant(this ReadOnlySpan<char> source, Span<char> destination)
+ {
+ // Assuming that changing case does not affect length
+ if (destination.Length < source.Length)
+ return -1;
+
+ if (GlobalizationMode.Invariant)
+ TextInfo.ToLowerAsciiInvariant(source, destination);
+ else
+ CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: false);
+ return source.Length;
+ }
+
+ /// <summary>
+ /// Copies the characters from the source span into the destination, converting each character to uppercase,
+ /// using the casing rules of the specified culture.
+ /// </summary>
+ /// <param name="source">The source span.</param>
+ /// <param name="destination">The destination span which contains the transformed characters.</param>
+ /// <param name="culture">An object that supplies culture-specific casing rules.</param>
+ /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.</remarks>
+ /// <exception cref="System.ArgumentNullException">
+ /// Thrown when <paramref name="culture"/> is null.
+ /// </exception>
+ public static int ToUpper(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
+ {
+ if (culture == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
+
+ // Assuming that changing case does not affect length
+ if (destination.Length < source.Length)
+ return -1;
+
+ if (GlobalizationMode.Invariant)
+ TextInfo.ToUpperAsciiInvariant(source, destination);
+ else
+ culture.TextInfo.ChangeCase(source, destination, toUpper: true);
+ return source.Length;
+ }
+
+ /// <summary>
+ /// Copies the characters from the source span into the destination, converting each character to uppercase
+ /// using the casing rules of the invariant culture.
+ /// </summary>
+ /// <param name="source">The source span.</param>
+ /// <param name="destination">The destination span which contains the transformed characters.</param>
+ /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.</remarks>
+ public static int ToUpperInvariant(this ReadOnlySpan<char> source, Span<char> destination)
+ {
+ // Assuming that changing case does not affect length
+ if (destination.Length < source.Length)
+ return -1;
+
+ if (GlobalizationMode.Invariant)
+ TextInfo.ToUpperAsciiInvariant(source, destination);
+ else
+ CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: true);
+ return source.Length;
+ }
+
+ /// <summary>
+ /// Determines whether the end of the <paramref name="span"/> matches the specified <paramref name="value"/> when compared using the specified <paramref name="comparisonType"/> option.
+ /// </summary>
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The sequence to compare to the end of the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ public static bool EndsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ string.CheckStringComparison(comparisonType);
+
+ if (value.Length == 0)
+ {
+ return true;
+ }
+
+ if (comparisonType >= StringComparison.Ordinal || GlobalizationMode.Invariant)
+ {
+ if (string.GetCaseCompareOfComparisonCulture(comparisonType) == CompareOptions.None)
+ return span.EndsWith(value);
+
+ return (span.Length >= value.Length) ? (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(span.Length - value.Length), value) == 0) : false;
+ }
+
+ if (span.Length == 0)
+ {
+ return false;
+ }
+
+ return (comparisonType >= StringComparison.InvariantCulture) ?
+ CompareInfo.Invariant.IsSuffix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType)) :
+ CultureInfo.CurrentCulture.CompareInfo.IsSuffix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
+ }
+
+ /// <summary>
+ /// Determines whether the beginning of the <paramref name="span"/> matches the specified <paramref name="value"/> when compared using the specified <paramref name="comparisonType"/> option.
+ /// </summary>
+ /// <param name="span">The source span.</param>
+ /// <param name="value">The sequence to compare to the beginning of the source span.</param>
+ /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
+ public static bool StartsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ string.CheckStringComparison(comparisonType);
+
+ if (value.Length == 0)
+ {
+ return true;
+ }
+
+ if (comparisonType >= StringComparison.Ordinal || GlobalizationMode.Invariant)
+ {
+ if (string.GetCaseCompareOfComparisonCulture(comparisonType) == CompareOptions.None)
+ return span.StartsWith(value);
+
+ return (span.Length >= value.Length) ? (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(0, value.Length), value) == 0) : false;
+ }
+
+ if (span.Length == 0)
+ {
+ return false;
+ }
+
+ return (comparisonType >= StringComparison.InvariantCulture) ?
+ CompareInfo.Invariant.IsPrefix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType)) :
+ CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
+ }
+
+ /// <summary>
+ /// Creates a new span over the portion of the target array.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this T[] array, int start)
+ {
+ if (array == null)
+ {
+ if (start != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ return default;
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+ if ((uint)start > (uint)array.Length)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), array.Length - start);
+ }
+
+ /// <summary>
+ /// Creates a new readonly span over the portion of the target string.
+ /// </summary>
+ /// <param name="text">The target string.</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> AsSpan(this string text)
+ {
+ if (text == null)
+ return default;
+
+ return new ReadOnlySpan<char>(ref text.GetRawStringData(), text.Length);
+ }
+
+ /// <summary>
+ /// Creates a new readonly span over the portion of the target string.
+ /// </summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="text"/> is null.</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;text.Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> AsSpan(this string text, int start)
+ {
+ if (text == null)
+ {
+ if (start != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ return default;
+ }
+
+ if ((uint)start > (uint)text.Length)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), text.Length - start);
+ }
+
+ /// <summary>
+ /// Creates a new readonly span over the portion of the target string.
+ /// </summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <param name="length">The desired length for the slice (exclusive).</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index or <paramref name="length"/> is not in range.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> AsSpan(this string text, int start, int length)
+ {
+ if (text == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ return default;
+ }
+
+ if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), length);
+ }
+
+ /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+ /// <param name="text">The target string.</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ public static ReadOnlyMemory<char> AsMemory(this string text)
+ {
+ if (text == null)
+ return default;
+
+ return new ReadOnlyMemory<char>(text, 0, text.Length);
+ }
+
+ /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;text.Length).
+ /// </exception>
+ public static ReadOnlyMemory<char> AsMemory(this string text, int start)
+ {
+ if (text == null)
+ {
+ if (start != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ return default;
+ }
+
+ if ((uint)start > (uint)text.Length)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new ReadOnlyMemory<char>(text, start, text.Length - start);
+ }
+
+ /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <param name="length">The desired length for the slice (exclusive).</param>
+ /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index or <paramref name="length"/> is not in range.
+ /// </exception>
+ public static ReadOnlyMemory<char> AsMemory(this string text, int start, int length)
+ {
+ if (text == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ return default;
+ }
+
+ if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new ReadOnlyMemory<char>(text, start, length);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs
new file mode 100644
index 0000000000..b4782dcaf0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs
@@ -0,0 +1,1389 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+#if netstandard
+using nuint = System.NUInt;
+#else
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif // BIT64
+#endif // netstandard
+
+namespace System
+{
+ /// <summary>
+ /// Extension methods for Span{T}, Memory{T}, and friends.
+ /// </summary>
+ public static partial class MemoryExtensions
+ {
+ /// <summary>
+ /// Removes all leading and trailing white-space characters from the span.
+ /// </summary>
+ public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span)
+ {
+ return span.TrimStart().TrimEnd();
+ }
+
+ /// <summary>
+ /// Removes all leading white-space characters from the span.
+ /// </summary>
+ public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span)
+ {
+ int start = 0;
+ for (; start < span.Length; start++)
+ {
+ if (!char.IsWhiteSpace(span[start]))
+ break;
+ }
+ return span.Slice(start);
+ }
+
+ /// <summary>
+ /// Removes all trailing white-space characters from the span.
+ /// </summary>
+ public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span)
+ {
+ int end = span.Length - 1;
+ for (; end >= 0; end--)
+ {
+ if (!char.IsWhiteSpace(span[end]))
+ break;
+ }
+ return span.Slice(0, end + 1);
+ }
+
+ /// <summary>
+ /// Removes all leading and trailing occurrences of a specified character.
+ /// </summary>
+ /// <param name="span">The source span from which the character is removed.</param>
+ /// <param name="trimChar">The specified character to look for and remove.</param>
+ public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, char trimChar)
+ {
+ return span.TrimStart(trimChar).TrimEnd(trimChar);
+ }
+
+ /// <summary>
+ /// Removes all leading occurrences of a specified character.
+ /// </summary>
+ /// <param name="span">The source span from which the character is removed.</param>
+ /// <param name="trimChar">The specified character to look for and remove.</param>
+ public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, char trimChar)
+ {
+ int start = 0;
+ for (; start < span.Length; start++)
+ {
+ if (span[start] != trimChar)
+ break;
+ }
+ return span.Slice(start);
+ }
+
+ /// <summary>
+ /// Removes all trailing occurrences of a specified character.
+ /// </summary>
+ /// <param name="span">The source span from which the character is removed.</param>
+ /// <param name="trimChar">The specified character to look for and remove.</param>
+ public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, char trimChar)
+ {
+ int end = span.Length - 1;
+ for (; end >= 0; end--)
+ {
+ if (span[end] != trimChar)
+ break;
+ }
+ return span.Slice(0, end + 1);
+ }
+
+ /// <summary>
+ /// Removes all leading and trailing occurrences of a set of characters specified
+ /// in a readonly span from the span.
+ /// </summary>
+ /// <param name="span">The source span from which the characters are removed.</param>
+ /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+ /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
+ public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+ {
+ return span.TrimStart(trimChars).TrimEnd(trimChars);
+ }
+
+ /// <summary>
+ /// Removes all leading occurrences of a set of characters specified
+ /// in a readonly span from the span.
+ /// </summary>
+ /// <param name="span">The source span from which the characters are removed.</param>
+ /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+ /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
+ public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+ {
+ if (trimChars.IsEmpty)
+ {
+ return span.TrimStart();
+ }
+
+ int start = 0;
+ for (; start < span.Length; start++)
+ {
+ for (int i = 0; i < trimChars.Length; i++)
+ {
+ if (span[start] == trimChars[i])
+ goto Next;
+ }
+ break;
+ Next:
+ ;
+ }
+ return span.Slice(start);
+ }
+
+ /// <summary>
+ /// Removes all trailing occurrences of a set of characters specified
+ /// in a readonly span from the span.
+ /// </summary>
+ /// <param name="span">The source span from which the characters are removed.</param>
+ /// <param name="trimChars">The span which contains the set of characters to remove.</param>
+ /// <remarks>If <paramref name="trimChars"/> is empty, white-space characters are removed instead.</remarks>
+ public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
+ {
+ if (trimChars.IsEmpty)
+ {
+ return span.TrimEnd();
+ }
+
+ int end = span.Length - 1;
+ for (; end >= 0; end--)
+ {
+ for (int i = 0; i < trimChars.Length; i++)
+ {
+ if (span[end] == trimChars[i])
+ goto Next;
+ }
+ break;
+ Next:
+ ;
+ }
+ return span.Slice(0, end + 1);
+ }
+
+ /// <summary>
+ /// Indicates whether the specified span contains only white-space characters.
+ /// </summary>
+ public static bool IsWhiteSpace(this ReadOnlySpan<char> span)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ if (!char.IsWhiteSpace(span[i]))
+ return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The value to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf<T>(this Span<T> span, T value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value),
+ span.Length);
+ if (typeof(T) == typeof(char))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, char>(ref value),
+ span.Length);
+
+ return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), value, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The sequence to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf<T>(this Span<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ value.Length);
+
+ return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The value to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOf<T>(this Span<T> span, T value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value),
+ span.Length);
+ if (typeof(T) == typeof(char))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, char>(ref value),
+ span.Length);
+
+ return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), value, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The sequence to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOf<T>(this Span<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ value.Length);
+
+ return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
+ }
+
+ /// <summary>
+ /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool SequenceEqual<T>(this Span<T> span, ReadOnlySpan<T> other)
+ where T : IEquatable<T>
+ {
+ int length = span.Length;
+
+ if (default(T) != null && IsTypeComparableAsBytes<T>(out nuint size))
+ return length == other.Length &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(other)),
+ ((nuint)length) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
+
+ return length == other.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(other), length);
+ }
+
+ /// <summary>
+ /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T).
+ /// </summary>
+ public static int SequenceCompareTo<T>(this Span<T> span, ReadOnlySpan<T> other)
+ where T : IComparable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.SequenceCompareTo(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(other)),
+ other.Length);
+
+ if (typeof(T) == typeof(char))
+ return SpanHelpers.SequenceCompareTo(
+ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(other)),
+ other.Length);
+
+ return SpanHelpers.SequenceCompareTo(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(other), other.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The value to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf<T>(this ReadOnlySpan<T> span, T value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value),
+ span.Length);
+ if (typeof(T) == typeof(char))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, char>(ref value),
+ span.Length);
+
+ return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), value, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The sequence to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOf<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ value.Length);
+
+ return SpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The value to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOf<T>(this ReadOnlySpan<T> span, T value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value),
+ span.Length);
+ if (typeof(T) == typeof(char))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, char>(ref value),
+ span.Length);
+
+ return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), value, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value">The sequence to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOf<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOf(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ value.Length);
+
+ return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this Span<T> span, T value0, T value1)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ span.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ /// <param name="value2">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this Span<T> span, T value0, T value1, T value2)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ Unsafe.As<T, byte>(ref value2),
+ span.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="values">The set of values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this Span<T> span, ReadOnlySpan<T> values)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)),
+ values.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ span.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ /// <param name="value2">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1, T value2)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ Unsafe.As<T, byte>(ref value2),
+ span.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="values">The set of values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int IndexOfAny<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> values)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.IndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)),
+ values.Length);
+
+ return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this Span<T> span, T value0, T value1)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ span.Length);
+
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ /// <param name="value2">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this Span<T> span, T value0, T value1, T value2)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ Unsafe.As<T, byte>(ref value2),
+ span.Length);
+
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="values">The set of values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this Span<T> span, ReadOnlySpan<T> values)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)),
+ values.Length);
+
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ span.Length);
+
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="value0">One of the values to search for.</param>
+ /// <param name="value1">One of the values to search for.</param>
+ /// <param name="value2">One of the values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1, T value2)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ Unsafe.As<T, byte>(ref value0),
+ Unsafe.As<T, byte>(ref value1),
+ Unsafe.As<T, byte>(ref value2),
+ span.Length);
+
+ return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length);
+ }
+
+ /// <summary>
+ /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
+ /// </summary>
+ /// <param name="span">The span to search.</param>
+ /// <param name="values">The set of values to search for.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> values)
+ where T : IEquatable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.LastIndexOfAny(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)),
+ values.Length);
+
+ return SpanHelpers.LastIndexOfAny<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(values), values.Length);
+ }
+
+ /// <summary>
+ /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool SequenceEqual<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other)
+ where T : IEquatable<T>
+ {
+ int length = span.Length;
+ if (default(T) != null && IsTypeComparableAsBytes<T>(out nuint size))
+ return length == other.Length &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(other)),
+ ((nuint)length) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
+
+ return length == other.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(other), length);
+ }
+
+ /// <summary>
+ /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T).
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int SequenceCompareTo<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other)
+ where T : IComparable<T>
+ {
+ if (typeof(T) == typeof(byte))
+ return SpanHelpers.SequenceCompareTo(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(other)),
+ other.Length);
+
+ if (typeof(T) == typeof(char))
+ return SpanHelpers.SequenceCompareTo(
+ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
+ span.Length,
+ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(other)),
+ other.Length);
+
+ return SpanHelpers.SequenceCompareTo(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(other), other.Length);
+ }
+
+ /// <summary>
+ /// Determines whether the specified sequence appears at the start of the span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool StartsWith<T>(this Span<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ int valueLength = value.Length;
+ if (default(T) != null && IsTypeComparableAsBytes<T>(out nuint size))
+ return valueLength <= span.Length &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ ((nuint)valueLength) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
+
+ return valueLength <= span.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), valueLength);
+ }
+
+ /// <summary>
+ /// Determines whether the specified sequence appears at the start of the span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool StartsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ int valueLength = value.Length;
+ if (default(T) != null && IsTypeComparableAsBytes<T>(out nuint size))
+ return valueLength <= span.Length &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ ((nuint)valueLength) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
+
+ return valueLength <= span.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), valueLength);
+ }
+
+ /// <summary>
+ /// Determines whether the specified sequence appears at the end of the span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool EndsWith<T>(this Span<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ int spanLength = span.Length;
+ int valueLength = value.Length;
+ if (default(T) != null && IsTypeComparableAsBytes<T>(out nuint size))
+ return valueLength <= spanLength &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref Unsafe.Add(ref MemoryMarshal.GetReference(span), spanLength - valueLength)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ ((nuint)valueLength) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
+
+ return valueLength <= spanLength &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.Add(ref MemoryMarshal.GetReference(span), spanLength - valueLength),
+ ref MemoryMarshal.GetReference(value),
+ valueLength);
+ }
+
+ /// <summary>
+ /// Determines whether the specified sequence appears at the end of the span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool EndsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
+ where T : IEquatable<T>
+ {
+ int spanLength = span.Length;
+ int valueLength = value.Length;
+ if (default(T) != null && IsTypeComparableAsBytes<T>(out nuint size))
+ return valueLength <= spanLength &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<T, byte>(ref Unsafe.Add(ref MemoryMarshal.GetReference(span), spanLength - valueLength)),
+ ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
+ ((nuint)valueLength) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
+
+ return valueLength <= spanLength &&
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.Add(ref MemoryMarshal.GetReference(span), spanLength - valueLength),
+ ref MemoryMarshal.GetReference(value),
+ valueLength);
+ }
+
+ /// <summary>
+ /// Reverses the sequence of the elements in the entire span.
+ /// </summary>
+ public static void Reverse<T>(this Span<T> span)
+ {
+ if (span.Length <= 1)
+ {
+ return;
+ }
+
+ ref T first = ref MemoryMarshal.GetReference(span);
+ ref T last = ref Unsafe.Add(ref Unsafe.Add(ref first, span.Length), -1);
+ do
+ {
+ T temp = first;
+ first = last;
+ last = temp;
+ first = ref Unsafe.Add(ref first, 1);
+ last = ref Unsafe.Add(ref last, -1);
+ } while (Unsafe.IsAddressLessThan(ref first, ref last));
+ }
+
+ /// <summary>
+ /// Creates a new span over the target array.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this T[] array)
+ {
+ return new Span<T>(array);
+ }
+
+ /// <summary>
+ /// Creates a new Span over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <param name="start">The index at which to begin the Span.</param>
+ /// <param name="length">The number of items in the Span.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this T[] array, int start, int length)
+ {
+ return new Span<T>(array, start, length);
+ }
+
+ /// <summary>
+ /// Creates a new span over the portion of the target array segment.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this ArraySegment<T> segment)
+ {
+ return new Span<T>(segment.Array, segment.Offset, segment.Count);
+ }
+
+ /// <summary>
+ /// Creates a new Span over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="segment">The target array.</param>
+ /// <param name="start">The index at which to begin the Span.</param>
+ /// <remarks>Returns default when <paramref name="segment"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="segment"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=segment.Count).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this ArraySegment<T> segment, int start)
+ {
+ if (((uint)start) > segment.Count)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new Span<T>(segment.Array, segment.Offset + start, segment.Count - start);
+ }
+
+ /// <summary>
+ /// Creates a new Span over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="segment">The target array.</param>
+ /// <param name="start">The index at which to begin the Span.</param>
+ /// <param name="length">The number of items in the Span.</param>
+ /// <remarks>Returns default when <paramref name="segment"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="segment"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=segment.Count).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> AsSpan<T>(this ArraySegment<T> segment, int start, int length)
+ {
+ if (((uint)start) > segment.Count)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ if (((uint)length) > segment.Count - start)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
+
+ return new Span<T>(segment.Array, segment.Offset + start, length);
+ }
+
+ /// <summary>
+ /// Creates a new memory over the target array.
+ /// </summary>
+ public static Memory<T> AsMemory<T>(this T[] array) => new Memory<T>(array);
+
+ /// <summary>
+ /// Creates a new memory over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=array.Length).
+ /// </exception>
+ public static Memory<T> AsMemory<T>(this T[] array, int start) => new Memory<T>(array, start);
+
+ /// <summary>
+ /// Creates a new memory over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <param name="length">The number of items in the memory.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
+ /// </exception>
+ public static Memory<T> AsMemory<T>(this T[] array, int start, int length) => new Memory<T>(array, start, length);
+
+ /// <summary>
+ /// Creates a new memory over the portion of the target array.
+ /// </summary>
+ public static Memory<T> AsMemory<T>(this ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count);
+
+ /// <summary>
+ /// Creates a new memory over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="segment">The target array.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <remarks>Returns default when <paramref name="segment"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="segment"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=segment.Count).
+ /// </exception>
+ public static Memory<T> AsMemory<T>(this ArraySegment<T> segment, int start)
+ {
+ if (((uint)start) > segment.Count)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ return new Memory<T>(segment.Array, segment.Offset + start, segment.Count - start);
+ }
+
+ /// <summary>
+ /// Creates a new memory over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="segment">The target array.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <param name="length">The number of items in the memory.</param>
+ /// <remarks>Returns default when <paramref name="segment"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="segment"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=segment.Count).
+ /// </exception>
+ public static Memory<T> AsMemory<T>(this ArraySegment<T> segment, int start, int length)
+ {
+ if (((uint)start) > segment.Count)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ if (((uint)length) > segment.Count - start)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
+
+ return new Memory<T>(segment.Array, segment.Offset + start, length);
+ }
+
+ /// <summary>
+ /// Copies the contents of the array into the span. If the source
+ /// and destinations overlap, this method behaves as if the original values in
+ /// a temporary location before the destination is overwritten.
+ ///
+ ///<param name="source">The array to copy items from.</param>
+ /// <param name="destination">The span to copy items into.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when the destination Span is shorter than the source array.
+ /// </exception>
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyTo<T>(this T[] source, Span<T> destination)
+ {
+ new ReadOnlySpan<T>(source).CopyTo(destination);
+ }
+
+ /// <summary>
+ /// Copies the contents of the array into the memory. If the source
+ /// and destinations overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.
+ ///
+ ///<param name="source">The array to copy items from.</param>
+ /// <param name="destination">The memory to copy items into.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when the destination is shorter than the source array.
+ /// </exception>
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyTo<T>(this T[] source, Memory<T> destination)
+ {
+ source.CopyTo(destination.Span);
+ }
+
+ //
+ // Overlaps
+ // ========
+ //
+ // The following methods can be used to determine if two sequences
+ // overlap in memory.
+ //
+ // Two sequences overlap if they have positions in common and neither
+ // is empty. Empty sequences do not overlap with any other sequence.
+ //
+ // If two sequences overlap, the element offset is the number of
+ // elements by which the second sequence is offset from the first
+ // sequence (i.e., second minus first). An exception is thrown if the
+ // number is not a whole number, which can happen when a sequence of a
+ // smaller type is cast to a sequence of a larger type with unsafe code
+ // or NonPortableCast. If the sequences do not overlap, the offset is
+ // meaningless and arbitrarily set to zero.
+ //
+ // Implementation
+ // --------------
+ //
+ // Implementing this correctly is quite tricky due of two problems:
+ //
+ // * If the sequences refer to two different objects on the managed
+ // heap, the garbage collector can move them freely around or change
+ // their relative order in memory.
+ //
+ // * The distance between two sequences can be greater than
+ // int.MaxValue (on a 32-bit system) or long.MaxValue (on a 64-bit
+ // system).
+ //
+ // (For simplicity, the following text assumes a 32-bit system, but
+ // everything also applies to a 64-bit system if every 32 is replaced a
+ // 64.)
+ //
+ // The first problem is solved by calculating the distance with exactly
+ // one atomic operation. If the garbage collector happens to move the
+ // sequences afterwards and the sequences overlapped before, they will
+ // still overlap after the move and their distance hasn't changed. If
+ // the sequences did not overlap, the distance can change but the
+ // sequences still won't overlap.
+ //
+ // The second problem is solved by making all addresses relative to the
+ // start of the first sequence and performing all operations in
+ // unsigned integer arithmetic modulo 2³².
+ //
+ // Example
+ // -------
+ //
+ // Let's say there are two sequences, x and y. Let
+ //
+ // ref T xRef = MemoryMarshal.GetReference(x)
+ // uint xLength = x.Length * Unsafe.SizeOf<T>()
+ // ref T yRef = MemoryMarshal.GetReference(y)
+ // uint yLength = y.Length * Unsafe.SizeOf<T>()
+ //
+ // Visually, the two sequences are located somewhere in the 32-bit
+ // address space as follows:
+ //
+ // [----------------------------------------------) normal address space
+ // 0 2³²
+ // [------------------) first sequence
+ // xRef xRef + xLength
+ // [--------------------------) . second sequence
+ // yRef . yRef + yLength
+ // : . . .
+ // : . . .
+ // . . .
+ // . . .
+ // . . .
+ // [----------------------------------------------) relative address space
+ // 0 . . 2³²
+ // [------------------) : first sequence
+ // x1 . x2 :
+ // -------------) [------------- second sequence
+ // y2 y1
+ //
+ // The idea is to make all addresses relative to xRef: Let x1 be the
+ // start address of x in this relative address space, x2 the end
+ // address of x, y1 the start address of y, and y2 the end address of
+ // y:
+ //
+ // nuint x1 = 0
+ // nuint x2 = xLength
+ // nuint y1 = (nuint)Unsafe.ByteOffset(xRef, yRef)
+ // nuint y2 = y1 + yLength
+ //
+ // xRef relative to xRef is 0.
+ //
+ // x2 is simply x1 + xLength. This cannot overflow.
+ //
+ // yRef relative to xRef is (yRef - xRef). If (yRef - xRef) is
+ // negative, casting it to an unsigned 32-bit integer turns it into
+ // (yRef - xRef + 2³²). So, in the example above, y1 moves to the right
+ // of x2.
+ //
+ // y2 is simply y1 + yLength. Note that this can overflow, as in the
+ // example above, which must be avoided.
+ //
+ // The two sequences do *not* overlap if y is entirely in the space
+ // right of x in the relative address space. (It can't be left of it!)
+ //
+ // (y1 >= x2) && (y2 <= 2³²)
+ //
+ // Inversely, they do overlap if
+ //
+ // (y1 < x2) || (y2 > 2³²)
+ //
+ // After substituting x2 and y2 with their respective definition:
+ //
+ // == (y1 < xLength) || (y1 + yLength > 2³²)
+ //
+ // Since yLength can't be greater than the size of the address space,
+ // the overflow can be avoided as follows:
+ //
+ // == (y1 < xLength) || (y1 > 2³² - yLength)
+ //
+ // However, 2³² cannot be stored in an unsigned 32-bit integer, so one
+ // more change is needed to keep doing everything with unsigned 32-bit
+ // integers:
+ //
+ // == (y1 < xLength) || (y1 > -yLength)
+ //
+ // Due to modulo arithmetic, this gives exactly same result *except* if
+ // yLength is zero, since 2³² - 0 is 0 and not 2³². So the case
+ // y.IsEmpty must be handled separately first.
+ //
+
+ /// <summary>
+ /// Determines whether two sequences overlap in memory.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool Overlaps<T>(this Span<T> span, ReadOnlySpan<T> other)
+ {
+ return Overlaps((ReadOnlySpan<T>)span, other);
+ }
+
+ /// <summary>
+ /// Determines whether two sequences overlap in memory and outputs the element offset.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool Overlaps<T>(this Span<T> span, ReadOnlySpan<T> other, out int elementOffset)
+ {
+ return Overlaps((ReadOnlySpan<T>)span, other, out elementOffset);
+ }
+
+ /// <summary>
+ /// Determines whether two sequences overlap in memory.
+ /// </summary>
+ public static bool Overlaps<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other)
+ {
+ if (span.IsEmpty || other.IsEmpty)
+ {
+ return false;
+ }
+
+ IntPtr byteOffset = Unsafe.ByteOffset(
+ ref MemoryMarshal.GetReference(span),
+ ref MemoryMarshal.GetReference(other));
+
+ if (Unsafe.SizeOf<IntPtr>() == sizeof(int))
+ {
+ return (uint)byteOffset < (uint)(span.Length * Unsafe.SizeOf<T>()) ||
+ (uint)byteOffset > (uint)-(other.Length * Unsafe.SizeOf<T>());
+ }
+ else
+ {
+ return (ulong)byteOffset < (ulong)((long)span.Length * Unsafe.SizeOf<T>()) ||
+ (ulong)byteOffset > (ulong)-((long)other.Length * Unsafe.SizeOf<T>());
+ }
+ }
+
+ /// <summary>
+ /// Determines whether two sequences overlap in memory and outputs the element offset.
+ /// </summary>
+ public static bool Overlaps<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other, out int elementOffset)
+ {
+ if (span.IsEmpty || other.IsEmpty)
+ {
+ elementOffset = 0;
+ return false;
+ }
+
+ IntPtr byteOffset = Unsafe.ByteOffset(
+ ref MemoryMarshal.GetReference(span),
+ ref MemoryMarshal.GetReference(other));
+
+ if (Unsafe.SizeOf<IntPtr>() == sizeof(int))
+ {
+ if ((uint)byteOffset < (uint)(span.Length * Unsafe.SizeOf<T>()) ||
+ (uint)byteOffset > (uint)-(other.Length * Unsafe.SizeOf<T>()))
+ {
+ if ((int)byteOffset % Unsafe.SizeOf<T>() != 0)
+ ThrowHelper.ThrowArgumentException_OverlapAlignmentMismatch();
+
+ elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
+ return true;
+ }
+ else
+ {
+ elementOffset = 0;
+ return false;
+ }
+ }
+ else
+ {
+ if ((ulong)byteOffset < (ulong)((long)span.Length * Unsafe.SizeOf<T>()) ||
+ (ulong)byteOffset > (ulong)-((long)other.Length * Unsafe.SizeOf<T>()))
+ {
+ if ((long)byteOffset % Unsafe.SizeOf<T>() != 0)
+ ThrowHelper.ThrowArgumentException_OverlapAlignmentMismatch();
+
+ elementOffset = (int)((long)byteOffset / Unsafe.SizeOf<T>());
+ return true;
+ }
+ else
+ {
+ elementOffset = 0;
+ return false;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="Span{T}"/> for a value
+ /// using the specified <see cref="IComparable{T}"/> generic interface.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
+ /// <param name="comparable">The <see cref="IComparable{T}"/> to use when comparing.</param>
+ /// <returns>
+ /// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparable" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T>(
+ this Span<T> span, IComparable<T> comparable)
+ {
+ return BinarySearch<T, IComparable<T>>(span, comparable);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="Span{T}"/> for a value
+ /// using the specified <typeparamref name="TComparable"/> generic type.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <typeparam name="TComparable">The specific type of <see cref="IComparable{T}"/>.</typeparam>
+ /// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
+ /// <param name="comparable">The <typeparamref name="TComparable"/> to use when comparing.</param>
+ /// <returns>
+ /// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparable" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparable>(
+ this Span<T> span, TComparable comparable)
+ where TComparable : IComparable<T>
+ {
+ return BinarySearch((ReadOnlySpan<T>)span, comparable);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="Span{T}"/> for the specified <paramref name="value"/>
+ /// using the specified <typeparamref name="TComparer"/> generic type.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <typeparam name="TComparer">The specific type of <see cref="IComparer{T}"/>.</typeparam>
+ /// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
+ /// <param name="value">The object to locate. The value can be null for reference types.</param>
+ /// <param name="comparer">The <typeparamref name="TComparer"/> to use when comparing.</param>
+ /// /// <returns>
+ /// The zero-based index of <paramref name="value"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="value"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="value"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparer" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparer>(
+ this Span<T> span, T value, TComparer comparer)
+ where TComparer : IComparer<T>
+ {
+ return BinarySearch((ReadOnlySpan<T>)span, value, comparer);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for a value
+ /// using the specified <see cref="IComparable{T}"/> generic interface.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
+ /// <param name="comparable">The <see cref="IComparable{T}"/> to use when comparing.</param>
+ /// <returns>
+ /// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparable" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T>(
+ this ReadOnlySpan<T> span, IComparable<T> comparable)
+ {
+ return BinarySearch<T, IComparable<T>>(span, comparable);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for a value
+ /// using the specified <typeparamref name="TComparable"/> generic type.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <typeparam name="TComparable">The specific type of <see cref="IComparable{T}"/>.</typeparam>
+ /// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
+ /// <param name="comparable">The <typeparamref name="TComparable"/> to use when comparing.</param>
+ /// <returns>
+ /// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparable" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparable>(
+ this ReadOnlySpan<T> span, TComparable comparable)
+ where TComparable : IComparable<T>
+ {
+ return SpanHelpers.BinarySearch(span, comparable);
+ }
+
+ /// <summary>
+ /// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for the specified <paramref name="value"/>
+ /// using the specified <typeparamref name="TComparer"/> generic type.
+ /// </summary>
+ /// <typeparam name="T">The element type of the span.</typeparam>
+ /// <typeparam name="TComparer">The specific type of <see cref="IComparer{T}"/>.</typeparam>
+ /// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
+ /// <param name="value">The object to locate. The value can be null for reference types.</param>
+ /// <param name="comparer">The <typeparamref name="TComparer"/> to use when comparing.</param>
+ /// /// <returns>
+ /// The zero-based index of <paramref name="value"/> in the sorted <paramref name="span"/>,
+ /// if <paramref name="value"/> is found; otherwise, a negative number that is the bitwise complement
+ /// of the index of the next element that is larger than <paramref name="value"/> or, if there is
+ /// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// <paramref name = "comparer" /> is <see langword="null"/> .
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparer>(
+ this ReadOnlySpan<T> span, T value, TComparer comparer)
+ where TComparer : IComparer<T>
+ {
+ if (comparer == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparer);
+
+ var comparable = new SpanHelpers.ComparerComparable<T, TComparer>(
+ value, comparer);
+ return BinarySearch(span, comparable);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsTypeComparableAsBytes<T>(out nuint size)
+ {
+ if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte))
+ {
+ size = (nuint)sizeof(byte);
+ return true;
+ }
+
+ if (typeof(T) == typeof(char) || typeof(T) == typeof(short) || typeof(T) == typeof(ushort))
+ {
+ size = (nuint)sizeof(char);
+ return true;
+ }
+
+ if (typeof(T) == typeof(int) || typeof(T) == typeof(uint))
+ {
+ size = (nuint)sizeof(int);
+ return true;
+ }
+
+ if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong))
+ {
+ size = (nuint)sizeof(long);
+ return true;
+ }
+
+ size = default;
+ return false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MethodAccessException.cs b/src/System.Private.CoreLib/shared/System/MethodAccessException.cs
new file mode 100644
index 0000000000..1ca0297b94
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MethodAccessException.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+** Purpose: The exception class for class loading failures.
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class MethodAccessException : MemberAccessException
+ {
+ public MethodAccessException()
+ : base(SR.Arg_MethodAccessException)
+ {
+ HResult = HResults.COR_E_METHODACCESS;
+ }
+
+ public MethodAccessException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_METHODACCESS;
+ }
+
+ public MethodAccessException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_METHODACCESS;
+ }
+
+ protected MethodAccessException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MidpointRounding.cs b/src/System.Private.CoreLib/shared/System/MidpointRounding.cs
new file mode 100644
index 0000000000..a75de43e65
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MidpointRounding.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public enum MidpointRounding
+ {
+ ToEven = 0,
+ AwayFromZero = 1,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MissingMethodException.cs b/src/System.Private.CoreLib/shared/System/MissingMethodException.cs
new file mode 100644
index 0000000000..abb6c0e97b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MissingMethodException.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: The exception class for class loading failures.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class MissingMethodException : MissingMemberException
+ {
+ public MissingMethodException()
+ : base(SR.Arg_MissingMethodException)
+ {
+ HResult = HResults.COR_E_MISSINGMETHOD;
+ }
+
+ public MissingMethodException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_MISSINGMETHOD;
+ }
+
+ public MissingMethodException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_MISSINGMETHOD;
+ }
+
+ public MissingMethodException(string className, string methodName)
+ {
+ ClassName = className;
+ MemberName = methodName;
+ }
+
+ protected MissingMethodException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+
+ public override string Message
+ {
+ get
+ {
+ return ClassName == null ? base.Message :
+ SR.Format(SR.MissingMethod_Name, ClassName + "." + MemberName +
+ (Signature != null ? " " + FormatSignature(Signature) : string.Empty));
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/MulticastNotSupportedException.cs b/src/System.Private.CoreLib/shared/System/MulticastNotSupportedException.cs
new file mode 100644
index 0000000000..cc6c77023e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/MulticastNotSupportedException.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////////
+// MulticastNotSupportedException
+// This is thrown when you add multiple callbacks to a non-multicast delegate.
+////////////////////////////////////////////////////////////////////////////////
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class MulticastNotSupportedException : SystemException
+ {
+ public MulticastNotSupportedException()
+ : base(SR.Arg_MulticastNotSupportedException)
+ {
+ HResult = HResults.COR_E_MULTICASTNOTSUPPORTED;
+ }
+
+ public MulticastNotSupportedException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_MULTICASTNOTSUPPORTED;
+ }
+
+ public MulticastNotSupportedException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_MULTICASTNOTSUPPORTED;
+ }
+
+ internal MulticastNotSupportedException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/NonSerializedAttribute.cs b/src/System.Private.CoreLib/shared/System/NonSerializedAttribute.cs
new file mode 100644
index 0000000000..cabd5a2aa2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/NonSerializedAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class NonSerializedAttribute : Attribute
+ {
+ public NonSerializedAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/NotFiniteNumberException.cs b/src/System.Private.CoreLib/shared/System/NotFiniteNumberException.cs
new file mode 100644
index 0000000000..b9c9af06d3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/NotFiniteNumberException.cs
@@ -0,0 +1,72 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class NotFiniteNumberException : ArithmeticException
+ {
+ private double _offendingNumber;
+
+ public NotFiniteNumberException()
+ : base(SR.Arg_NotFiniteNumberException)
+ {
+ _offendingNumber = 0;
+ HResult = HResults.COR_E_NOTFINITENUMBER;
+ }
+
+ public NotFiniteNumberException(double offendingNumber)
+ : base()
+ {
+ _offendingNumber = offendingNumber;
+ HResult = HResults.COR_E_NOTFINITENUMBER;
+ }
+
+ public NotFiniteNumberException(string message)
+ : base(message)
+ {
+ _offendingNumber = 0;
+ HResult = HResults.COR_E_NOTFINITENUMBER;
+ }
+
+ public NotFiniteNumberException(string message, double offendingNumber)
+ : base(message)
+ {
+ _offendingNumber = offendingNumber;
+ HResult = HResults.COR_E_NOTFINITENUMBER;
+ }
+
+ public NotFiniteNumberException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_NOTFINITENUMBER;
+ }
+
+ public NotFiniteNumberException(string message, double offendingNumber, Exception innerException)
+ : base(message, innerException)
+ {
+ _offendingNumber = offendingNumber;
+ HResult = HResults.COR_E_NOTFINITENUMBER;
+ }
+
+ protected NotFiniteNumberException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ _offendingNumber = info.GetInt32("OffendingNumber");
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("OffendingNumber", _offendingNumber, typeof(int));
+ }
+
+ public double OffendingNumber
+ {
+ get { return _offendingNumber; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/NotImplementedException.cs b/src/System.Private.CoreLib/shared/System/NotImplementedException.cs
new file mode 100644
index 0000000000..1a3b6afcd4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/NotImplementedException.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception thrown when a requested method or operation is not
+** implemented.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class NotImplementedException : SystemException
+ {
+ public NotImplementedException()
+ : base(SR.Arg_NotImplementedException)
+ {
+ HResult = HResults.E_NOTIMPL;
+ }
+ public NotImplementedException(String message)
+ : base(message)
+ {
+ HResult = HResults.E_NOTIMPL;
+ }
+ public NotImplementedException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.E_NOTIMPL;
+ }
+
+ protected NotImplementedException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/NotSupportedException.cs b/src/System.Private.CoreLib/shared/System/NotSupportedException.cs
new file mode 100644
index 0000000000..3180bc2837
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/NotSupportedException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: For methods that should be implemented on subclasses.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class NotSupportedException : SystemException
+ {
+ public NotSupportedException()
+ : base(SR.Arg_NotSupportedException)
+ {
+ HResult = HResults.COR_E_NOTSUPPORTED;
+ }
+
+ public NotSupportedException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_NOTSUPPORTED;
+ }
+
+ public NotSupportedException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_NOTSUPPORTED;
+ }
+
+ protected NotSupportedException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/NullReferenceException.cs b/src/System.Private.CoreLib/shared/System/NullReferenceException.cs
new file mode 100644
index 0000000000..c2e722470c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/NullReferenceException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for dereferencing a null reference.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class NullReferenceException : SystemException
+ {
+ public NullReferenceException()
+ : base(SR.Arg_NullReferenceException)
+ {
+ HResult = HResults.COR_E_NULLREFERENCE;
+ }
+
+ public NullReferenceException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_NULLREFERENCE;
+ }
+
+ public NullReferenceException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_NULLREFERENCE;
+ }
+
+ protected NullReferenceException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Nullable.cs b/src/System.Private.CoreLib/shared/System/Nullable.cs
new file mode 100644
index 0000000000..73ad6056c2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Nullable.cs
@@ -0,0 +1,152 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ // Because we have special type system support that says a a boxed Nullable<T>
+ // can be used where a boxed<T> is use, Nullable<T> can not implement any intefaces
+ // at all (since T may not). Do NOT add any interfaces to Nullable!
+ //
+ [Serializable]
+ [NonVersionable] // This only applies to field layout
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Nullable<T> where T : struct
+ {
+ private readonly bool hasValue; // Do not rename (binary serialization)
+ internal T value; // Do not rename (binary serialization) or make readonly (can be mutated in ToString, etc.)
+
+ [NonVersionable]
+ public Nullable(T value)
+ {
+ this.value = value;
+ hasValue = true;
+ }
+
+ public bool HasValue
+ {
+ [NonVersionable]
+ get
+ {
+ return hasValue;
+ }
+ }
+
+ public T Value
+ {
+ get
+ {
+ if (!hasValue)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_NoValue();
+ }
+ return value;
+ }
+ }
+
+ [NonVersionable]
+ public T GetValueOrDefault()
+ {
+ return value;
+ }
+
+ [NonVersionable]
+ public T GetValueOrDefault(T defaultValue)
+ {
+ return hasValue ? value : defaultValue;
+ }
+
+ public override bool Equals(object other)
+ {
+ if (!hasValue) return other == null;
+ if (other == null) return false;
+ return value.Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return hasValue ? value.GetHashCode() : 0;
+ }
+
+ public override string ToString()
+ {
+ return hasValue ? value.ToString() : "";
+ }
+
+ [NonVersionable]
+ public static implicit operator Nullable<T>(T value)
+ {
+ return new Nullable<T>(value);
+ }
+
+ [NonVersionable]
+ public static explicit operator T(Nullable<T> value)
+ {
+ return value.Value;
+ }
+ }
+
+ public static class Nullable
+ {
+ public static int Compare<T>(Nullable<T> n1, Nullable<T> n2) where T : struct
+ {
+ if (n1.HasValue)
+ {
+ if (n2.HasValue) return Comparer<T>.Default.Compare(n1.value, n2.value);
+ return 1;
+ }
+ if (n2.HasValue) return -1;
+ return 0;
+ }
+
+ public static bool Equals<T>(Nullable<T> n1, Nullable<T> n2) where T : struct
+ {
+ if (n1.HasValue)
+ {
+ if (n2.HasValue) return EqualityComparer<T>.Default.Equals(n1.value, n2.value);
+ return false;
+ }
+ if (n2.HasValue) return false;
+ return true;
+ }
+
+ // If the type provided is not a Nullable Type, return null.
+ // Otherwise, returns the underlying type of the Nullable type
+ public static Type GetUnderlyingType(Type nullableType)
+ {
+ if ((object)nullableType == null)
+ {
+ throw new ArgumentNullException(nameof(nullableType));
+ }
+
+#if CORERT
+ // This is necessary to handle types without reflection metadata
+ if (nullableType.TryGetEEType(out EETypePtr nullableEEType))
+ {
+ if (nullableEEType.IsGeneric)
+ {
+ if (nullableEEType.IsNullable)
+ {
+ return Internal.Reflection.Core.NonPortable.RuntimeTypeUnifier.GetRuntimeTypeForEEType(nullableEEType.NullableType);
+ }
+ }
+ return null;
+ }
+#endif
+
+ if (nullableType.IsGenericType && !nullableType.IsGenericTypeDefinition)
+ {
+ // instantiated generic type only
+ Type genericType = nullableType.GetGenericTypeDefinition();
+ if (Object.ReferenceEquals(genericType, typeof(Nullable<>)))
+ {
+ return nullableType.GetGenericArguments()[0];
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs
new file mode 100644
index 0000000000..74395ab2f5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs
@@ -0,0 +1,2291 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers.Text;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System
+{
+ // The Format methods provided by the numeric classes convert
+ // the numeric value to a string using the format string given by the
+ // format parameter. If the format parameter is null or
+ // an empty string, the number is formatted as if the string "G" (general
+ // format) was specified. The info parameter specifies the
+ // NumberFormatInfo instance to use when formatting the number. If the
+ // info parameter is null or omitted, the numeric formatting information
+ // is obtained from the current culture. The NumberFormatInfo supplies
+ // such information as the characters to use for decimal and thousand
+ // separators, and the spelling and placement of currency symbols in monetary
+ // values.
+ //
+ // Format strings fall into two categories: Standard format strings and
+ // user-defined format strings. A format string consisting of a single
+ // alphabetic character (A-Z or a-z), optionally followed by a sequence of
+ // digits (0-9), is a standard format string. All other format strings are
+ // used-defined format strings.
+ //
+ // A standard format string takes the form Axx, where A is an
+ // alphabetic character called the format specifier and xx is a
+ // sequence of digits called the precision specifier. The format
+ // specifier controls the type of formatting applied to the number and the
+ // precision specifier controls the number of significant digits or decimal
+ // places of the formatting operation. The following table describes the
+ // supported standard formats.
+ //
+ // C c - Currency format. The number is
+ // converted to a string that represents a currency amount. The conversion is
+ // controlled by the currency format information of the NumberFormatInfo
+ // used to format the number. The precision specifier indicates the desired
+ // number of decimal places. If the precision specifier is omitted, the default
+ // currency precision given by the NumberFormatInfo is used.
+ //
+ // D d - Decimal format. This format is
+ // supported for integral types only. The number is converted to a string of
+ // decimal digits, prefixed by a minus sign if the number is negative. The
+ // precision specifier indicates the minimum number of digits desired in the
+ // resulting string. If required, the number will be left-padded with zeros to
+ // produce the number of digits given by the precision specifier.
+ //
+ // E e Engineering (scientific) format.
+ // The number is converted to a string of the form
+ // "-d.ddd...E+ddd" or "-d.ddd...e+ddd", where each
+ // 'd' indicates a digit (0-9). The string starts with a minus sign if the
+ // number is negative, and one digit always precedes the decimal point. The
+ // precision specifier indicates the desired number of digits after the decimal
+ // point. If the precision specifier is omitted, a default of 6 digits after
+ // the decimal point is used. The format specifier indicates whether to prefix
+ // the exponent with an 'E' or an 'e'. The exponent is always consists of a
+ // plus or minus sign and three digits.
+ //
+ // F f Fixed point format. The number is
+ // converted to a string of the form "-ddd.ddd....", where each
+ // 'd' indicates a digit (0-9). The string starts with a minus sign if the
+ // number is negative. The precision specifier indicates the desired number of
+ // decimal places. If the precision specifier is omitted, the default numeric
+ // precision given by the NumberFormatInfo is used.
+ //
+ // G g - General format. The number is
+ // converted to the shortest possible decimal representation using fixed point
+ // or scientific format. The precision specifier determines the number of
+ // significant digits in the resulting string. If the precision specifier is
+ // omitted, the number of significant digits is determined by the type of the
+ // number being converted (10 for int, 19 for long, 7 for
+ // float, 15 for double, 19 for Currency, and 29 for
+ // Decimal). Trailing zeros after the decimal point are removed, and the
+ // resulting string contains a decimal point only if required. The resulting
+ // string uses fixed point format if the exponent of the number is less than
+ // the number of significant digits and greater than or equal to -4. Otherwise,
+ // the resulting string uses scientific format, and the case of the format
+ // specifier controls whether the exponent is prefixed with an 'E' or an 'e'.
+ //
+ // N n Number format. The number is
+ // converted to a string of the form "-d,ddd,ddd.ddd....", where
+ // each 'd' indicates a digit (0-9). The string starts with a minus sign if the
+ // number is negative. Thousand separators are inserted between each group of
+ // three digits to the left of the decimal point. The precision specifier
+ // indicates the desired number of decimal places. If the precision specifier
+ // is omitted, the default numeric precision given by the
+ // NumberFormatInfo is used.
+ //
+ // X x - Hexadecimal format. This format is
+ // supported for integral types only. The number is converted to a string of
+ // hexadecimal digits. The format specifier indicates whether to use upper or
+ // lower case characters for the hexadecimal digits above 9 ('X' for 'ABCDEF',
+ // and 'x' for 'abcdef'). The precision specifier indicates the minimum number
+ // of digits desired in the resulting string. If required, the number will be
+ // left-padded with zeros to produce the number of digits given by the
+ // precision specifier.
+ //
+ // Some examples of standard format strings and their results are shown in the
+ // table below. (The examples all assume a default NumberFormatInfo.)
+ //
+ // Value Format Result
+ // 12345.6789 C $12,345.68
+ // -12345.6789 C ($12,345.68)
+ // 12345 D 12345
+ // 12345 D8 00012345
+ // 12345.6789 E 1.234568E+004
+ // 12345.6789 E10 1.2345678900E+004
+ // 12345.6789 e4 1.2346e+004
+ // 12345.6789 F 12345.68
+ // 12345.6789 F0 12346
+ // 12345.6789 F6 12345.678900
+ // 12345.6789 G 12345.6789
+ // 12345.6789 G7 12345.68
+ // 123456789 G7 1.234568E8
+ // 12345.6789 N 12,345.68
+ // 123456789 N4 123,456,789.0000
+ // 0x2c45e x 2c45e
+ // 0x2c45e X 2C45E
+ // 0x2c45e X8 0002C45E
+ //
+ // Format strings that do not start with an alphabetic character, or that start
+ // with an alphabetic character followed by a non-digit, are called
+ // user-defined format strings. The following table describes the formatting
+ // characters that are supported in user defined format strings.
+ //
+ //
+ // 0 - Digit placeholder. If the value being
+ // formatted has a digit in the position where the '0' appears in the format
+ // string, then that digit is copied to the output string. Otherwise, a '0' is
+ // stored in that position in the output string. The position of the leftmost
+ // '0' before the decimal point and the rightmost '0' after the decimal point
+ // determines the range of digits that are always present in the output
+ // string.
+ //
+ // # - Digit placeholder. If the value being
+ // formatted has a digit in the position where the '#' appears in the format
+ // string, then that digit is copied to the output string. Otherwise, nothing
+ // is stored in that position in the output string.
+ //
+ // . - Decimal point. The first '.' character
+ // in the format string determines the location of the decimal separator in the
+ // formatted value; any additional '.' characters are ignored. The actual
+ // character used as a the decimal separator in the output string is given by
+ // the NumberFormatInfo used to format the number.
+ //
+ // , - Thousand separator and number scaling.
+ // The ',' character serves two purposes. First, if the format string contains
+ // a ',' character between two digit placeholders (0 or #) and to the left of
+ // the decimal point if one is present, then the output will have thousand
+ // separators inserted between each group of three digits to the left of the
+ // decimal separator. The actual character used as a the decimal separator in
+ // the output string is given by the NumberFormatInfo used to format the
+ // number. Second, if the format string contains one or more ',' characters
+ // immediately to the left of the decimal point, or after the last digit
+ // placeholder if there is no decimal point, then the number will be divided by
+ // 1000 times the number of ',' characters before it is formatted. For example,
+ // the format string '0,,' will represent 100 million as just 100. Use of the
+ // ',' character to indicate scaling does not also cause the formatted number
+ // to have thousand separators. Thus, to scale a number by 1 million and insert
+ // thousand separators you would use the format string '#,##0,,'.
+ //
+ // % - Percentage placeholder. The presence of
+ // a '%' character in the format string causes the number to be multiplied by
+ // 100 before it is formatted. The '%' character itself is inserted in the
+ // output string where it appears in the format string.
+ //
+ // E+ E- e+ e- - Scientific notation.
+ // If any of the strings 'E+', 'E-', 'e+', or 'e-' are present in the format
+ // string and are immediately followed by at least one '0' character, then the
+ // number is formatted using scientific notation with an 'E' or 'e' inserted
+ // between the number and the exponent. The number of '0' characters following
+ // the scientific notation indicator determines the minimum number of digits to
+ // output for the exponent. The 'E+' and 'e+' formats indicate that a sign
+ // character (plus or minus) should always precede the exponent. The 'E-' and
+ // 'e-' formats indicate that a sign character should only precede negative
+ // exponents.
+ //
+ // \ - Literal character. A backslash character
+ // causes the next character in the format string to be copied to the output
+ // string as-is. The backslash itself isn't copied, so to place a backslash
+ // character in the output string, use two backslashes (\\) in the format
+ // string.
+ //
+ // 'ABC' "ABC" - Literal string. Characters
+ // enclosed in single or double quotation marks are copied to the output string
+ // as-is and do not affect formatting.
+ //
+ // ; - Section separator. The ';' character is
+ // used to separate sections for positive, negative, and zero numbers in the
+ // format string.
+ //
+ // Other - All other characters are copied to
+ // the output string in the position they appear.
+ //
+ // For fixed point formats (formats not containing an 'E+', 'E-', 'e+', or
+ // 'e-'), the number is rounded to as many decimal places as there are digit
+ // placeholders to the right of the decimal point. If the format string does
+ // not contain a decimal point, the number is rounded to the nearest
+ // integer. If the number has more digits than there are digit placeholders to
+ // the left of the decimal point, the extra digits are copied to the output
+ // string immediately before the first digit placeholder.
+ //
+ // For scientific formats, the number is rounded to as many significant digits
+ // as there are digit placeholders in the format string.
+ //
+ // To allow for different formatting of positive, negative, and zero values, a
+ // user-defined format string may contain up to three sections separated by
+ // semicolons. The results of having one, two, or three sections in the format
+ // string are described in the table below.
+ //
+ // Sections:
+ //
+ // One - The format string applies to all values.
+ //
+ // Two - The first section applies to positive values
+ // and zeros, and the second section applies to negative values. If the number
+ // to be formatted is negative, but becomes zero after rounding according to
+ // the format in the second section, then the resulting zero is formatted
+ // according to the first section.
+ //
+ // Three - The first section applies to positive
+ // values, the second section applies to negative values, and the third section
+ // applies to zeros. The second section may be left empty (by having no
+ // characters between the semicolons), in which case the first section applies
+ // to all non-zero values. If the number to be formatted is non-zero, but
+ // becomes zero after rounding according to the format in the first or second
+ // section, then the resulting zero is formatted according to the third
+ // section.
+ //
+ // For both standard and user-defined formatting operations on values of type
+ // float and double, if the value being formatted is a NaN (Not
+ // a Number) or a positive or negative infinity, then regardless of the format
+ // string, the resulting string is given by the NaNSymbol,
+ // PositiveInfinitySymbol, or NegativeInfinitySymbol property of
+ // the NumberFormatInfo used to format the number.
+
+ internal static partial class Number
+ {
+ internal const int DecimalPrecision = 29; // Decimal.DecCalc also uses this value
+ private const int FloatPrecision = 7;
+ private const int DoublePrecision = 15;
+ private const int ScaleNAN = unchecked((int)0x80000000);
+ private const int ScaleINF = 0x7FFFFFFF;
+ private const int MaxUInt32DecDigits = 10;
+ private const int CharStackBufferSize = 32;
+ private const string PosNumberFormat = "#";
+
+ private static readonly string[] s_posCurrencyFormats =
+ {
+ "$#", "#$", "$ #", "# $"
+ };
+
+ private static readonly string[] s_negCurrencyFormats =
+ {
+ "($#)", "-$#", "$-#", "$#-",
+ "(#$)", "-#$", "#-$", "#$-",
+ "-# $", "-$ #", "# $-", "$ #-",
+ "$ -#", "#- $", "($ #)", "(# $)"
+ };
+
+ private static readonly string[] s_posPercentFormats =
+ {
+ "# %", "#%", "%#", "% #"
+ };
+
+ private static readonly string[] s_negPercentFormats =
+ {
+ "-# %", "-#%", "-%#",
+ "%-#", "%#-",
+ "#-%", "#%-",
+ "-% #", "# %-", "% #-",
+ "% -#", "#- %"
+ };
+
+ private static readonly string[] s_negNumberFormats =
+ {
+ "(#)", "-#", "- #", "#-", "# -",
+ };
+
+ public static string FormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info)
+ {
+ char fmt = ParseFormatSpecifier(format, out int digits);
+
+ NumberBuffer number = default;
+ DecimalToNumber(value, ref number);
+
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, isDecimal:true);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+
+ return sb.ToString();
+ }
+
+ public static bool TryFormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
+ {
+ char fmt = ParseFormatSpecifier(format, out int digits);
+
+ NumberBuffer number = default;
+ DecimalToNumber(value, ref number);
+
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, isDecimal: true);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+
+ return sb.TryCopyTo(destination, out charsWritten);
+ }
+
+ private static unsafe void DecimalToNumber(decimal value, ref NumberBuffer number)
+ {
+ decimal d = value;
+
+ char* buffer = number.digits;
+ number.precision = DecimalPrecision;
+ number.sign = d.IsNegative;
+
+ char* p = buffer + DecimalPrecision;
+ while ((d.Mid | d.High) != 0)
+ {
+ p = UInt32ToDecChars(p, decimal.DecDivMod1E9(ref d), 9);
+ }
+ p = UInt32ToDecChars(p, d.Low, 0);
+
+ int i = (int)(buffer + DecimalPrecision - p);
+ number.scale = i - d.Scale;
+
+ char* dst = number.digits;
+ while (--i >= 0)
+ {
+ *dst++ = *p++;
+ }
+ *dst = '\0';
+ }
+
+ public static string FormatDouble(double value, string format, NumberFormatInfo info)
+ {
+ Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
+ var sb = new ValueStringBuilder(stackBuffer);
+ return FormatDouble(ref sb, value, format, info) ?? sb.ToString();
+ }
+
+ public static bool TryFormatDouble(double value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
+ {
+ Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
+ var sb = new ValueStringBuilder(stackBuffer);
+ string s = FormatDouble(ref sb, value, format, info);
+ return s != null ?
+ TryCopyTo(s, destination, out charsWritten) :
+ sb.TryCopyTo(destination, out charsWritten);
+ }
+
+ /// <summary>Formats the specified value according to the specified format and info.</summary>
+ /// <returns>
+ /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
+ /// Null if no existing string was returned, in which case the formatted output is in the builder.
+ /// </returns>
+ private static string FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan<char> format, NumberFormatInfo info)
+ {
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ int precision = DoublePrecision;
+ NumberBuffer number = default;
+
+ switch (fmt)
+ {
+ case 'R':
+ case 'r':
+ {
+ // In order to give numbers that are both friendly to display and round-trippable, we parse the
+ // number using 15 digits and then determine if it round trips to the same value. If it does, we
+ // convert that NUMBER to a string, otherwise we reparse using 17 digits and display that.
+ DoubleToNumber(value, DoublePrecision, ref number);
+ if (number.scale == ScaleNAN)
+ {
+ return info.NaNSymbol;
+ }
+ else if (number.scale == ScaleINF)
+ {
+ return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
+ }
+
+ if (NumberToDouble(ref number) == value)
+ {
+ NumberToString(ref sb, ref number, 'G', DoublePrecision, info, isDecimal: false);
+ }
+ else
+ {
+ DoubleToNumber(value, 17, ref number);
+ NumberToString(ref sb, ref number, 'G', 17, info, isDecimal: false);
+ }
+
+ return null;
+ }
+
+ case 'E':
+ case 'e':
+ // Round values less than E14 to 15 digits
+ if (digits > 14)
+ {
+ precision = 17;
+ }
+ break;
+
+ case 'G':
+ case 'g':
+ // Round values less than G15 to 15 digits. G16 and G17 will not be touched.
+ if (digits > 15)
+ {
+ precision = 17;
+ }
+ break;
+ }
+
+ DoubleToNumber(value, precision, ref number);
+ if (number.scale == ScaleNAN)
+ {
+ return info.NaNSymbol;
+ }
+ else if (number.scale == ScaleINF)
+ {
+ return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
+ }
+
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, isDecimal: false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+
+ return null;
+ }
+
+ public static string FormatSingle(float value, string format, NumberFormatInfo info)
+ {
+ Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
+ var sb = new ValueStringBuilder(stackBuffer);
+ return FormatSingle(ref sb, value, format, info) ?? sb.ToString();
+ }
+
+ public static bool TryFormatSingle(float value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
+ {
+ Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
+ var sb = new ValueStringBuilder(stackBuffer);
+ string s = FormatSingle(ref sb, value, format, info);
+ return s != null ?
+ TryCopyTo(s, destination, out charsWritten) :
+ sb.TryCopyTo(destination, out charsWritten);
+ }
+
+ /// <summary>Formats the specified value according to the specified format and info.</summary>
+ /// <returns>
+ /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
+ /// Null if no existing string was returned, in which case the formatted output is in the builder.
+ /// </returns>
+ private static string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan<char> format, NumberFormatInfo info)
+ {
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ int precision = FloatPrecision;
+ NumberBuffer number = default;
+
+ switch (fmt)
+ {
+ case 'R':
+ case 'r':
+ {
+ // In order to give numbers that are both friendly to display and round-trippable, we parse the
+ // number using 7 digits and then determine if it round trips to the same value. If it does, we
+ // convert that NUMBER to a string, otherwise we reparse using 9 digits and display that.
+ DoubleToNumber(value, FloatPrecision, ref number);
+ if (number.scale == ScaleNAN)
+ {
+ return info.NaNSymbol;
+ }
+ else if (number.scale == ScaleINF)
+ {
+ return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
+ }
+
+ if ((float)NumberToDouble(ref number) == value)
+ {
+ NumberToString(ref sb, ref number, 'G', FloatPrecision, info, isDecimal: false);
+ }
+ else
+ {
+ DoubleToNumber(value, 9, ref number);
+ NumberToString(ref sb, ref number, 'G', 9, info, isDecimal: false);
+ }
+ return null;
+ }
+
+ case 'E':
+ case 'e':
+ // Round values less than E14 to 15 digits.
+ if (digits > 6)
+ {
+ precision = 9;
+ }
+ break;
+
+ case 'G':
+ case 'g':
+ // Round values less than G15 to 15 digits. G16 and G17 will not be touched.
+ if (digits > 7)
+ {
+ precision = 9;
+ }
+ break;
+ }
+
+ DoubleToNumber(value, precision, ref number);
+ if (number.scale == ScaleNAN)
+ {
+ return info.NaNSymbol;
+ }
+ else if (number.scale == ScaleINF)
+ {
+ return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
+ }
+
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return null;
+ }
+
+ private static bool TryCopyTo(string source, Span<char> destination, out int charsWritten)
+ {
+ Debug.Assert(source != null);
+
+ if (source.AsSpan().TryCopyTo(destination))
+ {
+ charsWritten = source.Length;
+ return true;
+ }
+
+ charsWritten = 0;
+ return false;
+ }
+
+ public static string FormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider)
+ {
+ // Fast path for default format with a non-negative value
+ if (value >= 0 && format.Length == 0)
+ {
+ return UInt32ToDecStr((uint)value, digits: -1);
+ }
+
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
+
+ char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
+ if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
+ {
+ return value >= 0 ?
+ UInt32ToDecStr((uint)value, digits) :
+ NegativeInt32ToDecStr(value, digits, info.NegativeSign);
+ }
+ else if (fmtUpper == 'X')
+ {
+ // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
+ // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
+ return Int32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
+ }
+ else
+ {
+ NumberBuffer number = default;
+ Int32ToNumber(value, ref number);
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return sb.ToString();
+ }
+ }
+
+ public static bool TryFormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
+ {
+ // Fast path for default format with a non-negative value
+ if (value >= 0 && format.Length == 0)
+ {
+ return TryUInt32ToDecStr((uint)value, digits: -1, destination, out charsWritten);
+ }
+
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
+
+ char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
+ if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
+ {
+ return value >= 0 ?
+ TryUInt32ToDecStr((uint)value, digits, destination, out charsWritten) :
+ TryNegativeInt32ToDecStr(value, digits, info.NegativeSign, destination, out charsWritten);
+ }
+ else if (fmtUpper == 'X')
+ {
+ // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
+ // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
+ return TryInt32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
+ }
+ else
+ {
+ NumberBuffer number = default;
+ Int32ToNumber(value, ref number);
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return sb.TryCopyTo(destination, out charsWritten);
+ }
+ }
+
+ public static string FormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider)
+ {
+ // Fast path for default format
+ if (format.Length == 0)
+ {
+ return UInt32ToDecStr(value, digits: -1);
+ }
+
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
+
+ char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
+ if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
+ {
+ return UInt32ToDecStr(value, digits);
+ }
+ else if (fmtUpper == 'X')
+ {
+ // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
+ // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
+ return Int32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits);
+ }
+ else
+ {
+ NumberBuffer number = default;
+ UInt32ToNumber(value, ref number);
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return sb.ToString();
+ }
+ }
+
+ public static bool TryFormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
+ {
+ // Fast path for default format
+ if (format.Length == 0)
+ {
+ return TryUInt32ToDecStr(value, digits: -1, destination, out charsWritten);
+ }
+
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
+
+ char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
+ if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
+ {
+ return TryUInt32ToDecStr(value, digits, destination, out charsWritten);
+ }
+ else if (fmtUpper == 'X')
+ {
+ // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
+ // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
+ return TryInt32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
+ }
+ else
+ {
+ NumberBuffer number = default;
+ UInt32ToNumber(value, ref number);
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return sb.TryCopyTo(destination, out charsWritten);
+ }
+ }
+
+ public static string FormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider)
+ {
+ // Fast path for default format with a non-negative value
+ if (value >= 0 && format.Length == 0)
+ {
+ return UInt64ToDecStr((ulong)value, digits: -1);
+ }
+
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
+
+ char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
+ if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
+ {
+ return value >= 0 ?
+ UInt64ToDecStr((ulong)value, digits) :
+ NegativeInt64ToDecStr(value, digits, info.NegativeSign);
+ }
+ else if (fmtUpper == 'X')
+ {
+ // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
+ // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
+ // produces lowercase.
+ return Int64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
+ }
+ else
+ {
+ NumberBuffer number = default;
+ Int64ToNumber(value, ref number);
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return sb.ToString();
+ }
+ }
+
+ public static bool TryFormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
+ {
+ // Fast path for default format with a non-negative value
+ if (value >= 0 && format.Length == 0)
+ {
+ return TryUInt64ToDecStr((ulong)value, digits: -1, destination, out charsWritten);
+ }
+
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
+
+ char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
+ if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
+ {
+ return value >= 0 ?
+ TryUInt64ToDecStr((ulong)value, digits, destination, out charsWritten) :
+ TryNegativeInt64ToDecStr(value, digits, info.NegativeSign, destination, out charsWritten);
+ }
+ else if (fmtUpper == 'X')
+ {
+ // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
+ // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
+ // produces lowercase.
+ return TryInt64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
+ }
+ else
+ {
+ NumberBuffer number = default;
+ Int64ToNumber(value, ref number);
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return sb.TryCopyTo(destination, out charsWritten);
+ }
+ }
+
+ public static string FormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider)
+ {
+ // Fast path for default format
+ if (format.Length == 0)
+ {
+ return UInt64ToDecStr(value, digits: -1);
+ }
+
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
+
+ char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
+ if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
+ {
+ return UInt64ToDecStr(value, digits);
+ }
+ else if (fmtUpper == 'X')
+ {
+ // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
+ // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
+ // produces lowercase.
+ return Int64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits);
+ }
+ else
+ {
+ NumberBuffer number = default;
+ UInt64ToNumber(value, ref number);
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return sb.ToString();
+ }
+ }
+
+ public static bool TryFormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
+ {
+ // Fast path for default format
+ if (format.Length == 0)
+ {
+ return TryUInt64ToDecStr(value, digits: -1, destination, out charsWritten);
+ }
+
+ char fmt = ParseFormatSpecifier(format, out int digits);
+ NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
+
+ char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
+ if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
+ {
+ return TryUInt64ToDecStr(value, digits, destination, out charsWritten);
+ }
+ else if (fmtUpper == 'X')
+ {
+ // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
+ // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
+ // produces lowercase.
+ return TryInt64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
+ }
+ else
+ {
+ NumberBuffer number = default;
+ UInt64ToNumber(value, ref number);
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+ }
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, false);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+ return sb.TryCopyTo(destination, out charsWritten);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
+ private static unsafe void Int32ToNumber(int value, ref NumberBuffer number)
+ {
+ number.precision = Int32Precision;
+
+ if (value >= 0)
+ {
+ number.sign = false;
+ }
+ else
+ {
+ number.sign = true;
+ value = -value;
+ }
+
+ char* buffer = number.digits;
+ char* p = UInt32ToDecChars(buffer + Int32Precision, (uint)value, 0);
+ int i = (int)(buffer + Int32Precision - p);
+
+ number.scale = i;
+
+ char* dst = number.digits;
+ while (--i >= 0)
+ *dst++ = *p++;
+ *dst = '\0';
+ }
+
+ private static unsafe string NegativeInt32ToDecStr(int value, int digits, string sNegative)
+ {
+ Debug.Assert(value < 0);
+
+ if (digits < 1)
+ digits = 1;
+
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length;
+ string result = string.FastAllocateString(bufferLength);
+ fixed (char* buffer = result)
+ {
+ char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
+ Debug.Assert(p == buffer + sNegative.Length);
+
+ for (int i = sNegative.Length - 1; i >= 0; i--)
+ {
+ *(--p) = sNegative[i];
+ }
+ Debug.Assert(p == buffer);
+ }
+ return result;
+ }
+
+ private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, string sNegative, Span<char> destination, out int charsWritten)
+ {
+ Debug.Assert(value < 0);
+
+ if (digits < 1)
+ digits = 1;
+
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length;
+ if (bufferLength > destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = bufferLength;
+ fixed (char* buffer = &MemoryMarshal.GetReference(destination))
+ {
+ char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
+ Debug.Assert(p == buffer + sNegative.Length);
+
+ for (int i = sNegative.Length - 1; i >= 0; i--)
+ {
+ *(--p) = sNegative[i];
+ }
+ Debug.Assert(p == buffer);
+ }
+ return true;
+ }
+
+ private static unsafe string Int32ToHexStr(int value, char hexBase, int digits)
+ {
+ if (digits < 1)
+ digits = 1;
+
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value));
+ string result = string.FastAllocateString(bufferLength);
+ fixed (char* buffer = result)
+ {
+ char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
+ Debug.Assert(p == buffer);
+ }
+ return result;
+ }
+
+ private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span<char> destination, out int charsWritten)
+ {
+ if (digits < 1)
+ digits = 1;
+
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value));
+ if (bufferLength > destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = bufferLength;
+ fixed (char* buffer = &MemoryMarshal.GetReference(destination))
+ {
+ char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
+ Debug.Assert(p == buffer);
+ }
+ return true;
+ }
+
+ private static unsafe char* Int32ToHexChars(char* buffer, uint value, int hexBase, int digits)
+ {
+ while (--digits >= 0 || value != 0)
+ {
+ byte digit = (byte)(value & 0xF);
+ *(--buffer) = (char)(digit + (digit < 10 ? (byte)'0' : hexBase));
+ value >>= 4;
+ }
+ return buffer;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
+ private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number)
+ {
+ number.precision = UInt32Precision;
+ number.sign = false;
+
+ char* buffer = number.digits;
+ char* p = UInt32ToDecChars(buffer + UInt32Precision, value, 0);
+ int i = (int)(buffer + UInt32Precision - p);
+ number.scale = i;
+
+ char* dst = number.digits;
+ while (--i >= 0)
+ *dst++ = *p++;
+ *dst = '\0';
+ }
+
+ internal static unsafe char* UInt32ToDecChars(char* bufferEnd, uint value, int digits)
+ {
+ while (--digits >= 0 || value != 0)
+ {
+ // TODO https://github.com/dotnet/coreclr/issues/3439
+ uint newValue = value / 10;
+ *(--bufferEnd) = (char)(value - (newValue * 10) + '0');
+ value = newValue;
+ }
+ return bufferEnd;
+ }
+
+ private static unsafe string UInt32ToDecStr(uint value, int digits)
+ {
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
+ string result = string.FastAllocateString(bufferLength);
+ fixed (char* buffer = result)
+ {
+ char* p = buffer + bufferLength;
+ if (digits <= 1)
+ {
+ do
+ {
+ // TODO https://github.com/dotnet/coreclr/issues/3439
+ uint div = value / 10;
+ *(--p) = (char)('0' + value - (div * 10));
+ value = div;
+ }
+ while (value != 0);
+ }
+ else
+ {
+ p = UInt32ToDecChars(p, value, digits);
+ }
+ Debug.Assert(p == buffer);
+ }
+ return result;
+ }
+
+ private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span<char> destination, out int charsWritten)
+ {
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
+ if (bufferLength > destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = bufferLength;
+ fixed (char* buffer = &MemoryMarshal.GetReference(destination))
+ {
+ char* p = buffer + bufferLength;
+ if (digits <= 1)
+ {
+ do
+ {
+ // TODO https://github.com/dotnet/coreclr/issues/3439
+ uint div = value / 10;
+ *(--p) = (char)('0' + value - (div * 10));
+ value = div;
+ }
+ while (value != 0);
+ }
+ else
+ {
+ p = UInt32ToDecChars(p, value, digits);
+ }
+ Debug.Assert(p == buffer);
+ }
+ return true;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe bool TryCopyTo(char* src, int length, Span<char> destination, out int charsWritten)
+ {
+ if (new ReadOnlySpan<char>(src, length).TryCopyTo(destination))
+ {
+ charsWritten = length;
+ return true;
+ }
+ else
+ {
+ charsWritten = 0;
+ return false;
+ }
+ }
+
+ private static unsafe void Int64ToNumber(long input, ref NumberBuffer number)
+ {
+ ulong value = (ulong)input;
+ number.sign = input < 0;
+ number.precision = Int64Precision;
+ if (number.sign)
+ {
+ value = (ulong)(-input);
+ }
+
+ char* buffer = number.digits;
+ char* p = buffer + Int64Precision;
+ while (High32(value) != 0)
+ p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
+ p = UInt32ToDecChars(p, Low32(value), 0);
+ int i = (int)(buffer + Int64Precision - p);
+
+ number.scale = i;
+
+ char* dst = number.digits;
+ while (--i >= 0)
+ *dst++ = *p++;
+ *dst = '\0';
+ }
+
+ private static unsafe string NegativeInt64ToDecStr(long input, int digits, string sNegative)
+ {
+ Debug.Assert(input < 0);
+
+ if (digits < 1)
+ {
+ digits = 1;
+ }
+
+ ulong value = (ulong)(-input);
+
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)) + sNegative.Length;
+ string result = string.FastAllocateString(bufferLength);
+ fixed (char* buffer = result)
+ {
+ char* p = buffer + bufferLength;
+ while (High32(value) != 0)
+ {
+ p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
+ digits -= 9;
+ }
+ p = UInt32ToDecChars(p, Low32(value), digits);
+ Debug.Assert(p == buffer + sNegative.Length);
+
+ for (int i = sNegative.Length - 1; i >= 0; i--)
+ {
+ *(--p) = sNegative[i];
+ }
+ Debug.Assert(p == buffer);
+ }
+ return result;
+ }
+
+ private static unsafe bool TryNegativeInt64ToDecStr(long input, int digits, string sNegative, Span<char> destination, out int charsWritten)
+ {
+ Debug.Assert(input < 0);
+
+ if (digits < 1)
+ {
+ digits = 1;
+ }
+
+ ulong value = (ulong)(-input);
+
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((ulong)(-input))) + sNegative.Length;
+ if (bufferLength > destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = bufferLength;
+ fixed (char* buffer = &MemoryMarshal.GetReference(destination))
+ {
+ char* p = buffer + bufferLength;
+ while (High32(value) != 0)
+ {
+ p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
+ digits -= 9;
+ }
+ p = UInt32ToDecChars(p, Low32(value), digits);
+ Debug.Assert(p == buffer + sNegative.Length);
+
+ for (int i = sNegative.Length - 1; i >= 0; i--)
+ {
+ *(--p) = sNegative[i];
+ }
+ Debug.Assert(p == buffer);
+ }
+ return true;
+ }
+
+ private static unsafe string Int64ToHexStr(long value, char hexBase, int digits)
+ {
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value));
+ string result = string.FastAllocateString(bufferLength);
+ fixed (char* buffer = result)
+ {
+ char* p = buffer + bufferLength;
+ if (High32((ulong)value) != 0)
+ {
+ p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8);
+ p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
+ }
+ else
+ {
+ p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1));
+ }
+ Debug.Assert(p == buffer);
+ }
+ return result;
+ }
+
+ private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span<char> destination, out int charsWritten)
+ {
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value));
+ if (bufferLength > destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = bufferLength;
+ fixed (char* buffer = &MemoryMarshal.GetReference(destination))
+ {
+ char* p = buffer + bufferLength;
+ if (High32((ulong)value) != 0)
+ {
+ p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8);
+ p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
+ }
+ else
+ {
+ p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1));
+ }
+ Debug.Assert(p == buffer);
+ }
+ return true;
+ }
+
+ private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number)
+ {
+ number.precision = UInt64Precision;
+ number.sign = false;
+
+ char* buffer = number.digits;
+ char* p = buffer + UInt64Precision;
+
+ while (High32(value) != 0)
+ p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
+ p = UInt32ToDecChars(p, Low32(value), 0);
+ int i = (int)(buffer + UInt64Precision - p);
+
+ number.scale = i;
+
+ char* dst = number.digits;
+ while (--i >= 0)
+ *dst++ = *p++;
+ *dst = '\0';
+ }
+
+ private static unsafe string UInt64ToDecStr(ulong value, int digits)
+ {
+ if (digits < 1)
+ digits = 1;
+
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
+ string result = string.FastAllocateString(bufferLength);
+ fixed (char* buffer = result)
+ {
+ char* p = buffer + bufferLength;
+ while (High32(value) != 0)
+ {
+ p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
+ digits -= 9;
+ }
+ p = UInt32ToDecChars(p, Low32(value), digits);
+ Debug.Assert(p == buffer);
+ }
+ return result;
+ }
+
+ private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span<char> destination, out int charsWritten)
+ {
+ if (digits < 1)
+ digits = 1;
+
+ int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
+ if (bufferLength > destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = bufferLength;
+ fixed (char* buffer = &MemoryMarshal.GetReference(destination))
+ {
+ char* p = buffer + bufferLength;
+ while (High32(value) != 0)
+ {
+ p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
+ digits -= 9;
+ }
+ p = UInt32ToDecChars(p, Low32(value), digits);
+ Debug.Assert(p == buffer);
+ }
+ return true;
+ }
+
+ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits)
+ {
+ char c = default;
+ if (format.Length > 0)
+ {
+ // If the format begins with a symbol, see if it's a standard format
+ // with or without a specified number of digits.
+ c = format[0];
+ if ((uint)(c - 'A') <= 'Z' - 'A' ||
+ (uint)(c - 'a') <= 'z' - 'a')
+ {
+ // Fast path for sole symbol, e.g. "D"
+ if (format.Length == 1)
+ {
+ digits = -1;
+ return c;
+ }
+
+ if (format.Length == 2)
+ {
+ // Fast path for symbol and single digit, e.g. "X4"
+ int d = format[1] - '0';
+ if ((uint)d < 10)
+ {
+ digits = d;
+ return c;
+ }
+ }
+ else if (format.Length == 3)
+ {
+ // Fast path for symbol and double digit, e.g. "F12"
+ int d1 = format[1] - '0', d2 = format[2] - '0';
+ if ((uint)d1 < 10 && (uint)d2 < 10)
+ {
+ digits = d1 * 10 + d2;
+ return c;
+ }
+ }
+
+ // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
+ // but it can begin with any number of 0s, and thus we may need to check more than two
+ // digits. Further, for compat, we need to stop when we hit a null char.
+ int n = 0;
+ int i = 1;
+ while (i < format.Length && (((uint)format[i] - '0') < 10) && n < 10)
+ {
+ n = (n * 10) + format[i++] - '0';
+ }
+
+ // If we're at the end of the digits rather than having stopped because we hit something
+ // other than a digit or overflowed, return the standard format info.
+ if (i == format.Length || format[i] == '\0')
+ {
+ digits = n;
+ return c;
+ }
+ }
+ }
+
+ // Default empty format to be "G"; custom format is signified with '\0'.
+ digits = -1;
+ return format.Length == 0 || c == '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it.
+ 'G' :
+ '\0';
+ }
+
+ internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info, bool isDecimal)
+ {
+ int nMinDigits = -1;
+
+ switch (format)
+ {
+ case 'C':
+ case 'c':
+ {
+ nMinDigits = nMaxDigits >= 0 ? nMaxDigits : info.CurrencyDecimalDigits;
+ if (nMaxDigits < 0)
+ nMaxDigits = info.CurrencyDecimalDigits;
+
+ RoundNumber(ref number, number.scale + nMaxDigits); // Don't change this line to use digPos since digCount could have its sign changed.
+
+ FormatCurrency(ref sb, ref number, nMinDigits, nMaxDigits, info);
+
+ break;
+ }
+
+ case 'F':
+ case 'f':
+ {
+ if (nMaxDigits < 0)
+ nMaxDigits = nMinDigits = info.NumberDecimalDigits;
+ else
+ nMinDigits = nMaxDigits;
+
+ RoundNumber(ref number, number.scale + nMaxDigits);
+
+ if (number.sign)
+ sb.Append(info.NegativeSign);
+
+ FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, null, info.NumberDecimalSeparator, null);
+
+ break;
+ }
+
+ case 'N':
+ case 'n':
+ {
+ if (nMaxDigits < 0)
+ nMaxDigits = nMinDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation
+ else
+ nMinDigits = nMaxDigits;
+
+ RoundNumber(ref number, number.scale + nMaxDigits);
+
+ FormatNumber(ref sb, ref number, nMinDigits, nMaxDigits, info);
+
+ break;
+ }
+
+ case 'E':
+ case 'e':
+ {
+ if (nMaxDigits < 0)
+ nMaxDigits = nMinDigits = 6;
+ else
+ nMinDigits = nMaxDigits;
+ nMaxDigits++;
+
+ RoundNumber(ref number, nMaxDigits);
+
+ if (number.sign)
+ sb.Append(info.NegativeSign);
+
+ FormatScientific(ref sb, ref number, nMinDigits, nMaxDigits, info, format);
+
+ break;
+ }
+
+ case 'G':
+ case 'g':
+ {
+ bool enableRounding = true;
+ if (nMaxDigits < 1)
+ {
+ if (isDecimal && (nMaxDigits == -1))
+ {
+ // Default to 29 digits precision only for G formatting without a precision specifier
+ // This ensures that the PAL code pads out to the correct place even when we use the default precision
+ nMaxDigits = nMinDigits = DecimalPrecision;
+ enableRounding = false; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant
+ }
+ else
+ {
+ // This ensures that the PAL code pads out to the correct place even when we use the default precision
+ nMaxDigits = nMinDigits = number.precision;
+ }
+ }
+ else
+ nMinDigits = nMaxDigits;
+
+ if (enableRounding) // Don't round for G formatting without precision
+ RoundNumber(ref number, nMaxDigits); // This also fixes up the minus zero case
+ else
+ {
+ if (isDecimal && (number.digits[0] == 0))
+ {
+ // Minus zero should be formatted as 0
+ number.sign = false;
+ }
+ }
+
+ if (number.sign)
+ sb.Append(info.NegativeSign);
+
+ FormatGeneral(ref sb, ref number, nMinDigits, nMaxDigits, info, (char)(format - ('G' - 'E')), !enableRounding);
+
+ break;
+ }
+
+ case 'P':
+ case 'p':
+ {
+ if (nMaxDigits < 0)
+ nMaxDigits = nMinDigits = info.PercentDecimalDigits;
+ else
+ nMinDigits = nMaxDigits;
+ number.scale += 2;
+
+ RoundNumber(ref number, number.scale + nMaxDigits);
+
+ FormatPercent(ref sb, ref number, nMinDigits, nMaxDigits, info);
+
+ break;
+ }
+
+ default:
+ throw new FormatException(SR.Argument_BadFormatSpecifier);
+ }
+ }
+
+ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref NumberBuffer number, ReadOnlySpan<char> format, NumberFormatInfo info)
+ {
+ int digitCount;
+ int decimalPos;
+ int firstDigit;
+ int lastDigit;
+ int digPos;
+ bool scientific;
+ int thousandPos;
+ int thousandCount = 0;
+ bool thousandSeps;
+ int scaleAdjust;
+ int adjust;
+
+ int section;
+ int src;
+ char* dig = number.digits;
+ char ch;
+
+ section = FindSection(format, dig[0] == 0 ? 2 : number.sign ? 1 : 0);
+
+ while (true)
+ {
+ digitCount = 0;
+ decimalPos = -1;
+ firstDigit = 0x7FFFFFFF;
+ lastDigit = 0;
+ scientific = false;
+ thousandPos = -1;
+ thousandSeps = false;
+ scaleAdjust = 0;
+ src = section;
+
+ fixed (char* pFormat = &MemoryMarshal.GetReference(format))
+ {
+ while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
+ {
+ switch (ch)
+ {
+ case '#':
+ digitCount++;
+ break;
+ case '0':
+ if (firstDigit == 0x7FFFFFFF)
+ firstDigit = digitCount;
+ digitCount++;
+ lastDigit = digitCount;
+ break;
+ case '.':
+ if (decimalPos < 0)
+ decimalPos = digitCount;
+ break;
+ case ',':
+ if (digitCount > 0 && decimalPos < 0)
+ {
+ if (thousandPos >= 0)
+ {
+ if (thousandPos == digitCount)
+ {
+ thousandCount++;
+ break;
+ }
+ thousandSeps = true;
+ }
+ thousandPos = digitCount;
+ thousandCount = 1;
+ }
+ break;
+ case '%':
+ scaleAdjust += 2;
+ break;
+ case '\x2030':
+ scaleAdjust += 3;
+ break;
+ case '\'':
+ case '"':
+ while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
+ ;
+ break;
+ case '\\':
+ if (src < format.Length && pFormat[src] != 0)
+ src++;
+ break;
+ case 'E':
+ case 'e':
+ if ((src < format.Length && pFormat[src] == '0') ||
+ (src + 1 < format.Length && (pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0'))
+ {
+ while (++src < format.Length && pFormat[src] == '0');
+ scientific = true;
+ }
+ break;
+ }
+ }
+ }
+
+ if (decimalPos < 0)
+ decimalPos = digitCount;
+
+ if (thousandPos >= 0)
+ {
+ if (thousandPos == decimalPos)
+ scaleAdjust -= thousandCount * 3;
+ else
+ thousandSeps = true;
+ }
+
+ if (dig[0] != 0)
+ {
+ number.scale += scaleAdjust;
+ int pos = scientific ? digitCount : number.scale + digitCount - decimalPos;
+ RoundNumber(ref number, pos);
+ if (dig[0] == 0)
+ {
+ src = FindSection(format, 2);
+ if (src != section)
+ {
+ section = src;
+ continue;
+ }
+ }
+ }
+ else
+ {
+ number.sign = false; // We need to format -0 without the sign set.
+ number.scale = 0; // Decimals with scale ('0.00') should be rounded.
+ }
+
+ break;
+ }
+
+ firstDigit = firstDigit < decimalPos ? decimalPos - firstDigit : 0;
+ lastDigit = lastDigit > decimalPos ? decimalPos - lastDigit : 0;
+ if (scientific)
+ {
+ digPos = decimalPos;
+ adjust = 0;
+ }
+ else
+ {
+ digPos = number.scale > decimalPos ? number.scale : decimalPos;
+ adjust = number.scale - decimalPos;
+ }
+ src = section;
+
+ // Adjust can be negative, so we make this an int instead of an unsigned int.
+ // Adjust represents the number of characters over the formatting e.g. format string is "0000" and you are trying to
+ // format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
+ // -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
+ Span<int> thousandsSepPos = stackalloc int[4];
+ int thousandsSepCtr = -1;
+
+ if (thousandSeps)
+ {
+ // We need to precompute this outside the number formatting loop
+ if (info.NumberGroupSeparator.Length > 0)
+ {
+ // We need this array to figure out where to insert the thousands separator. We would have to traverse the string
+ // backwards. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
+ // the thousands separator so we can get away with traversing forwards. Note we only have to compute up to digPos.
+ // The max is not bound since you can have formatting strings of the form "000,000..", and this
+ // should handle that case too.
+
+ int[] groupDigits = info.numberGroupSizes;
+
+ int groupSizeIndex = 0; // Index into the groupDigits array.
+ int groupTotalSizeCount = 0;
+ int groupSizeLen = groupDigits.Length; // The length of groupDigits array.
+ if (groupSizeLen != 0)
+ groupTotalSizeCount = groupDigits[groupSizeIndex]; // The current running total of group size.
+ int groupSize = groupTotalSizeCount;
+
+ int totalDigits = digPos + ((adjust < 0) ? adjust : 0); // Actual number of digits in o/p
+ int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits;
+ while (numDigits > groupTotalSizeCount)
+ {
+ if (groupSize == 0)
+ break;
+ ++thousandsSepCtr;
+ if (thousandsSepCtr >= thousandsSepPos.Length)
+ {
+ var newThousandsSepPos = new int[thousandsSepPos.Length * 2];
+ thousandsSepPos.CopyTo(newThousandsSepPos);
+ thousandsSepPos = newThousandsSepPos;
+ }
+
+ thousandsSepPos[thousandsSepCtr] = groupTotalSizeCount;
+ if (groupSizeIndex < groupSizeLen - 1)
+ {
+ groupSizeIndex++;
+ groupSize = groupDigits[groupSizeIndex];
+ }
+ groupTotalSizeCount += groupSize;
+ }
+ }
+ }
+
+ if (number.sign && section == 0)
+ sb.Append(info.NegativeSign);
+
+ bool decimalWritten = false;
+
+ fixed (char* pFormat = &MemoryMarshal.GetReference(format))
+ {
+ char* cur = dig;
+
+ while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
+ {
+ if (adjust > 0)
+ {
+ switch (ch)
+ {
+ case '#':
+ case '0':
+ case '.':
+ while (adjust > 0)
+ {
+ // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
+ // the character after which the groupSeparator needs to be appended.
+ sb.Append(*cur != 0 ? *cur++ : '0');
+ if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
+ {
+ if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
+ {
+ sb.Append(info.NumberGroupSeparator);
+ thousandsSepCtr--;
+ }
+ }
+ digPos--;
+ adjust--;
+ }
+ break;
+ }
+ }
+
+ switch (ch)
+ {
+ case '#':
+ case '0':
+ {
+ if (adjust < 0)
+ {
+ adjust++;
+ ch = digPos <= firstDigit ? '0' : '\0';
+ }
+ else
+ {
+ ch = *cur != 0 ? *cur++ : digPos > lastDigit ? '0' : '\0';
+ }
+ if (ch != 0)
+ {
+ sb.Append(ch);
+ if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
+ {
+ if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
+ {
+ sb.Append(info.NumberGroupSeparator);
+ thousandsSepCtr--;
+ }
+ }
+ }
+
+ digPos--;
+ break;
+ }
+ case '.':
+ {
+ if (digPos != 0 || decimalWritten)
+ {
+ // For compatibility, don't echo repeated decimals
+ break;
+ }
+ // If the format has trailing zeros or the format has a decimal and digits remain
+ if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0))
+ {
+ sb.Append(info.NumberDecimalSeparator);
+ decimalWritten = true;
+ }
+ break;
+ }
+ case '\x2030':
+ sb.Append(info.PerMilleSymbol);
+ break;
+ case '%':
+ sb.Append(info.PercentSymbol);
+ break;
+ case ',':
+ break;
+ case '\'':
+ case '"':
+ while (src < format.Length && pFormat[src] != 0 && pFormat[src] != ch)
+ sb.Append(pFormat[src++]);
+ if (src < format.Length && pFormat[src] != 0)
+ src++;
+ break;
+ case '\\':
+ if (src < format.Length && pFormat[src] != 0)
+ sb.Append(pFormat[src++]);
+ break;
+ case 'E':
+ case 'e':
+ {
+ bool positiveSign = false;
+ int i = 0;
+ if (scientific)
+ {
+ if (src < format.Length && pFormat[src] == '0')
+ {
+ // Handles E0, which should format the same as E-0
+ i++;
+ }
+ else if (src+1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0')
+ {
+ // Handles E+0
+ positiveSign = true;
+ }
+ else if (src+1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0')
+ {
+ // Handles E-0
+ // Do nothing, this is just a place holder s.t. we don't break out of the loop.
+ }
+ else
+ {
+ sb.Append(ch);
+ break;
+ }
+
+ while (++src < format.Length && pFormat[src] == '0')
+ i++;
+ if (i > 10)
+ i = 10;
+
+ int exp = dig[0] == 0 ? 0 : number.scale - decimalPos;
+ FormatExponent(ref sb, info, exp, ch, i, positiveSign);
+ scientific = false;
+ }
+ else
+ {
+ sb.Append(ch); // Copy E or e to output
+ if (src < format.Length)
+ {
+ if (pFormat[src] == '+' || pFormat[src] == '-')
+ sb.Append(pFormat[src++]);
+ while (src < format.Length && pFormat[src] == '0')
+ sb.Append(pFormat[src++]);
+ }
+ }
+ break;
+ }
+ default:
+ sb.Append(ch);
+ break;
+ }
+ }
+ }
+ }
+
+ private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
+ {
+ string fmt = number.sign ?
+ s_negCurrencyFormats[info.CurrencyNegativePattern] :
+ s_posCurrencyFormats[info.CurrencyPositivePattern];
+
+ foreach (char ch in fmt)
+ {
+ switch (ch)
+ {
+ case '#':
+ FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.currencyGroupSizes, info.CurrencyDecimalSeparator, info.CurrencyGroupSeparator);
+ break;
+ case '-':
+ sb.Append(info.NegativeSign);
+ break;
+ case '$':
+ sb.Append(info.CurrencySymbol);
+ break;
+ default:
+ sb.Append(ch);
+ break;
+ }
+ }
+ }
+
+ private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, int[] groupDigits, string sDecimal, string sGroup)
+ {
+ int digPos = number.scale;
+ char* dig = number.digits;
+
+ if (digPos > 0)
+ {
+ if (groupDigits != null)
+ {
+ int groupSizeIndex = 0; // Index into the groupDigits array.
+ int groupSizeCount = groupDigits[groupSizeIndex]; // The current total of group size.
+ int bufferSize = digPos; // The length of the result buffer string.
+ int groupSize = 0; // The current group size.
+
+ // Find out the size of the string buffer for the result.
+ if (groupDigits.Length != 0) // You can pass in 0 length arrays
+ {
+ while (digPos > groupSizeCount)
+ {
+ groupSize = groupDigits[groupSizeIndex];
+ if (groupSize == 0)
+ break;
+
+ bufferSize += sGroup.Length;
+ if (groupSizeIndex < groupDigits.Length - 1)
+ groupSizeIndex++;
+
+ groupSizeCount += groupDigits[groupSizeIndex];
+ if (groupSizeCount < 0 || bufferSize < 0)
+ throw new ArgumentOutOfRangeException(); // If we overflow
+ }
+
+ groupSize = groupSizeCount == 0 ? 0 : groupDigits[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0
+ }
+
+ groupSizeIndex = 0;
+ int digitCount = 0;
+ int digLength = string.wcslen(dig);
+ int digStart = (digPos < digLength) ? digPos : digLength;
+ fixed (char* spanPtr = &MemoryMarshal.GetReference(sb.AppendSpan(bufferSize)))
+ {
+ char* p = spanPtr + bufferSize - 1;
+ for (int i = digPos - 1; i >= 0; i--)
+ {
+ *(p--) = (i < digStart) ? dig[i] : '0';
+
+ if (groupSize > 0)
+ {
+ digitCount++;
+ if ((digitCount == groupSize) && (i != 0))
+ {
+ for (int j = sGroup.Length - 1; j >= 0; j--)
+ *(p--) = sGroup[j];
+
+ if (groupSizeIndex < groupDigits.Length - 1)
+ {
+ groupSizeIndex++;
+ groupSize = groupDigits[groupSizeIndex];
+ }
+ digitCount = 0;
+ }
+ }
+ }
+
+ Debug.Assert(p >= spanPtr - 1, "Underflow");
+ dig += digStart;
+ }
+ }
+ else
+ {
+ do
+ {
+ sb.Append(*dig != 0 ? *dig++ : '0');
+ }
+ while (--digPos > 0);
+ }
+ }
+ else
+ {
+ sb.Append('0');
+ }
+
+ if (nMaxDigits > 0)
+ {
+ sb.Append(sDecimal);
+ if ((digPos < 0) && (nMaxDigits > 0))
+ {
+ int zeroes = Math.Min(-digPos, nMaxDigits);
+ sb.Append('0', zeroes);
+ digPos += zeroes;
+ nMaxDigits -= zeroes;
+ }
+
+ while (nMaxDigits > 0)
+ {
+ sb.Append((*dig != 0) ? *dig++ : '0');
+ nMaxDigits--;
+ }
+ }
+ }
+
+ private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
+ {
+ string fmt = number.sign ?
+ s_negNumberFormats[info.NumberNegativePattern] :
+ PosNumberFormat;
+
+ foreach (char ch in fmt)
+ {
+ switch (ch)
+ {
+ case '#':
+ FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.numberGroupSizes, info.NumberDecimalSeparator, info.NumberGroupSeparator);
+ break;
+ case '-':
+ sb.Append(info.NegativeSign);
+ break;
+ default:
+ sb.Append(ch);
+ break;
+ }
+ }
+ }
+
+ private static unsafe void FormatScientific(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, char expChar)
+ {
+ char* dig = number.digits;
+
+ sb.Append((*dig != 0) ? *dig++ : '0');
+
+ if (nMaxDigits != 1) // For E0 we would like to suppress the decimal point
+ sb.Append(info.NumberDecimalSeparator);
+
+ while (--nMaxDigits > 0)
+ sb.Append((*dig != 0) ? *dig++ : '0');
+
+ int e = number.digits[0] == 0 ? 0 : number.scale - 1;
+ FormatExponent(ref sb, info, e, expChar, 3, true);
+ }
+
+ private static unsafe void FormatExponent(ref ValueStringBuilder sb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign)
+ {
+ sb.Append(expChar);
+
+ if (value < 0)
+ {
+ sb.Append(info.NegativeSign);
+ value = -value;
+ }
+ else
+ {
+ if (positiveSign)
+ sb.Append(info.PositiveSign);
+ }
+
+ char* digits = stackalloc char[MaxUInt32DecDigits];
+ char* p = UInt32ToDecChars(digits + MaxUInt32DecDigits, (uint)value, minDigits);
+ int i = (int)(digits + MaxUInt32DecDigits - p);
+ sb.Append(p, (int)(digits + MaxUInt32DecDigits - p));
+ }
+
+ private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, char expChar, bool bSuppressScientific)
+ {
+ int digPos = number.scale;
+ bool scientific = false;
+
+ if (!bSuppressScientific)
+ {
+ // Don't switch to scientific notation
+ if (digPos > nMaxDigits || digPos < -3)
+ {
+ digPos = 1;
+ scientific = true;
+ }
+ }
+
+ char* dig = number.digits;
+
+ if (digPos > 0)
+ {
+ do
+ {
+ sb.Append((*dig != 0) ? *dig++ : '0');
+ } while (--digPos > 0);
+ }
+ else
+ {
+ sb.Append('0');
+ }
+
+ if (*dig != 0 || digPos < 0)
+ {
+ sb.Append(info.NumberDecimalSeparator);
+
+ while (digPos < 0)
+ {
+ sb.Append('0');
+ digPos++;
+ }
+
+ while (*dig != 0)
+ sb.Append(*dig++);
+ }
+
+ if (scientific)
+ FormatExponent(ref sb, info, number.scale - 1, expChar, 2, true);
+ }
+
+ private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
+ {
+ string fmt = number.sign ?
+ s_negPercentFormats[info.PercentNegativePattern] :
+ s_posPercentFormats[info.PercentPositivePattern];
+
+ foreach (char ch in fmt)
+ {
+ switch (ch)
+ {
+ case '#':
+ FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.percentGroupSizes, info.PercentDecimalSeparator, info.PercentGroupSeparator);
+ break;
+ case '-':
+ sb.Append(info.NegativeSign);
+ break;
+ case '%':
+ sb.Append(info.PercentSymbol);
+ break;
+ default:
+ sb.Append(ch);
+ break;
+ }
+ }
+ }
+
+ private static unsafe void RoundNumber(ref NumberBuffer number, int pos)
+ {
+ char* dig = number.digits;
+
+ int i = 0;
+ while (i < pos && dig[i] != 0)
+ i++;
+
+ if (i == pos && dig[i] >= '5')
+ {
+ while (i > 0 && dig[i - 1] == '9')
+ i--;
+
+ if (i > 0)
+ {
+ dig[i - 1]++;
+ }
+ else
+ {
+ number.scale++;
+ dig[0] = '1';
+ i = 1;
+ }
+ }
+ else
+ {
+ while (i > 0 && dig[i - 1] == '0')
+ i--;
+ }
+ if (i == 0)
+ {
+ number.scale = 0;
+ number.sign = false;
+ }
+ dig[i] = '\0';
+ }
+
+ private static unsafe int FindSection(ReadOnlySpan<char> format, int section)
+ {
+ int src;
+ char ch;
+
+ if (section == 0)
+ return 0;
+
+ fixed (char* pFormat = &MemoryMarshal.GetReference(format))
+ {
+ src = 0;
+ for (;;)
+ {
+ if (src >= format.Length)
+ {
+ return 0;
+ }
+
+ switch (ch = pFormat[src++])
+ {
+ case '\'':
+ case '"':
+ while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
+ ;
+ break;
+ case '\\':
+ if (src < format.Length && pFormat[src] != 0)
+ src++;
+ break;
+ case ';':
+ if (--section != 0)
+ break;
+ if (src < format.Length && pFormat[src] != 0 && pFormat[src] != ';')
+ return src;
+ goto case '\0';
+ case '\0':
+ return 0;
+ }
+ }
+ }
+ }
+
+ private static uint Low32(ulong value) => (uint)value;
+
+ private static uint High32(ulong value) => (uint)((value & 0xFFFFFFFF00000000) >> 32);
+
+ private static uint Int64DivMod1E9(ref ulong value)
+ {
+ uint rem = (uint)(value % 1000000000);
+ value /= 1000000000;
+ return rem;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs
new file mode 100644
index 0000000000..2316f99bd3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ internal static partial class Number
+ {
+ private const int NumberMaxDigits = 50; // needs to == NUMBER_MAXDIGITS in coreclr's src/classlibnative/bcltype/number.h.
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal unsafe ref struct NumberBuffer // needs to match layout of NUMBER in coreclr's src/classlibnative/bcltype/number.h
+ {
+ public int precision;
+ public int scale;
+ private int _sign;
+ private DigitsAndNullTerminator _digits;
+ private char* _allDigits;
+
+ public bool sign { get => _sign != 0; set => _sign = value ? 1 : 0; }
+ public char* digits => (char*)Unsafe.AsPointer(ref _digits);
+
+ [StructLayout(LayoutKind.Sequential, Size = (NumberMaxDigits + 1) * sizeof(char))]
+ private struct DigitsAndNullTerminator { }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs
new file mode 100644
index 0000000000..936a826c93
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs
@@ -0,0 +1,973 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ // The Parse methods provided by the numeric classes convert a
+ // string to a numeric value. The optional style parameter specifies the
+ // permitted style of the numeric string. It must be a combination of bit flags
+ // from the NumberStyles enumeration. The optional info parameter
+ // specifies the NumberFormatInfo instance to use when parsing the
+ // string. If the info parameter is null or omitted, the numeric
+ // formatting information is obtained from the current culture.
+ //
+ // Numeric strings produced by the Format methods using the Currency,
+ // Decimal, Engineering, Fixed point, General, or Number standard formats
+ // (the C, D, E, F, G, and N format specifiers) are guaranteed to be parseable
+ // by the Parse methods if the NumberStyles.Any style is
+ // specified. Note, however, that the Parse methods do not accept
+ // NaNs or Infinities.
+
+ internal partial class Number
+ {
+ private const int Int32Precision = 10;
+ private const int UInt32Precision = Int32Precision;
+ private const int Int64Precision = 19;
+ private const int UInt64Precision = 20;
+
+ private static bool HexNumberToInt32(ref NumberBuffer number, ref int value)
+ {
+ uint passedValue = 0;
+ bool returnValue = HexNumberToUInt32(ref number, ref passedValue);
+ value = (int)passedValue;
+ return returnValue;
+ }
+
+ private static bool HexNumberToInt64(ref NumberBuffer number, ref long value)
+ {
+ ulong passedValue = 0;
+ bool returnValue = HexNumberToUInt64(ref number, ref passedValue);
+ value = (long)passedValue;
+ return returnValue;
+ }
+
+ private static unsafe bool HexNumberToUInt32(ref NumberBuffer number, ref uint value)
+ {
+ int i = number.scale;
+ if (i > UInt32Precision || i < number.precision)
+ {
+ return false;
+ }
+ char* p = number.digits;
+ Debug.Assert(p != null);
+
+ uint n = 0;
+ while (--i >= 0)
+ {
+ if (n > ((uint)0xFFFFFFFF / 16))
+ {
+ return false;
+ }
+ n *= 16;
+ if (*p != '\0')
+ {
+ uint newN = n;
+ if (*p != '\0')
+ {
+ if (*p >= '0' && *p <= '9')
+ {
+ newN += (uint)(*p - '0');
+ }
+ else
+ {
+ if (*p >= 'A' && *p <= 'F')
+ {
+ newN += (uint)((*p - 'A') + 10);
+ }
+ else
+ {
+ Debug.Assert(*p >= 'a' && *p <= 'f');
+ newN += (uint)((*p - 'a') + 10);
+ }
+ }
+ p++;
+ }
+
+ // Detect an overflow here...
+ if (newN < n)
+ {
+ return false;
+ }
+ n = newN;
+ }
+ }
+ value = n;
+ return true;
+ }
+
+ private static unsafe bool HexNumberToUInt64(ref NumberBuffer number, ref ulong value)
+ {
+ int i = number.scale;
+ if (i > UInt64Precision || i < number.precision)
+ {
+ return false;
+ }
+ char* p = number.digits;
+ Debug.Assert(p != null);
+
+ ulong n = 0;
+ while (--i >= 0)
+ {
+ if (n > (0xFFFFFFFFFFFFFFFF / 16))
+ {
+ return false;
+ }
+ n *= 16;
+ if (*p != '\0')
+ {
+ ulong newN = n;
+ if (*p != '\0')
+ {
+ if (*p >= '0' && *p <= '9')
+ {
+ newN += (ulong)(*p - '0');
+ }
+ else
+ {
+ if (*p >= 'A' && *p <= 'F')
+ {
+ newN += (ulong)((*p - 'A') + 10);
+ }
+ else
+ {
+ Debug.Assert(*p >= 'a' && *p <= 'f');
+ newN += (ulong)((*p - 'a') + 10);
+ }
+ }
+ p++;
+ }
+
+ // Detect an overflow here...
+ if (newN < n)
+ {
+ return false;
+ }
+ n = newN;
+ }
+ }
+ value = n;
+ return true;
+ }
+
+ private static unsafe bool NumberToInt32(ref NumberBuffer number, ref int value)
+ {
+ int i = number.scale;
+ if (i > Int32Precision || i < number.precision)
+ {
+ return false;
+ }
+ char* p = number.digits;
+ Debug.Assert(p != null);
+ int n = 0;
+ while (--i >= 0)
+ {
+ if ((uint)n > (0x7FFFFFFF / 10))
+ {
+ return false;
+ }
+ n *= 10;
+ if (*p != '\0')
+ {
+ n += (int)(*p++ - '0');
+ }
+ }
+ if (number.sign)
+ {
+ n = -n;
+ if (n > 0)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (n < 0)
+ {
+ return false;
+ }
+ }
+ value = n;
+ return true;
+ }
+
+ private static unsafe bool NumberToInt64(ref NumberBuffer number, ref long value)
+ {
+ int i = number.scale;
+ if (i > Int64Precision || i < number.precision)
+ {
+ return false;
+ }
+ char* p = number.digits;
+ Debug.Assert(p != null);
+ long n = 0;
+ while (--i >= 0)
+ {
+ if ((ulong)n > (0x7FFFFFFFFFFFFFFF / 10))
+ {
+ return false;
+ }
+ n *= 10;
+ if (*p != '\0')
+ {
+ n += (int)(*p++ - '0');
+ }
+ }
+ if (number.sign)
+ {
+ n = -n;
+ if (n > 0)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (n < 0)
+ {
+ return false;
+ }
+ }
+ value = n;
+ return true;
+ }
+
+ private static unsafe bool NumberToUInt32(ref NumberBuffer number, ref uint value)
+ {
+ int i = number.scale;
+ if (i > UInt32Precision || i < number.precision || number.sign)
+ {
+ return false;
+ }
+ char* p = number.digits;
+ Debug.Assert(p != null);
+ uint n = 0;
+ while (--i >= 0)
+ {
+ if (n > (0xFFFFFFFF / 10))
+ {
+ return false;
+ }
+ n *= 10;
+ if (*p != '\0')
+ {
+ uint newN = n + (uint)(*p++ - '0');
+ // Detect an overflow here...
+ if (newN < n)
+ {
+ return false;
+ }
+ n = newN;
+ }
+ }
+ value = n;
+ return true;
+ }
+
+ private static unsafe bool NumberToUInt64(ref NumberBuffer number, ref ulong value)
+ {
+ int i = number.scale;
+ if (i > UInt64Precision || i < number.precision || number.sign)
+ {
+ return false;
+ }
+ char* p = number.digits;
+ Debug.Assert(p != null);
+ ulong n = 0;
+ while (--i >= 0)
+ {
+ if (n > (0xFFFFFFFFFFFFFFFF / 10))
+ {
+ return false;
+ }
+ n *= 10;
+ if (*p != '\0')
+ {
+ ulong newN = n + (ulong)(*p++ - '0');
+ // Detect an overflow here...
+ if (newN < n)
+ {
+ return false;
+ }
+ n = newN;
+ }
+ }
+ value = n;
+ return true;
+ }
+
+ internal static unsafe int ParseInt32(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info)
+ {
+ NumberBuffer number = default;
+ int i = 0;
+
+ StringToNumber(s, style, ref number, info, false);
+
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ if (!HexNumberToInt32(ref number, ref i))
+ {
+ throw new OverflowException(SR.Overflow_Int32);
+ }
+ }
+ else
+ {
+ if (!NumberToInt32(ref number, ref i))
+ {
+ throw new OverflowException(SR.Overflow_Int32);
+ }
+ }
+ return i;
+ }
+
+ internal static unsafe long ParseInt64(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ {
+ NumberBuffer number = default;
+ long i = 0;
+
+ StringToNumber(value, options, ref number, numfmt, false);
+
+ if ((options & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ if (!HexNumberToInt64(ref number, ref i))
+ {
+ throw new OverflowException(SR.Overflow_Int64);
+ }
+ }
+ else
+ {
+ if (!NumberToInt64(ref number, ref i))
+ {
+ throw new OverflowException(SR.Overflow_Int64);
+ }
+ }
+ return i;
+ }
+
+ internal static unsafe uint ParseUInt32(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ {
+ NumberBuffer number = default;
+ uint i = 0;
+
+ StringToNumber(value, options, ref number, numfmt, false);
+
+ if ((options & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ if (!HexNumberToUInt32(ref number, ref i))
+ {
+ throw new OverflowException(SR.Overflow_UInt32);
+ }
+ }
+ else
+ {
+ if (!NumberToUInt32(ref number, ref i))
+ {
+ throw new OverflowException(SR.Overflow_UInt32);
+ }
+ }
+
+ return i;
+ }
+
+ internal static unsafe ulong ParseUInt64(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ {
+ NumberBuffer number = default;
+ ulong i = 0;
+
+ StringToNumber(value, options, ref number, numfmt, false);
+ if ((options & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ if (!HexNumberToUInt64(ref number, ref i))
+ {
+ throw new OverflowException(SR.Overflow_UInt64);
+ }
+ }
+ else
+ {
+ if (!NumberToUInt64(ref number, ref i))
+ {
+ throw new OverflowException(SR.Overflow_UInt64);
+ }
+ }
+ return i;
+ }
+
+ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles options, ref NumberBuffer number, NumberFormatInfo numfmt, bool parseDecimal)
+ {
+ Debug.Assert(str != null);
+ Debug.Assert(strEnd != null);
+ Debug.Assert(str <= strEnd);
+
+ const int StateSign = 0x0001;
+ const int StateParens = 0x0002;
+ const int StateDigits = 0x0004;
+ const int StateNonZero = 0x0008;
+ const int StateDecimal = 0x0010;
+ const int StateCurrency = 0x0020;
+
+ number.scale = 0;
+ number.sign = false;
+ string decSep; // decimal separator from NumberFormatInfo.
+ string groupSep; // group separator from NumberFormatInfo.
+ string currSymbol = null; // currency symbol from NumberFormatInfo.
+
+ bool parsingCurrency = false;
+ if ((options & NumberStyles.AllowCurrencySymbol) != 0)
+ {
+ currSymbol = numfmt.CurrencySymbol;
+
+ // The idea here is to match the currency separators and on failure match the number separators to keep the perf of VB's IsNumeric fast.
+ // The values of decSep are setup to use the correct relevant separator (currency in the if part and decimal in the else part).
+ decSep = numfmt.CurrencyDecimalSeparator;
+ groupSep = numfmt.CurrencyGroupSeparator;
+ parsingCurrency = true;
+ }
+ else
+ {
+ decSep = numfmt.NumberDecimalSeparator;
+ groupSep = numfmt.NumberGroupSeparator;
+ }
+
+ int state = 0;
+ char* p = str;
+ char ch = p < strEnd ? *p : '\0';
+ char* next;
+
+ while (true)
+ {
+ // Eat whitespace unless we've found a sign which isn't followed by a currency symbol.
+ // "-Kr 1231.47" is legal but "- 1231.47" is not.
+ if (!IsWhite(ch) || (options & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && numfmt.NumberNegativePattern != 2)))
+ {
+ if ((((options & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, numfmt.PositiveSign)) != null || ((next = MatchChars(p, strEnd, numfmt.NegativeSign)) != null && (number.sign = true))))
+ {
+ state |= StateSign;
+ p = next - 1;
+ }
+ else if (ch == '(' && ((options & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0))
+ {
+ state |= StateSign | StateParens;
+ number.sign = true;
+ }
+ else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
+ {
+ state |= StateCurrency;
+ currSymbol = null;
+ // We already found the currency symbol. There should not be more currency symbols. Set
+ // currSymbol to NULL so that we won't search it again in the later code path.
+ p = next - 1;
+ }
+ else
+ {
+ break;
+ }
+ }
+ ch = ++p < strEnd ? *p : '\0';
+ }
+ int digCount = 0;
+ int digEnd = 0;
+ while (true)
+ {
+ if ((ch >= '0' && ch <= '9') || (((options & NumberStyles.AllowHexSpecifier) != 0) && ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))))
+ {
+ state |= StateDigits;
+
+ if (ch != '0' || (state & StateNonZero) != 0)
+ {
+ if (digCount < NumberMaxDigits)
+ {
+ number.digits[digCount++] = ch;
+ if (ch != '0' || parseDecimal)
+ {
+ digEnd = digCount;
+ }
+ }
+ if ((state & StateDecimal) == 0)
+ {
+ number.scale++;
+ }
+ state |= StateNonZero;
+ }
+ else if ((state & StateDecimal) != 0)
+ {
+ number.scale--;
+ }
+ }
+ else if (((options & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, numfmt.NumberDecimalSeparator)) != null))
+ {
+ state |= StateDecimal;
+ p = next - 1;
+ }
+ else if (((options & NumberStyles.AllowThousands) != 0) && ((state & StateDigits) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, groupSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, numfmt.NumberGroupSeparator)) != null))
+ {
+ p = next - 1;
+ }
+ else
+ {
+ break;
+ }
+ ch = ++p < strEnd ? *p : '\0';
+ }
+
+ bool negExp = false;
+ number.precision = digEnd;
+ number.digits[digEnd] = '\0';
+ if ((state & StateDigits) != 0)
+ {
+ if ((ch == 'E' || ch == 'e') && ((options & NumberStyles.AllowExponent) != 0))
+ {
+ char* temp = p;
+ ch = ++p < strEnd ? *p : '\0';
+ if ((next = MatchChars(p, strEnd, numfmt.positiveSign)) != null)
+ {
+ ch = (p = next) < strEnd ? *p : '\0';
+ }
+ else if ((next = MatchChars(p, strEnd, numfmt.negativeSign)) != null)
+ {
+ ch = (p = next) < strEnd ? *p : '\0';
+ negExp = true;
+ }
+ if (ch >= '0' && ch <= '9')
+ {
+ int exp = 0;
+ do
+ {
+ exp = exp * 10 + (ch - '0');
+ ch = ++p < strEnd ? *p : '\0';
+ if (exp > 1000)
+ {
+ exp = 9999;
+ while (ch >= '0' && ch <= '9')
+ {
+ ch = ++p < strEnd ? *p : '\0';
+ }
+ }
+ } while (ch >= '0' && ch <= '9');
+ if (negExp)
+ {
+ exp = -exp;
+ }
+ number.scale += exp;
+ }
+ else
+ {
+ p = temp;
+ ch = p < strEnd ? *p : '\0';
+ }
+ }
+ while (true)
+ {
+ if (!IsWhite(ch) || (options & NumberStyles.AllowTrailingWhite) == 0)
+ {
+ if (((options & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0)) && ((next = MatchChars(p, strEnd, numfmt.PositiveSign)) != null || (((next = MatchChars(p, strEnd, numfmt.NegativeSign)) != null) && (number.sign = true))))
+ {
+ state |= StateSign;
+ p = next - 1;
+ }
+ else if (ch == ')' && ((state & StateParens) != 0))
+ {
+ state &= ~StateParens;
+ }
+ else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null)
+ {
+ currSymbol = null;
+ p = next - 1;
+ }
+ else
+ {
+ break;
+ }
+ }
+ ch = ++p < strEnd ? *p : '\0';
+ }
+ if ((state & StateParens) == 0)
+ {
+ if ((state & StateNonZero) == 0)
+ {
+ if (!parseDecimal)
+ {
+ number.scale = 0;
+ }
+ if ((state & StateDecimal) == 0)
+ {
+ number.sign = false;
+ }
+ }
+ str = p;
+ return true;
+ }
+ }
+ str = p;
+ return false;
+ }
+
+ internal static unsafe bool TryParseInt32(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out int result)
+ {
+ NumberBuffer number = default;
+ result = 0;
+
+ if (!TryStringToNumber(s, style, ref number, info, false))
+ {
+ return false;
+ }
+
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ if (!HexNumberToInt32(ref number, ref result))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!NumberToInt32(ref number, ref result))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ internal static unsafe bool TryParseInt64(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out long result)
+ {
+ NumberBuffer number = default;
+ result = 0;
+
+ if (!TryStringToNumber(s, style, ref number, info, false))
+ {
+ return false;
+ }
+
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ if (!HexNumberToInt64(ref number, ref result))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!NumberToInt64(ref number, ref result))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ internal static unsafe bool TryParseUInt32(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out uint result)
+ {
+ NumberBuffer number = default;
+ result = 0;
+
+ if (!TryStringToNumber(s, style, ref number, info, false))
+ {
+ return false;
+ }
+
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ if (!HexNumberToUInt32(ref number, ref result))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!NumberToUInt32(ref number, ref result))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ internal static unsafe bool TryParseUInt64(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out ulong result)
+ {
+ NumberBuffer number = default;
+ result = 0;
+
+ if (!TryStringToNumber(s, style, ref number, info, false))
+ {
+ return false;
+ }
+
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ {
+ if (!HexNumberToUInt64(ref number, ref result))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!NumberToUInt64(ref number, ref result))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ internal static unsafe decimal ParseDecimal(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ {
+ NumberBuffer number = default;
+ decimal result = 0;
+
+ StringToNumber(value, options, ref number, numfmt, true);
+
+ if (!NumberBufferToDecimal(ref number, ref result))
+ {
+ throw new OverflowException(SR.Overflow_Decimal);
+ }
+ return result;
+ }
+
+ internal static unsafe double ParseDouble(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ {
+ NumberBuffer number = default;
+ double d = 0;
+
+ if (!TryStringToNumber(value, options, ref number, numfmt, false))
+ {
+ //If we failed TryStringToNumber, it may be from one of our special strings.
+ //Check the three with which we're concerned and rethrow if it's not one of
+ //those strings.
+ ReadOnlySpan<char> sTrim = value.Trim();
+ if (sTrim.EqualsOrdinal(numfmt.PositiveInfinitySymbol))
+ {
+ return double.PositiveInfinity;
+ }
+ if (sTrim.EqualsOrdinal(numfmt.NegativeInfinitySymbol))
+ {
+ return double.NegativeInfinity;
+ }
+ if (sTrim.EqualsOrdinal(numfmt.NaNSymbol))
+ {
+ return double.NaN;
+ }
+ throw new FormatException(SR.Format_InvalidString);
+ }
+
+ if (!NumberBufferToDouble(ref number, ref d))
+ {
+ throw new OverflowException(SR.Overflow_Double);
+ }
+
+ return d;
+ }
+
+ internal static unsafe float ParseSingle(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt)
+ {
+ NumberBuffer number = default;
+ double d = 0;
+
+ if (!TryStringToNumber(value, options, ref number, numfmt, false))
+ {
+ //If we failed TryStringToNumber, it may be from one of our special strings.
+ //Check the three with which we're concerned and rethrow if it's not one of
+ //those strings.
+ ReadOnlySpan<char> sTrim = value.Trim();
+ if (sTrim.EqualsOrdinal(numfmt.PositiveInfinitySymbol))
+ {
+ return float.PositiveInfinity;
+ }
+ if (sTrim.EqualsOrdinal(numfmt.NegativeInfinitySymbol))
+ {
+ return float.NegativeInfinity;
+ }
+ if (sTrim.EqualsOrdinal(numfmt.NaNSymbol))
+ {
+ return float.NaN;
+ }
+ throw new FormatException(SR.Format_InvalidString);
+ }
+
+ if (!NumberBufferToDouble(ref number, ref d))
+ {
+ throw new OverflowException(SR.Overflow_Single);
+ }
+ float castSingle = (float)d;
+ if (float.IsInfinity(castSingle))
+ {
+ throw new OverflowException(SR.Overflow_Single);
+ }
+ return castSingle;
+ }
+
+ internal static unsafe bool TryParseDecimal(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt, out decimal result)
+ {
+ NumberBuffer number = default;
+ result = 0;
+
+ if (!TryStringToNumber(value, options, ref number, numfmt, true))
+ {
+ return false;
+ }
+
+ if (!NumberBufferToDecimal(ref number, ref result))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ internal static unsafe bool TryParseDouble(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt, out double result)
+ {
+ NumberBuffer number = default;
+ result = 0;
+
+
+ if (!TryStringToNumber(value, options, ref number, numfmt, false))
+ {
+ return false;
+ }
+ if (!NumberBufferToDouble(ref number, ref result))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ internal static unsafe bool TryParseSingle(ReadOnlySpan<char> value, NumberStyles options, NumberFormatInfo numfmt, out float result)
+ {
+ NumberBuffer number = default;
+ result = 0;
+ double d = 0;
+
+ if (!TryStringToNumber(value, options, ref number, numfmt, false))
+ {
+ return false;
+ }
+ if (!NumberBufferToDouble(ref number, ref d))
+ {
+ return false;
+ }
+ float castSingle = (float)d;
+ if (float.IsInfinity(castSingle))
+ {
+ return false;
+ }
+
+ result = castSingle;
+ return true;
+ }
+
+ private static unsafe void StringToNumber(ReadOnlySpan<char> str, NumberStyles options, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal)
+ {
+ Debug.Assert(info != null);
+ fixed (char* stringPointer = &MemoryMarshal.GetReference(str))
+ {
+ char* p = stringPointer;
+ if (!ParseNumber(ref p, p + str.Length, options, ref number, info, parseDecimal)
+ || (p - stringPointer < str.Length && !TrailingZeros(str, (int)(p - stringPointer))))
+ {
+ throw new FormatException(SR.Format_InvalidString);
+ }
+ }
+ }
+
+ internal static unsafe bool TryStringToNumber(ReadOnlySpan<char> str, NumberStyles options, ref NumberBuffer number, NumberFormatInfo numfmt, bool parseDecimal)
+ {
+ Debug.Assert(numfmt != null);
+ fixed (char* stringPointer = &MemoryMarshal.GetReference(str))
+ {
+ char* p = stringPointer;
+ if (!ParseNumber(ref p, p + str.Length, options, ref number, numfmt, parseDecimal)
+ || (p - stringPointer < str.Length && !TrailingZeros(str, (int)(p - stringPointer))))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool TrailingZeros(ReadOnlySpan<char> s, int index)
+ {
+ // For compatibility, we need to allow trailing zeros at the end of a number string
+ for (int i = index; i < s.Length; i++)
+ {
+ if (s[i] != '\0')
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static unsafe char* MatchChars(char* p, char* pEnd, string str)
+ {
+ fixed (char* stringPointer = str)
+ {
+ return MatchChars(p, pEnd, stringPointer);
+ }
+ }
+
+ private static unsafe char* MatchChars(char* p, char* pEnd, char* str)
+ {
+ Debug.Assert(p != null && pEnd != null && p <= pEnd && str != null);
+
+ if (*str == '\0')
+ {
+ return null;
+ }
+
+ // We only hurt the failure case
+ // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 as a
+ // space character we use 0x20 space character instead to mean the same.
+ while (true)
+ {
+ char cp = p < pEnd ? *p : '\0';
+ if (cp != *str && !(*str == '\u00a0' && cp == '\u0020'))
+ {
+ break;
+ }
+ p++;
+ str++;
+ if (*str == '\0') return p;
+ }
+
+ return null;
+ }
+
+ private static bool IsWhite(char ch) => ch == 0x20 || (ch >= 0x09 && ch <= 0x0D);
+
+ private static bool NumberBufferToDouble(ref NumberBuffer number, ref double value)
+ {
+ double d = NumberToDouble(ref number);
+ uint e = DoubleHelper.Exponent(d);
+ ulong m = DoubleHelper.Mantissa(d);
+
+ if (e == 0x7FF)
+ {
+ return false;
+ }
+
+ if (e == 0 && m == 0)
+ {
+ d = 0;
+ }
+
+ value = d;
+ return true;
+ }
+
+ private static class DoubleHelper
+ {
+ public static unsafe uint Exponent(double d) =>
+ (*((uint*)&d + 1) >> 20) & 0x000007ff;
+
+ public static unsafe ulong Mantissa(double d) =>
+ *((ulong*)&d) & 0x000fffffffffffff;
+
+ public static unsafe bool Sign(double d) =>
+ (*((uint*)&d + 1) >> 31) != 0;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Numerics/ConstantHelper.cs b/src/System.Private.CoreLib/shared/System/Numerics/ConstantHelper.cs
new file mode 100644
index 0000000000..ea32ed3803
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Numerics/ConstantHelper.cs
@@ -0,0 +1,142 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Numerics
+{
+ internal class ConstantHelper
+ {
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Byte GetByteWithAllBitsSet()
+ {
+ Byte value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((Byte*)&value) = (Byte)0xff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static SByte GetSByteWithAllBitsSet()
+ {
+ SByte value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((SByte*)&value) = (SByte)0xff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static UInt16 GetUInt16WithAllBitsSet()
+ {
+ UInt16 value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((UInt16*)&value) = (UInt16)0xffff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Int16 GetInt16WithAllBitsSet()
+ {
+ Int16 value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((Int16*)&value) = (Int16)0xffff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static UInt32 GetUInt32WithAllBitsSet()
+ {
+ UInt32 value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((UInt32*)&value) = (UInt32)0xffffffff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Int32 GetInt32WithAllBitsSet()
+ {
+ Int32 value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((Int32*)&value) = (Int32)0xffffffff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static UInt64 GetUInt64WithAllBitsSet()
+ {
+ UInt64 value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((UInt64*)&value) = (UInt64)0xffffffffffffffff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Int64 GetInt64WithAllBitsSet()
+ {
+ Int64 value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((Int64*)&value) = (Int64)0xffffffffffffffff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Single GetSingleWithAllBitsSet()
+ {
+ Single value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((Int32*)&value) = (Int32)0xffffffff;
+ }
+ }
+ return value;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Double GetDoubleWithAllBitsSet()
+ {
+ Double value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((Int64*)&value) = (Int64)0xffffffffffffffff;
+ }
+ }
+ return value;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Numerics/ConstantHelper.tt b/src/System.Private.CoreLib/shared/System/Numerics/ConstantHelper.tt
new file mode 100644
index 0000000000..4b1c677574
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Numerics/ConstantHelper.tt
@@ -0,0 +1,60 @@
+<#@ template debug="true" hostSpecific="true" #>
+<#@ output extension=".cs" #>
+<#@ Assembly Name="System.Core.dll" #>
+<#@ Assembly Name="System.Xml.dll" #>
+<#@ Assembly Name="System.Xml.Linq.dll" #>
+<#@ Assembly Name="System.Windows.Forms.dll" #>
+<#@ import namespace="System" #>
+<#@ import namespace="System.IO" #>
+<#@ import namespace="System.Diagnostics" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Xml.Linq" #>
+<#@ import namespace="System.Collections" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ import namespace="System.Runtime.InteropServices" #>
+<#@ include file="GenerationConfig.ttinclude" #><# GenerateCopyrightHeader(); #>
+
+using System.Runtime.CompilerServices;
+
+namespace System.Numerics
+{
+ internal class ConstantHelper
+ {
+<# foreach (var type in supportedTypes)
+ {
+ string hexValue = "0x" + new string('f', Marshal.SizeOf(type) * 2);
+#>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static <#= type.Name #> Get<#= type.Name #>WithAllBitsSet()
+ {
+ <#= type.Name #> value = 0;
+ unsafe
+ {
+ unchecked
+ {
+ *((<#= GetIntegralEquivalent(type).Name #>*)&value) = (<#=GetIntegralEquivalent(type).Name#>)<#= hexValue #>;
+ }
+ }
+ return value;
+ }
+<#
+ }
+#>
+ }
+}<#+
+ public Type GetIntegralEquivalent(Type type)
+ {
+ if (type == typeof(Single))
+ {
+ return typeof(Int32);
+ }
+ else if (type == typeof(double))
+ {
+ return typeof(Int64);
+ }
+ else
+ {
+ return type;
+ }
+ }
+#> \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Numerics/GenerationConfig.ttinclude b/src/System.Private.CoreLib/shared/System/Numerics/GenerationConfig.ttinclude
new file mode 100644
index 0000000000..a21188e51b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Numerics/GenerationConfig.ttinclude
@@ -0,0 +1,173 @@
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ import namespace="System.Text" #>
+<#+
+ /* This file includes static data used as compilation configuration for the rest of the code generation.
+ It is shared here to ensure that all generated code compiles with the same constants and configurations. */
+
+ // The set of supported numeric types to compile
+ public static Type[] supportedTypes = new[]
+ {
+ typeof(Byte), typeof(SByte), typeof(UInt16), typeof(Int16),
+ typeof(UInt32), typeof(Int32), typeof(UInt64), typeof(Int64),
+ typeof(Single), typeof(Double)
+ };
+
+ // The set of unsigned types, a subset of the above. Used for excluding from certain methods, i.e. Abs and Negate
+ public static Type[] unsignedTypes = new[]
+ {
+ typeof(Byte), typeof(UInt16), typeof(UInt32), typeof(UInt64)
+ };
+
+ public static Type[] integralTypes = new[]
+ {
+ typeof(Byte), typeof(SByte), typeof(UInt16), typeof(Int16),
+ typeof(UInt32), typeof(Int32), typeof(UInt64), typeof(Int64)
+ };
+
+ public static Type[] nonClsCompliantTypes = new[]
+ {
+ typeof(SByte), typeof(UInt16),
+ typeof(UInt32), typeof(UInt64)
+ };
+
+ // The total register size, in bytes. 16 for SSE2, 32 for AVX, 64 for AVX512
+ public static int totalSize = 16;
+
+ /* General template helper procedures */
+
+ // Returns the constructed register field name for the given type and index.
+ public string GetRegisterFieldName(Type t, int index)
+ {
+ return "register." + t.Name.ToLowerInvariant() + "_" + index;
+ }
+
+ // Returns the number of fields for a given type, based on the current configuration's register size
+ public int GetNumFields(Type t, int totalSize)
+ {
+ return totalSize / Marshal.SizeOf(t);
+ }
+
+ public static HashSet<Type> WidenableTypes { get; private set; } = new HashSet<Type>()
+ {
+ typeof(byte),
+ typeof(ushort),
+ typeof(uint),
+ typeof(sbyte),
+ typeof(short),
+ typeof(int),
+ typeof(float),
+ };
+
+ private static Dictionary<Type, Type> s_widenTargets = new Dictionary<Type, Type>()
+ {
+ { typeof(byte), typeof(ushort) },
+ { typeof(ushort), typeof(uint) },
+ { typeof(uint), typeof(ulong) },
+ { typeof(sbyte), typeof(short) },
+ { typeof(short), typeof(int) },
+ { typeof(int), typeof(long) },
+ { typeof(float), typeof(double) },
+ };
+
+ public Type GetWidenTarget(Type t)
+ {
+ Type target;
+ if (!s_widenTargets.TryGetValue(t, out target))
+ {
+ throw new InvalidOperationException("No widen target for " + t.Name);
+ }
+
+ return target;
+ }
+
+ public static HashSet<Type> NarrowableTypes { get; private set; } = new HashSet<Type>()
+ {
+ typeof(ushort),
+ typeof(uint),
+ typeof(ulong),
+ typeof(short),
+ typeof(int),
+ typeof(long),
+ typeof(double),
+ };
+
+ private static Dictionary<Type, Type> s_narrowTargets = new Dictionary<Type, Type>()
+ {
+ { typeof(ulong), typeof(uint) },
+ { typeof(uint), typeof(ushort) },
+ { typeof(ushort), typeof(byte) },
+ { typeof(long), typeof(int) },
+ { typeof(int), typeof(short) },
+ { typeof(short), typeof(sbyte) },
+ { typeof(double), typeof(float) },
+ };
+
+ public Type GetNarrowTarget(Type t)
+ {
+ Type target;
+ if (!s_narrowTargets.TryGetValue(t, out target))
+ {
+ throw new InvalidOperationException("No narrow target for " + t.Name);
+ }
+
+ return target;
+ }
+
+ public static List<KeyValuePair<Type, Type>> SameSizeConversionPairs = new List<KeyValuePair<Type, Type>>()
+ {
+ new KeyValuePair<Type, Type>(typeof(int), typeof(float)),
+ new KeyValuePair<Type, Type>(typeof(uint), typeof(float)),
+ new KeyValuePair<Type, Type>(typeof(long), typeof(double)),
+ new KeyValuePair<Type, Type>(typeof(ulong), typeof(double)),
+ new KeyValuePair<Type, Type>(typeof(float), typeof(int)),
+ new KeyValuePair<Type, Type>(typeof(float), typeof(uint)),
+ new KeyValuePair<Type, Type>(typeof(double), typeof(long)),
+ new KeyValuePair<Type, Type>(typeof(double), typeof(ulong)),
+ };
+
+ public void GenerateCopyrightHeader()
+ {
+#>// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+<#+
+ }
+
+ public string GenerateIfStatementHeader(Type type)
+ {
+ string keyword = (type == supportedTypes[0]) ? "if" : "else if";
+ return string.Format("{0} (typeof(T) == typeof({1}))", keyword, type.Name);
+ }
+
+ public string GenerateIfStatementHeader(Type type, IEnumerable<Type> allTypes)
+ {
+ string keyword = (type == allTypes.ToArray()[0]) ? "if" : "else if";
+ return string.Format("{0} (typeof(T) == typeof({1}))", keyword, type.Name);
+ }
+
+ public string MakeTypeComparisonCondition(Type type)
+ {
+ return string.Format("(typeof(T) == typeof({0}))", type.Name);
+ }
+
+ public string GenerateIfConditionAllTypes(IEnumerable<Type> allTypes)
+ {
+ StringBuilder sbuilder = new StringBuilder();
+ bool firstTime = true;
+ foreach (var type in allTypes)
+ {
+ if (firstTime)
+ {
+ sbuilder.Append("if (").Append(MakeTypeComparisonCondition(type));
+ firstTime = false;
+ }
+ else
+ {
+ sbuilder.AppendLine().Append(" || ").Append(MakeTypeComparisonCondition(type));
+ }
+ }
+ sbuilder.Append(")");
+ return sbuilder.ToString();
+ }
+#>
diff --git a/src/System.Private.CoreLib/shared/System/Numerics/Register.cs b/src/System.Private.CoreLib/shared/System/Numerics/Register.cs
new file mode 100644
index 0000000000..a27e922b9d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Numerics/Register.cs
@@ -0,0 +1,172 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Numerics
+{
+ /// <summary>
+ /// A structure describing the layout of an SSE2-sized register.
+ /// Contains overlapping fields representing the set of valid numeric types.
+ /// Allows the generic Vector'T struct to contain an explicit field layout.
+ /// </summary>
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct Register
+ {
+ #region Internal Storage Fields
+ // Internal System.Byte Fields
+ [FieldOffset(0)]
+ internal Byte byte_0;
+ [FieldOffset(1)]
+ internal Byte byte_1;
+ [FieldOffset(2)]
+ internal Byte byte_2;
+ [FieldOffset(3)]
+ internal Byte byte_3;
+ [FieldOffset(4)]
+ internal Byte byte_4;
+ [FieldOffset(5)]
+ internal Byte byte_5;
+ [FieldOffset(6)]
+ internal Byte byte_6;
+ [FieldOffset(7)]
+ internal Byte byte_7;
+ [FieldOffset(8)]
+ internal Byte byte_8;
+ [FieldOffset(9)]
+ internal Byte byte_9;
+ [FieldOffset(10)]
+ internal Byte byte_10;
+ [FieldOffset(11)]
+ internal Byte byte_11;
+ [FieldOffset(12)]
+ internal Byte byte_12;
+ [FieldOffset(13)]
+ internal Byte byte_13;
+ [FieldOffset(14)]
+ internal Byte byte_14;
+ [FieldOffset(15)]
+ internal Byte byte_15;
+
+ // Internal System.SByte Fields
+ [FieldOffset(0)]
+ internal SByte sbyte_0;
+ [FieldOffset(1)]
+ internal SByte sbyte_1;
+ [FieldOffset(2)]
+ internal SByte sbyte_2;
+ [FieldOffset(3)]
+ internal SByte sbyte_3;
+ [FieldOffset(4)]
+ internal SByte sbyte_4;
+ [FieldOffset(5)]
+ internal SByte sbyte_5;
+ [FieldOffset(6)]
+ internal SByte sbyte_6;
+ [FieldOffset(7)]
+ internal SByte sbyte_7;
+ [FieldOffset(8)]
+ internal SByte sbyte_8;
+ [FieldOffset(9)]
+ internal SByte sbyte_9;
+ [FieldOffset(10)]
+ internal SByte sbyte_10;
+ [FieldOffset(11)]
+ internal SByte sbyte_11;
+ [FieldOffset(12)]
+ internal SByte sbyte_12;
+ [FieldOffset(13)]
+ internal SByte sbyte_13;
+ [FieldOffset(14)]
+ internal SByte sbyte_14;
+ [FieldOffset(15)]
+ internal SByte sbyte_15;
+
+ // Internal System.UInt16 Fields
+ [FieldOffset(0)]
+ internal UInt16 uint16_0;
+ [FieldOffset(2)]
+ internal UInt16 uint16_1;
+ [FieldOffset(4)]
+ internal UInt16 uint16_2;
+ [FieldOffset(6)]
+ internal UInt16 uint16_3;
+ [FieldOffset(8)]
+ internal UInt16 uint16_4;
+ [FieldOffset(10)]
+ internal UInt16 uint16_5;
+ [FieldOffset(12)]
+ internal UInt16 uint16_6;
+ [FieldOffset(14)]
+ internal UInt16 uint16_7;
+
+ // Internal System.Int16 Fields
+ [FieldOffset(0)]
+ internal Int16 int16_0;
+ [FieldOffset(2)]
+ internal Int16 int16_1;
+ [FieldOffset(4)]
+ internal Int16 int16_2;
+ [FieldOffset(6)]
+ internal Int16 int16_3;
+ [FieldOffset(8)]
+ internal Int16 int16_4;
+ [FieldOffset(10)]
+ internal Int16 int16_5;
+ [FieldOffset(12)]
+ internal Int16 int16_6;
+ [FieldOffset(14)]
+ internal Int16 int16_7;
+
+ // Internal System.UInt32 Fields
+ [FieldOffset(0)]
+ internal UInt32 uint32_0;
+ [FieldOffset(4)]
+ internal UInt32 uint32_1;
+ [FieldOffset(8)]
+ internal UInt32 uint32_2;
+ [FieldOffset(12)]
+ internal UInt32 uint32_3;
+
+ // Internal System.Int32 Fields
+ [FieldOffset(0)]
+ internal Int32 int32_0;
+ [FieldOffset(4)]
+ internal Int32 int32_1;
+ [FieldOffset(8)]
+ internal Int32 int32_2;
+ [FieldOffset(12)]
+ internal Int32 int32_3;
+
+ // Internal System.UInt64 Fields
+ [FieldOffset(0)]
+ internal UInt64 uint64_0;
+ [FieldOffset(8)]
+ internal UInt64 uint64_1;
+
+ // Internal System.Int64 Fields
+ [FieldOffset(0)]
+ internal Int64 int64_0;
+ [FieldOffset(8)]
+ internal Int64 int64_1;
+
+ // Internal System.Single Fields
+ [FieldOffset(0)]
+ internal Single single_0;
+ [FieldOffset(4)]
+ internal Single single_1;
+ [FieldOffset(8)]
+ internal Single single_2;
+ [FieldOffset(12)]
+ internal Single single_3;
+
+ // Internal System.Double Fields
+ [FieldOffset(0)]
+ internal Double double_0;
+ [FieldOffset(8)]
+ internal Double double_1;
+
+ #endregion Internal Storage Fields
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Numerics/Register.tt b/src/System.Private.CoreLib/shared/System/Numerics/Register.tt
new file mode 100644
index 0000000000..a9de3b9748
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Numerics/Register.tt
@@ -0,0 +1,46 @@
+<#@ template debug="true" hostSpecific="true" #>
+<#@ output extension=".cs" #>
+<#@ Assembly Name="System.Core.dll" #>
+<#@ Assembly Name="System.Xml.dll" #>
+<#@ import namespace="System" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Runtime.InteropServices" #>
+<#@ import namespace="System.Diagnostics" #>
+<#@ include file="GenerationConfig.ttinclude" #><# GenerateCopyrightHeader(); #>
+
+using System.Runtime.InteropServices;
+
+namespace System.Numerics
+{
+ /// <summary>
+ /// A structure describing the layout of an SSE2-sized register.
+ /// Contains overlapping fields representing the set of valid numeric types.
+ /// Allows the generic Vector'T struct to contain an explicit field layout.
+ /// </summary>
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct Register
+ {
+ #region Internal Storage Fields
+<#
+ foreach (var type in supportedTypes)
+ {
+ Debug.Assert(
+ totalSize % Marshal.SizeOf(type) == 0,
+ "The size of supported structs must be a factor of the supported register size.");
+#>
+ // Internal <#= type.FullName #> Fields
+<#
+ for (int g = 0; g < totalSize / Marshal.SizeOf(type); g++)
+ {
+#>
+ [FieldOffset(<#=Marshal.SizeOf(type) * g#>)]
+ internal <#=type.Name#> <#= type.Name.ToLowerInvariant() + "_" + g #>;
+<#
+ }
+#>
+
+<#
+ }
+#> #endregion Internal Storage Fields
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Numerics/Vector.cs b/src/System.Private.CoreLib/shared/System/Numerics/Vector.cs
new file mode 100644
index 0000000000..42f86d9297
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Numerics/Vector.cs
@@ -0,0 +1,5538 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if netcoreapp
+using Internal.Runtime.CompilerServices;
+#endif
+using System.Globalization;
+using System.Numerics.Hashing;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Numerics
+{
+ /* Note: The following patterns are used throughout the code here and are described here
+ *
+ * PATTERN:
+ * if (typeof(T) == typeof(Int32)) { ... }
+ * else if (typeof(T) == typeof(Single)) { ... }
+ * EXPLANATION:
+ * At runtime, each instantiation of Vector<T> will be type-specific, and each of these typeof blocks will be eliminated,
+ * as typeof(T) is a (JIT) compile-time constant for each instantiation. This design was chosen to eliminate any overhead from
+ * delegates and other patterns.
+ *
+ * PATTERN:
+ * if (Vector.IsHardwareAccelerated) { ... }
+ * else { ... }
+ * EXPLANATION
+ * This pattern solves two problems:
+ * 1. Allows us to unroll loops when we know the size (when no hardware acceleration is present)
+ * 2. Allows reflection to work:
+ * - If a method is called via reflection, it will not be "intrinsified", which would cause issues if we did
+ * not provide an implementation for that case (i.e. if it only included a case which assumed 16-byte registers)
+ * (NOTE: It is assumed that Vector.IsHardwareAccelerated will be a compile-time constant, eliminating these checks
+ * from the JIT'd code.)
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /// <summary>
+ /// A structure that represents a single Vector. The count of this Vector is fixed but CPU register dependent.
+ /// This struct only supports numerical types. This type is intended to be used as a building block for vectorizing
+ /// large algorithms. This type is immutable, individual elements cannot be modified.
+ /// </summary>
+ [Intrinsic]
+ public struct Vector<T> : IEquatable<Vector<T>>, IFormattable where T : struct
+ {
+ #region Fields
+ private Register register;
+ #endregion Fields
+
+ #region Static Members
+ /// <summary>
+ /// Returns the number of elements stored in the vector. This value is hardware dependent.
+ /// </summary>
+ public static int Count
+ {
+ [Intrinsic]
+ get
+ {
+ return s_count;
+ }
+ }
+ private static readonly int s_count = InitializeCount();
+
+ /// <summary>
+ /// Returns a vector containing all zeroes.
+ /// </summary>
+ public static Vector<T> Zero
+ {
+ [Intrinsic]
+ get
+ {
+ return s_zero;
+ }
+ }
+ private static readonly Vector<T> s_zero = new Vector<T>();
+
+ /// <summary>
+ /// Returns a vector containing all ones.
+ /// </summary>
+ public static Vector<T> One
+ {
+ [Intrinsic]
+ get
+ {
+ return s_one;
+ }
+ }
+ private static readonly Vector<T> s_one = new Vector<T>(GetOneValue());
+
+ internal static Vector<T> AllOnes { get { return s_allOnes; } }
+ private static readonly Vector<T> s_allOnes = new Vector<T>(GetAllBitsSetValue());
+ #endregion Static Members
+
+ #region Static Initialization
+ private struct VectorSizeHelper
+ {
+ internal Vector<T> _placeholder;
+ internal byte _byte;
+ }
+
+ // Calculates the size of this struct in bytes, by computing the offset of a field in a structure
+ private static unsafe int InitializeCount()
+ {
+ VectorSizeHelper vsh;
+ byte* vectorBase = &vsh._placeholder.register.byte_0;
+ byte* byteBase = &vsh._byte;
+ int vectorSizeInBytes = (int)(byteBase - vectorBase);
+
+ int typeSizeInBytes = -1;
+ if (typeof(T) == typeof(Byte))
+ {
+ typeSizeInBytes = sizeof(Byte);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ typeSizeInBytes = sizeof(SByte);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ typeSizeInBytes = sizeof(UInt16);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ typeSizeInBytes = sizeof(Int16);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ typeSizeInBytes = sizeof(UInt32);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ typeSizeInBytes = sizeof(Int32);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ typeSizeInBytes = sizeof(UInt64);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ typeSizeInBytes = sizeof(Int64);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ typeSizeInBytes = sizeof(Single);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ typeSizeInBytes = sizeof(Double);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+
+ return vectorSizeInBytes / typeSizeInBytes;
+ }
+ #endregion Static Initialization
+
+ #region Constructors
+ /// <summary>
+ /// Constructs a vector whose components are all <code>value</code>
+ /// </summary>
+ [Intrinsic]
+ public unsafe Vector(T value)
+ : this()
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ fixed (Byte* basePtr = &this.register.byte_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Byte)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ fixed (SByte* basePtr = &this.register.sbyte_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (SByte)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ fixed (UInt16* basePtr = &this.register.uint16_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (UInt16)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ fixed (Int16* basePtr = &this.register.int16_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Int16)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ fixed (UInt32* basePtr = &this.register.uint32_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (UInt32)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ fixed (Int32* basePtr = &this.register.int32_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Int32)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ fixed (UInt64* basePtr = &this.register.uint64_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (UInt64)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ fixed (Int64* basePtr = &this.register.int64_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Int64)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ fixed (Single* basePtr = &this.register.single_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Single)(object)value;
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ fixed (Double* basePtr = &this.register.double_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Double)(object)value;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ register.byte_0 = (Byte)(object)value;
+ register.byte_1 = (Byte)(object)value;
+ register.byte_2 = (Byte)(object)value;
+ register.byte_3 = (Byte)(object)value;
+ register.byte_4 = (Byte)(object)value;
+ register.byte_5 = (Byte)(object)value;
+ register.byte_6 = (Byte)(object)value;
+ register.byte_7 = (Byte)(object)value;
+ register.byte_8 = (Byte)(object)value;
+ register.byte_9 = (Byte)(object)value;
+ register.byte_10 = (Byte)(object)value;
+ register.byte_11 = (Byte)(object)value;
+ register.byte_12 = (Byte)(object)value;
+ register.byte_13 = (Byte)(object)value;
+ register.byte_14 = (Byte)(object)value;
+ register.byte_15 = (Byte)(object)value;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ register.sbyte_0 = (SByte)(object)value;
+ register.sbyte_1 = (SByte)(object)value;
+ register.sbyte_2 = (SByte)(object)value;
+ register.sbyte_3 = (SByte)(object)value;
+ register.sbyte_4 = (SByte)(object)value;
+ register.sbyte_5 = (SByte)(object)value;
+ register.sbyte_6 = (SByte)(object)value;
+ register.sbyte_7 = (SByte)(object)value;
+ register.sbyte_8 = (SByte)(object)value;
+ register.sbyte_9 = (SByte)(object)value;
+ register.sbyte_10 = (SByte)(object)value;
+ register.sbyte_11 = (SByte)(object)value;
+ register.sbyte_12 = (SByte)(object)value;
+ register.sbyte_13 = (SByte)(object)value;
+ register.sbyte_14 = (SByte)(object)value;
+ register.sbyte_15 = (SByte)(object)value;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ register.uint16_0 = (UInt16)(object)value;
+ register.uint16_1 = (UInt16)(object)value;
+ register.uint16_2 = (UInt16)(object)value;
+ register.uint16_3 = (UInt16)(object)value;
+ register.uint16_4 = (UInt16)(object)value;
+ register.uint16_5 = (UInt16)(object)value;
+ register.uint16_6 = (UInt16)(object)value;
+ register.uint16_7 = (UInt16)(object)value;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ register.int16_0 = (Int16)(object)value;
+ register.int16_1 = (Int16)(object)value;
+ register.int16_2 = (Int16)(object)value;
+ register.int16_3 = (Int16)(object)value;
+ register.int16_4 = (Int16)(object)value;
+ register.int16_5 = (Int16)(object)value;
+ register.int16_6 = (Int16)(object)value;
+ register.int16_7 = (Int16)(object)value;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ register.uint32_0 = (UInt32)(object)value;
+ register.uint32_1 = (UInt32)(object)value;
+ register.uint32_2 = (UInt32)(object)value;
+ register.uint32_3 = (UInt32)(object)value;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ register.int32_0 = (Int32)(object)value;
+ register.int32_1 = (Int32)(object)value;
+ register.int32_2 = (Int32)(object)value;
+ register.int32_3 = (Int32)(object)value;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ register.uint64_0 = (UInt64)(object)value;
+ register.uint64_1 = (UInt64)(object)value;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ register.int64_0 = (Int64)(object)value;
+ register.int64_1 = (Int64)(object)value;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ register.single_0 = (Single)(object)value;
+ register.single_1 = (Single)(object)value;
+ register.single_2 = (Single)(object)value;
+ register.single_3 = (Single)(object)value;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ register.double_0 = (Double)(object)value;
+ register.double_1 = (Double)(object)value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Constructs a vector from the given array. The size of the given array must be at least Vector'T.Count.
+ /// </summary>
+ [Intrinsic]
+ public unsafe Vector(T[] values) : this(values, 0) { }
+
+ /// <summary>
+ /// Constructs a vector from the given array, starting from the given index.
+ /// The array must contain at least Vector'T.Count from the given index.
+ /// </summary>
+ public unsafe Vector(T[] values, int index)
+ : this()
+ {
+ if (values == null)
+ {
+ // Match the JIT's exception type here. For perf, a NullReference is thrown instead of an ArgumentNull.
+ throw new NullReferenceException(SR.Arg_NullArgumentNullRef);
+ }
+ if (index < 0 || (values.Length - index) < Count)
+ {
+ throw new IndexOutOfRangeException(SR.Format(SR.Arg_InsufficientNumberOfElements, Vector<T>.Count, nameof(values)));
+ }
+
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ fixed (Byte* basePtr = &this.register.byte_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Byte)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ fixed (SByte* basePtr = &this.register.sbyte_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (SByte)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ fixed (UInt16* basePtr = &this.register.uint16_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (UInt16)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ fixed (Int16* basePtr = &this.register.int16_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Int16)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ fixed (UInt32* basePtr = &this.register.uint32_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (UInt32)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ fixed (Int32* basePtr = &this.register.int32_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Int32)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ fixed (UInt64* basePtr = &this.register.uint64_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (UInt64)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ fixed (Int64* basePtr = &this.register.int64_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Int64)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ fixed (Single* basePtr = &this.register.single_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Single)(object)values[g + index];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ fixed (Double* basePtr = &this.register.double_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (Double)(object)values[g + index];
+ }
+ }
+ }
+ }
+ else
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ fixed (Byte* basePtr = &this.register.byte_0)
+ {
+ *(basePtr + 0) = (Byte)(object)values[0 + index];
+ *(basePtr + 1) = (Byte)(object)values[1 + index];
+ *(basePtr + 2) = (Byte)(object)values[2 + index];
+ *(basePtr + 3) = (Byte)(object)values[3 + index];
+ *(basePtr + 4) = (Byte)(object)values[4 + index];
+ *(basePtr + 5) = (Byte)(object)values[5 + index];
+ *(basePtr + 6) = (Byte)(object)values[6 + index];
+ *(basePtr + 7) = (Byte)(object)values[7 + index];
+ *(basePtr + 8) = (Byte)(object)values[8 + index];
+ *(basePtr + 9) = (Byte)(object)values[9 + index];
+ *(basePtr + 10) = (Byte)(object)values[10 + index];
+ *(basePtr + 11) = (Byte)(object)values[11 + index];
+ *(basePtr + 12) = (Byte)(object)values[12 + index];
+ *(basePtr + 13) = (Byte)(object)values[13 + index];
+ *(basePtr + 14) = (Byte)(object)values[14 + index];
+ *(basePtr + 15) = (Byte)(object)values[15 + index];
+ }
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ fixed (SByte* basePtr = &this.register.sbyte_0)
+ {
+ *(basePtr + 0) = (SByte)(object)values[0 + index];
+ *(basePtr + 1) = (SByte)(object)values[1 + index];
+ *(basePtr + 2) = (SByte)(object)values[2 + index];
+ *(basePtr + 3) = (SByte)(object)values[3 + index];
+ *(basePtr + 4) = (SByte)(object)values[4 + index];
+ *(basePtr + 5) = (SByte)(object)values[5 + index];
+ *(basePtr + 6) = (SByte)(object)values[6 + index];
+ *(basePtr + 7) = (SByte)(object)values[7 + index];
+ *(basePtr + 8) = (SByte)(object)values[8 + index];
+ *(basePtr + 9) = (SByte)(object)values[9 + index];
+ *(basePtr + 10) = (SByte)(object)values[10 + index];
+ *(basePtr + 11) = (SByte)(object)values[11 + index];
+ *(basePtr + 12) = (SByte)(object)values[12 + index];
+ *(basePtr + 13) = (SByte)(object)values[13 + index];
+ *(basePtr + 14) = (SByte)(object)values[14 + index];
+ *(basePtr + 15) = (SByte)(object)values[15 + index];
+ }
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ fixed (UInt16* basePtr = &this.register.uint16_0)
+ {
+ *(basePtr + 0) = (UInt16)(object)values[0 + index];
+ *(basePtr + 1) = (UInt16)(object)values[1 + index];
+ *(basePtr + 2) = (UInt16)(object)values[2 + index];
+ *(basePtr + 3) = (UInt16)(object)values[3 + index];
+ *(basePtr + 4) = (UInt16)(object)values[4 + index];
+ *(basePtr + 5) = (UInt16)(object)values[5 + index];
+ *(basePtr + 6) = (UInt16)(object)values[6 + index];
+ *(basePtr + 7) = (UInt16)(object)values[7 + index];
+ }
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ fixed (Int16* basePtr = &this.register.int16_0)
+ {
+ *(basePtr + 0) = (Int16)(object)values[0 + index];
+ *(basePtr + 1) = (Int16)(object)values[1 + index];
+ *(basePtr + 2) = (Int16)(object)values[2 + index];
+ *(basePtr + 3) = (Int16)(object)values[3 + index];
+ *(basePtr + 4) = (Int16)(object)values[4 + index];
+ *(basePtr + 5) = (Int16)(object)values[5 + index];
+ *(basePtr + 6) = (Int16)(object)values[6 + index];
+ *(basePtr + 7) = (Int16)(object)values[7 + index];
+ }
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ fixed (UInt32* basePtr = &this.register.uint32_0)
+ {
+ *(basePtr + 0) = (UInt32)(object)values[0 + index];
+ *(basePtr + 1) = (UInt32)(object)values[1 + index];
+ *(basePtr + 2) = (UInt32)(object)values[2 + index];
+ *(basePtr + 3) = (UInt32)(object)values[3 + index];
+ }
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ fixed (Int32* basePtr = &this.register.int32_0)
+ {
+ *(basePtr + 0) = (Int32)(object)values[0 + index];
+ *(basePtr + 1) = (Int32)(object)values[1 + index];
+ *(basePtr + 2) = (Int32)(object)values[2 + index];
+ *(basePtr + 3) = (Int32)(object)values[3 + index];
+ }
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ fixed (UInt64* basePtr = &this.register.uint64_0)
+ {
+ *(basePtr + 0) = (UInt64)(object)values[0 + index];
+ *(basePtr + 1) = (UInt64)(object)values[1 + index];
+ }
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ fixed (Int64* basePtr = &this.register.int64_0)
+ {
+ *(basePtr + 0) = (Int64)(object)values[0 + index];
+ *(basePtr + 1) = (Int64)(object)values[1 + index];
+ }
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ fixed (Single* basePtr = &this.register.single_0)
+ {
+ *(basePtr + 0) = (Single)(object)values[0 + index];
+ *(basePtr + 1) = (Single)(object)values[1 + index];
+ *(basePtr + 2) = (Single)(object)values[2 + index];
+ *(basePtr + 3) = (Single)(object)values[3 + index];
+ }
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ fixed (Double* basePtr = &this.register.double_0)
+ {
+ *(basePtr + 0) = (Double)(object)values[0 + index];
+ *(basePtr + 1) = (Double)(object)values[1 + index];
+ }
+ }
+ }
+ }
+
+#pragma warning disable 3001 // void* is not a CLS-Compliant argument type
+ internal unsafe Vector(void* dataPointer) : this(dataPointer, 0) { }
+#pragma warning restore 3001 // void* is not a CLS-Compliant argument type
+
+#pragma warning disable 3001 // void* is not a CLS-Compliant argument type
+ // Implemented with offset if this API ever becomes public; an offset of 0 is used internally.
+ internal unsafe Vector(void* dataPointer, int offset)
+ : this()
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* castedPtr = (Byte*)dataPointer;
+ castedPtr += offset;
+ fixed (Byte* registerBase = &this.register.byte_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* castedPtr = (SByte*)dataPointer;
+ castedPtr += offset;
+ fixed (SByte* registerBase = &this.register.sbyte_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* castedPtr = (UInt16*)dataPointer;
+ castedPtr += offset;
+ fixed (UInt16* registerBase = &this.register.uint16_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* castedPtr = (Int16*)dataPointer;
+ castedPtr += offset;
+ fixed (Int16* registerBase = &this.register.int16_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* castedPtr = (UInt32*)dataPointer;
+ castedPtr += offset;
+ fixed (UInt32* registerBase = &this.register.uint32_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* castedPtr = (Int32*)dataPointer;
+ castedPtr += offset;
+ fixed (Int32* registerBase = &this.register.int32_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* castedPtr = (UInt64*)dataPointer;
+ castedPtr += offset;
+ fixed (UInt64* registerBase = &this.register.uint64_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* castedPtr = (Int64*)dataPointer;
+ castedPtr += offset;
+ fixed (Int64* registerBase = &this.register.int64_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* castedPtr = (Single*)dataPointer;
+ castedPtr += offset;
+ fixed (Single* registerBase = &this.register.single_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* castedPtr = (Double*)dataPointer;
+ castedPtr += offset;
+ fixed (Double* registerBase = &this.register.double_0)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+#pragma warning restore 3001 // void* is not a CLS-Compliant argument type
+
+ private Vector(ref Register existingRegister)
+ {
+ this.register = existingRegister;
+ }
+
+#if netcoreapp
+ /// <summary>
+ /// Constructs a vector from the given span. The span must contain at least Vector'T.Count elements.
+ /// </summary>
+ public Vector(Span<T> values)
+ : this()
+ {
+ if ((typeof(T) == typeof(Byte))
+ || (typeof(T) == typeof(SByte))
+ || (typeof(T) == typeof(UInt16))
+ || (typeof(T) == typeof(Int16))
+ || (typeof(T) == typeof(UInt32))
+ || (typeof(T) == typeof(Int32))
+ || (typeof(T) == typeof(UInt64))
+ || (typeof(T) == typeof(Int64))
+ || (typeof(T) == typeof(Single))
+ || (typeof(T) == typeof(Double)))
+ {
+ if (values.Length < Count)
+ {
+ throw new IndexOutOfRangeException(SR.Format(SR.Arg_InsufficientNumberOfElements, Vector<T>.Count, nameof(values)));
+ }
+ this = Unsafe.ReadUnaligned<Vector<T>>(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)));
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+#endif
+ #endregion Constructors
+
+ #region Public Instance Methods
+ /// <summary>
+ /// Copies the vector to the given destination array. The destination array must be at least size Vector'T.Count.
+ /// </summary>
+ /// <param name="destination">The destination array which the values are copied into</param>
+ /// <exception cref="ArgumentNullException">If the destination array is null</exception>
+ /// <exception cref="ArgumentException">If number of elements in source vector is greater than those available in destination array</exception>
+ [Intrinsic]
+ public unsafe void CopyTo(T[] destination)
+ {
+ CopyTo(destination, 0);
+ }
+
+ /// <summary>
+ /// Copies the vector to the given destination array. The destination array must be at least size Vector'T.Count.
+ /// </summary>
+ /// <param name="destination">The destination array which the values are copied into</param>
+ /// <param name="startIndex">The index to start copying to</param>
+ /// <exception cref="ArgumentNullException">If the destination array is null</exception>
+ /// <exception cref="ArgumentOutOfRangeException">If index is greater than end of the array or index is less than zero</exception>
+ /// <exception cref="ArgumentException">If number of elements in source vector is greater than those available in destination array</exception>
+ [Intrinsic]
+ public unsafe void CopyTo(T[] destination, int startIndex)
+ {
+ if (destination == null)
+ {
+ // Match the JIT's exception type here. For perf, a NullReference is thrown instead of an ArgumentNull.
+ throw new NullReferenceException(SR.Arg_NullArgumentNullRef);
+ }
+ if (startIndex < 0 || startIndex >= destination.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.Format(SR.Arg_ArgumentOutOfRangeException, startIndex));
+ }
+ if ((destination.Length - startIndex) < Count)
+ {
+ throw new ArgumentException(SR.Format(SR.Arg_ElementsInSourceIsGreaterThanDestination, startIndex));
+ }
+
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte[] byteArray = (Byte[])(object)destination;
+ fixed (Byte* destinationBase = byteArray)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (Byte)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte[] sbyteArray = (SByte[])(object)destination;
+ fixed (SByte* destinationBase = sbyteArray)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (SByte)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16[] uint16Array = (UInt16[])(object)destination;
+ fixed (UInt16* destinationBase = uint16Array)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (UInt16)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16[] int16Array = (Int16[])(object)destination;
+ fixed (Int16* destinationBase = int16Array)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (Int16)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32[] uint32Array = (UInt32[])(object)destination;
+ fixed (UInt32* destinationBase = uint32Array)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (UInt32)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32[] int32Array = (Int32[])(object)destination;
+ fixed (Int32* destinationBase = int32Array)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (Int32)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64[] uint64Array = (UInt64[])(object)destination;
+ fixed (UInt64* destinationBase = uint64Array)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (UInt64)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64[] int64Array = (Int64[])(object)destination;
+ fixed (Int64* destinationBase = int64Array)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (Int64)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single[] singleArray = (Single[])(object)destination;
+ fixed (Single* destinationBase = singleArray)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (Single)(object)this[g];
+ }
+ }
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double[] doubleArray = (Double[])(object)destination;
+ fixed (Double* destinationBase = doubleArray)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (Double)(object)this[g];
+ }
+ }
+ }
+ }
+ else
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte[] byteArray = (Byte[])(object)destination;
+ fixed (Byte* destinationBase = byteArray)
+ {
+ destinationBase[startIndex + 0] = this.register.byte_0;
+ destinationBase[startIndex + 1] = this.register.byte_1;
+ destinationBase[startIndex + 2] = this.register.byte_2;
+ destinationBase[startIndex + 3] = this.register.byte_3;
+ destinationBase[startIndex + 4] = this.register.byte_4;
+ destinationBase[startIndex + 5] = this.register.byte_5;
+ destinationBase[startIndex + 6] = this.register.byte_6;
+ destinationBase[startIndex + 7] = this.register.byte_7;
+ destinationBase[startIndex + 8] = this.register.byte_8;
+ destinationBase[startIndex + 9] = this.register.byte_9;
+ destinationBase[startIndex + 10] = this.register.byte_10;
+ destinationBase[startIndex + 11] = this.register.byte_11;
+ destinationBase[startIndex + 12] = this.register.byte_12;
+ destinationBase[startIndex + 13] = this.register.byte_13;
+ destinationBase[startIndex + 14] = this.register.byte_14;
+ destinationBase[startIndex + 15] = this.register.byte_15;
+ }
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte[] sbyteArray = (SByte[])(object)destination;
+ fixed (SByte* destinationBase = sbyteArray)
+ {
+ destinationBase[startIndex + 0] = this.register.sbyte_0;
+ destinationBase[startIndex + 1] = this.register.sbyte_1;
+ destinationBase[startIndex + 2] = this.register.sbyte_2;
+ destinationBase[startIndex + 3] = this.register.sbyte_3;
+ destinationBase[startIndex + 4] = this.register.sbyte_4;
+ destinationBase[startIndex + 5] = this.register.sbyte_5;
+ destinationBase[startIndex + 6] = this.register.sbyte_6;
+ destinationBase[startIndex + 7] = this.register.sbyte_7;
+ destinationBase[startIndex + 8] = this.register.sbyte_8;
+ destinationBase[startIndex + 9] = this.register.sbyte_9;
+ destinationBase[startIndex + 10] = this.register.sbyte_10;
+ destinationBase[startIndex + 11] = this.register.sbyte_11;
+ destinationBase[startIndex + 12] = this.register.sbyte_12;
+ destinationBase[startIndex + 13] = this.register.sbyte_13;
+ destinationBase[startIndex + 14] = this.register.sbyte_14;
+ destinationBase[startIndex + 15] = this.register.sbyte_15;
+ }
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16[] uint16Array = (UInt16[])(object)destination;
+ fixed (UInt16* destinationBase = uint16Array)
+ {
+ destinationBase[startIndex + 0] = this.register.uint16_0;
+ destinationBase[startIndex + 1] = this.register.uint16_1;
+ destinationBase[startIndex + 2] = this.register.uint16_2;
+ destinationBase[startIndex + 3] = this.register.uint16_3;
+ destinationBase[startIndex + 4] = this.register.uint16_4;
+ destinationBase[startIndex + 5] = this.register.uint16_5;
+ destinationBase[startIndex + 6] = this.register.uint16_6;
+ destinationBase[startIndex + 7] = this.register.uint16_7;
+ }
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16[] int16Array = (Int16[])(object)destination;
+ fixed (Int16* destinationBase = int16Array)
+ {
+ destinationBase[startIndex + 0] = this.register.int16_0;
+ destinationBase[startIndex + 1] = this.register.int16_1;
+ destinationBase[startIndex + 2] = this.register.int16_2;
+ destinationBase[startIndex + 3] = this.register.int16_3;
+ destinationBase[startIndex + 4] = this.register.int16_4;
+ destinationBase[startIndex + 5] = this.register.int16_5;
+ destinationBase[startIndex + 6] = this.register.int16_6;
+ destinationBase[startIndex + 7] = this.register.int16_7;
+ }
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32[] uint32Array = (UInt32[])(object)destination;
+ fixed (UInt32* destinationBase = uint32Array)
+ {
+ destinationBase[startIndex + 0] = this.register.uint32_0;
+ destinationBase[startIndex + 1] = this.register.uint32_1;
+ destinationBase[startIndex + 2] = this.register.uint32_2;
+ destinationBase[startIndex + 3] = this.register.uint32_3;
+ }
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32[] int32Array = (Int32[])(object)destination;
+ fixed (Int32* destinationBase = int32Array)
+ {
+ destinationBase[startIndex + 0] = this.register.int32_0;
+ destinationBase[startIndex + 1] = this.register.int32_1;
+ destinationBase[startIndex + 2] = this.register.int32_2;
+ destinationBase[startIndex + 3] = this.register.int32_3;
+ }
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64[] uint64Array = (UInt64[])(object)destination;
+ fixed (UInt64* destinationBase = uint64Array)
+ {
+ destinationBase[startIndex + 0] = this.register.uint64_0;
+ destinationBase[startIndex + 1] = this.register.uint64_1;
+ }
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64[] int64Array = (Int64[])(object)destination;
+ fixed (Int64* destinationBase = int64Array)
+ {
+ destinationBase[startIndex + 0] = this.register.int64_0;
+ destinationBase[startIndex + 1] = this.register.int64_1;
+ }
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single[] singleArray = (Single[])(object)destination;
+ fixed (Single* destinationBase = singleArray)
+ {
+ destinationBase[startIndex + 0] = this.register.single_0;
+ destinationBase[startIndex + 1] = this.register.single_1;
+ destinationBase[startIndex + 2] = this.register.single_2;
+ destinationBase[startIndex + 3] = this.register.single_3;
+ }
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double[] doubleArray = (Double[])(object)destination;
+ fixed (Double* destinationBase = doubleArray)
+ {
+ destinationBase[startIndex + 0] = this.register.double_0;
+ destinationBase[startIndex + 1] = this.register.double_1;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns the element at the given index.
+ /// </summary>
+ public unsafe T this[int index]
+ {
+ [Intrinsic]
+ get
+ {
+ if (index >= Count || index < 0)
+ {
+ throw new IndexOutOfRangeException(SR.Format(SR.Arg_ArgumentOutOfRangeException, index));
+ }
+ if (typeof(T) == typeof(Byte))
+ {
+ fixed (Byte* basePtr = &this.register.byte_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ fixed (SByte* basePtr = &this.register.sbyte_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ fixed (UInt16* basePtr = &this.register.uint16_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ fixed (Int16* basePtr = &this.register.int16_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ fixed (UInt32* basePtr = &this.register.uint32_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ fixed (Int32* basePtr = &this.register.int32_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ fixed (UInt64* basePtr = &this.register.uint64_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ fixed (Int64* basePtr = &this.register.int64_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ fixed (Single* basePtr = &this.register.single_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ fixed (Double* basePtr = &this.register.double_0)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether the given Object is equal to this vector instance.
+ /// </summary>
+ /// <param name="obj">The Object to compare against.</param>
+ /// <returns>True if the Object is equal to this vector; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (!(obj is Vector<T>))
+ {
+ return false;
+ }
+ return Equals((Vector<T>)obj);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether the given vector is equal to this vector instance.
+ /// </summary>
+ /// <param name="other">The vector to compare this instance to.</param>
+ /// <returns>True if the other vector is equal to this instance; False otherwise.</returns>
+ [Intrinsic]
+ public bool Equals(Vector<T> other)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ if (!ScalarEquals(this[g], other[g]))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return
+ this.register.byte_0 == other.register.byte_0
+ && this.register.byte_1 == other.register.byte_1
+ && this.register.byte_2 == other.register.byte_2
+ && this.register.byte_3 == other.register.byte_3
+ && this.register.byte_4 == other.register.byte_4
+ && this.register.byte_5 == other.register.byte_5
+ && this.register.byte_6 == other.register.byte_6
+ && this.register.byte_7 == other.register.byte_7
+ && this.register.byte_8 == other.register.byte_8
+ && this.register.byte_9 == other.register.byte_9
+ && this.register.byte_10 == other.register.byte_10
+ && this.register.byte_11 == other.register.byte_11
+ && this.register.byte_12 == other.register.byte_12
+ && this.register.byte_13 == other.register.byte_13
+ && this.register.byte_14 == other.register.byte_14
+ && this.register.byte_15 == other.register.byte_15;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return
+ this.register.sbyte_0 == other.register.sbyte_0
+ && this.register.sbyte_1 == other.register.sbyte_1
+ && this.register.sbyte_2 == other.register.sbyte_2
+ && this.register.sbyte_3 == other.register.sbyte_3
+ && this.register.sbyte_4 == other.register.sbyte_4
+ && this.register.sbyte_5 == other.register.sbyte_5
+ && this.register.sbyte_6 == other.register.sbyte_6
+ && this.register.sbyte_7 == other.register.sbyte_7
+ && this.register.sbyte_8 == other.register.sbyte_8
+ && this.register.sbyte_9 == other.register.sbyte_9
+ && this.register.sbyte_10 == other.register.sbyte_10
+ && this.register.sbyte_11 == other.register.sbyte_11
+ && this.register.sbyte_12 == other.register.sbyte_12
+ && this.register.sbyte_13 == other.register.sbyte_13
+ && this.register.sbyte_14 == other.register.sbyte_14
+ && this.register.sbyte_15 == other.register.sbyte_15;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return
+ this.register.uint16_0 == other.register.uint16_0
+ && this.register.uint16_1 == other.register.uint16_1
+ && this.register.uint16_2 == other.register.uint16_2
+ && this.register.uint16_3 == other.register.uint16_3
+ && this.register.uint16_4 == other.register.uint16_4
+ && this.register.uint16_5 == other.register.uint16_5
+ && this.register.uint16_6 == other.register.uint16_6
+ && this.register.uint16_7 == other.register.uint16_7;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return
+ this.register.int16_0 == other.register.int16_0
+ && this.register.int16_1 == other.register.int16_1
+ && this.register.int16_2 == other.register.int16_2
+ && this.register.int16_3 == other.register.int16_3
+ && this.register.int16_4 == other.register.int16_4
+ && this.register.int16_5 == other.register.int16_5
+ && this.register.int16_6 == other.register.int16_6
+ && this.register.int16_7 == other.register.int16_7;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return
+ this.register.uint32_0 == other.register.uint32_0
+ && this.register.uint32_1 == other.register.uint32_1
+ && this.register.uint32_2 == other.register.uint32_2
+ && this.register.uint32_3 == other.register.uint32_3;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return
+ this.register.int32_0 == other.register.int32_0
+ && this.register.int32_1 == other.register.int32_1
+ && this.register.int32_2 == other.register.int32_2
+ && this.register.int32_3 == other.register.int32_3;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return
+ this.register.uint64_0 == other.register.uint64_0
+ && this.register.uint64_1 == other.register.uint64_1;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return
+ this.register.int64_0 == other.register.int64_0
+ && this.register.int64_1 == other.register.int64_1;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return
+ this.register.single_0 == other.register.single_0
+ && this.register.single_1 == other.register.single_1
+ && this.register.single_2 == other.register.single_2
+ && this.register.single_3 == other.register.single_3;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return
+ this.register.double_0 == other.register.double_0
+ && this.register.double_1 == other.register.double_1;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns the hash code for this instance.
+ /// </summary>
+ /// <returns>The hash code.</returns>
+ public override int GetHashCode()
+ {
+ int hash = 0;
+
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((Byte)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((SByte)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((UInt16)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((Int16)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((UInt32)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((Int32)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((UInt64)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((Int64)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((Single)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((Double)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ hash = HashHelpers.Combine(hash, this.register.byte_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_1.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_2.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_3.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_4.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_5.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_6.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_7.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_8.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_9.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_10.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_11.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_12.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_13.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_14.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.byte_15.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ hash = HashHelpers.Combine(hash, this.register.sbyte_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_1.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_2.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_3.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_4.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_5.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_6.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_7.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_8.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_9.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_10.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_11.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_12.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_13.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_14.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.sbyte_15.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ hash = HashHelpers.Combine(hash, this.register.uint16_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint16_1.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint16_2.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint16_3.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint16_4.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint16_5.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint16_6.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint16_7.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ hash = HashHelpers.Combine(hash, this.register.int16_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int16_1.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int16_2.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int16_3.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int16_4.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int16_5.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int16_6.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int16_7.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ hash = HashHelpers.Combine(hash, this.register.uint32_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint32_1.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint32_2.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint32_3.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ hash = HashHelpers.Combine(hash, this.register.int32_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int32_1.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int32_2.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int32_3.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ hash = HashHelpers.Combine(hash, this.register.uint64_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.uint64_1.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ hash = HashHelpers.Combine(hash, this.register.int64_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.int64_1.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ hash = HashHelpers.Combine(hash, this.register.single_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.single_1.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.single_2.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.single_3.GetHashCode());
+ return hash;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ hash = HashHelpers.Combine(hash, this.register.double_0.GetHashCode());
+ hash = HashHelpers.Combine(hash, this.register.double_1.GetHashCode());
+ return hash;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a String representing this vector.
+ /// </summary>
+ /// <returns>The string representation.</returns>
+ public override string ToString()
+ {
+ return ToString("G", CultureInfo.CurrentCulture);
+ }
+
+ /// <summary>
+ /// Returns a String representing this vector, using the specified format string to format individual elements.
+ /// </summary>
+ /// <param name="format">The format of individual elements.</param>
+ /// <returns>The string representation.</returns>
+ public string ToString(string format)
+ {
+ return ToString(format, CultureInfo.CurrentCulture);
+ }
+
+ /// <summary>
+ /// Returns a String representing this vector, using the specified format string to format individual elements
+ /// and the given IFormatProvider.
+ /// </summary>
+ /// <param name="format">The format of individual elements.</param>
+ /// <param name="formatProvider">The format provider to use when formatting elements.</param>
+ /// <returns>The string representation.</returns>
+ public string ToString(string format, IFormatProvider formatProvider)
+ {
+ StringBuilder sb = new StringBuilder();
+ string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator;
+ sb.Append('<');
+ for (int g = 0; g < Count - 1; g++)
+ {
+ sb.Append(((IFormattable)this[g]).ToString(format, formatProvider));
+ sb.Append(separator);
+ sb.Append(' ');
+ }
+ // Append last element w/out separator
+ sb.Append(((IFormattable)this[Count - 1]).ToString(format, formatProvider));
+ sb.Append('>');
+ return sb.ToString();
+ }
+ #endregion Public Instance Methods
+
+ #region Arithmetic Operators
+ /// <summary>
+ /// Adds two vectors together.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The summed vector.</returns>
+ public static unsafe Vector<T> operator +(Vector<T> left, Vector<T> right)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Byte)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (SByte)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt16)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int16)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt32)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int32)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt64)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int64)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Single)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Double)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> sum = new Vector<T>();
+ if (typeof(T) == typeof(Byte))
+ {
+ sum.register.byte_0 = (Byte)(left.register.byte_0 + right.register.byte_0);
+ sum.register.byte_1 = (Byte)(left.register.byte_1 + right.register.byte_1);
+ sum.register.byte_2 = (Byte)(left.register.byte_2 + right.register.byte_2);
+ sum.register.byte_3 = (Byte)(left.register.byte_3 + right.register.byte_3);
+ sum.register.byte_4 = (Byte)(left.register.byte_4 + right.register.byte_4);
+ sum.register.byte_5 = (Byte)(left.register.byte_5 + right.register.byte_5);
+ sum.register.byte_6 = (Byte)(left.register.byte_6 + right.register.byte_6);
+ sum.register.byte_7 = (Byte)(left.register.byte_7 + right.register.byte_7);
+ sum.register.byte_8 = (Byte)(left.register.byte_8 + right.register.byte_8);
+ sum.register.byte_9 = (Byte)(left.register.byte_9 + right.register.byte_9);
+ sum.register.byte_10 = (Byte)(left.register.byte_10 + right.register.byte_10);
+ sum.register.byte_11 = (Byte)(left.register.byte_11 + right.register.byte_11);
+ sum.register.byte_12 = (Byte)(left.register.byte_12 + right.register.byte_12);
+ sum.register.byte_13 = (Byte)(left.register.byte_13 + right.register.byte_13);
+ sum.register.byte_14 = (Byte)(left.register.byte_14 + right.register.byte_14);
+ sum.register.byte_15 = (Byte)(left.register.byte_15 + right.register.byte_15);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ sum.register.sbyte_0 = (SByte)(left.register.sbyte_0 + right.register.sbyte_0);
+ sum.register.sbyte_1 = (SByte)(left.register.sbyte_1 + right.register.sbyte_1);
+ sum.register.sbyte_2 = (SByte)(left.register.sbyte_2 + right.register.sbyte_2);
+ sum.register.sbyte_3 = (SByte)(left.register.sbyte_3 + right.register.sbyte_3);
+ sum.register.sbyte_4 = (SByte)(left.register.sbyte_4 + right.register.sbyte_4);
+ sum.register.sbyte_5 = (SByte)(left.register.sbyte_5 + right.register.sbyte_5);
+ sum.register.sbyte_6 = (SByte)(left.register.sbyte_6 + right.register.sbyte_6);
+ sum.register.sbyte_7 = (SByte)(left.register.sbyte_7 + right.register.sbyte_7);
+ sum.register.sbyte_8 = (SByte)(left.register.sbyte_8 + right.register.sbyte_8);
+ sum.register.sbyte_9 = (SByte)(left.register.sbyte_9 + right.register.sbyte_9);
+ sum.register.sbyte_10 = (SByte)(left.register.sbyte_10 + right.register.sbyte_10);
+ sum.register.sbyte_11 = (SByte)(left.register.sbyte_11 + right.register.sbyte_11);
+ sum.register.sbyte_12 = (SByte)(left.register.sbyte_12 + right.register.sbyte_12);
+ sum.register.sbyte_13 = (SByte)(left.register.sbyte_13 + right.register.sbyte_13);
+ sum.register.sbyte_14 = (SByte)(left.register.sbyte_14 + right.register.sbyte_14);
+ sum.register.sbyte_15 = (SByte)(left.register.sbyte_15 + right.register.sbyte_15);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ sum.register.uint16_0 = (UInt16)(left.register.uint16_0 + right.register.uint16_0);
+ sum.register.uint16_1 = (UInt16)(left.register.uint16_1 + right.register.uint16_1);
+ sum.register.uint16_2 = (UInt16)(left.register.uint16_2 + right.register.uint16_2);
+ sum.register.uint16_3 = (UInt16)(left.register.uint16_3 + right.register.uint16_3);
+ sum.register.uint16_4 = (UInt16)(left.register.uint16_4 + right.register.uint16_4);
+ sum.register.uint16_5 = (UInt16)(left.register.uint16_5 + right.register.uint16_5);
+ sum.register.uint16_6 = (UInt16)(left.register.uint16_6 + right.register.uint16_6);
+ sum.register.uint16_7 = (UInt16)(left.register.uint16_7 + right.register.uint16_7);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ sum.register.int16_0 = (Int16)(left.register.int16_0 + right.register.int16_0);
+ sum.register.int16_1 = (Int16)(left.register.int16_1 + right.register.int16_1);
+ sum.register.int16_2 = (Int16)(left.register.int16_2 + right.register.int16_2);
+ sum.register.int16_3 = (Int16)(left.register.int16_3 + right.register.int16_3);
+ sum.register.int16_4 = (Int16)(left.register.int16_4 + right.register.int16_4);
+ sum.register.int16_5 = (Int16)(left.register.int16_5 + right.register.int16_5);
+ sum.register.int16_6 = (Int16)(left.register.int16_6 + right.register.int16_6);
+ sum.register.int16_7 = (Int16)(left.register.int16_7 + right.register.int16_7);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ sum.register.uint32_0 = (UInt32)(left.register.uint32_0 + right.register.uint32_0);
+ sum.register.uint32_1 = (UInt32)(left.register.uint32_1 + right.register.uint32_1);
+ sum.register.uint32_2 = (UInt32)(left.register.uint32_2 + right.register.uint32_2);
+ sum.register.uint32_3 = (UInt32)(left.register.uint32_3 + right.register.uint32_3);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ sum.register.int32_0 = (Int32)(left.register.int32_0 + right.register.int32_0);
+ sum.register.int32_1 = (Int32)(left.register.int32_1 + right.register.int32_1);
+ sum.register.int32_2 = (Int32)(left.register.int32_2 + right.register.int32_2);
+ sum.register.int32_3 = (Int32)(left.register.int32_3 + right.register.int32_3);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ sum.register.uint64_0 = (UInt64)(left.register.uint64_0 + right.register.uint64_0);
+ sum.register.uint64_1 = (UInt64)(left.register.uint64_1 + right.register.uint64_1);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ sum.register.int64_0 = (Int64)(left.register.int64_0 + right.register.int64_0);
+ sum.register.int64_1 = (Int64)(left.register.int64_1 + right.register.int64_1);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ sum.register.single_0 = (Single)(left.register.single_0 + right.register.single_0);
+ sum.register.single_1 = (Single)(left.register.single_1 + right.register.single_1);
+ sum.register.single_2 = (Single)(left.register.single_2 + right.register.single_2);
+ sum.register.single_3 = (Single)(left.register.single_3 + right.register.single_3);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ sum.register.double_0 = (Double)(left.register.double_0 + right.register.double_0);
+ sum.register.double_1 = (Double)(left.register.double_1 + right.register.double_1);
+ }
+ return sum;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Subtracts the second vector from the first.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The difference vector.</returns>
+ public static unsafe Vector<T> operator -(Vector<T> left, Vector<T> right)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Byte)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (SByte)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt16)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int16)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt32)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int32)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt64)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int64)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Single)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Double)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> difference = new Vector<T>();
+ if (typeof(T) == typeof(Byte))
+ {
+ difference.register.byte_0 = (Byte)(left.register.byte_0 - right.register.byte_0);
+ difference.register.byte_1 = (Byte)(left.register.byte_1 - right.register.byte_1);
+ difference.register.byte_2 = (Byte)(left.register.byte_2 - right.register.byte_2);
+ difference.register.byte_3 = (Byte)(left.register.byte_3 - right.register.byte_3);
+ difference.register.byte_4 = (Byte)(left.register.byte_4 - right.register.byte_4);
+ difference.register.byte_5 = (Byte)(left.register.byte_5 - right.register.byte_5);
+ difference.register.byte_6 = (Byte)(left.register.byte_6 - right.register.byte_6);
+ difference.register.byte_7 = (Byte)(left.register.byte_7 - right.register.byte_7);
+ difference.register.byte_8 = (Byte)(left.register.byte_8 - right.register.byte_8);
+ difference.register.byte_9 = (Byte)(left.register.byte_9 - right.register.byte_9);
+ difference.register.byte_10 = (Byte)(left.register.byte_10 - right.register.byte_10);
+ difference.register.byte_11 = (Byte)(left.register.byte_11 - right.register.byte_11);
+ difference.register.byte_12 = (Byte)(left.register.byte_12 - right.register.byte_12);
+ difference.register.byte_13 = (Byte)(left.register.byte_13 - right.register.byte_13);
+ difference.register.byte_14 = (Byte)(left.register.byte_14 - right.register.byte_14);
+ difference.register.byte_15 = (Byte)(left.register.byte_15 - right.register.byte_15);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ difference.register.sbyte_0 = (SByte)(left.register.sbyte_0 - right.register.sbyte_0);
+ difference.register.sbyte_1 = (SByte)(left.register.sbyte_1 - right.register.sbyte_1);
+ difference.register.sbyte_2 = (SByte)(left.register.sbyte_2 - right.register.sbyte_2);
+ difference.register.sbyte_3 = (SByte)(left.register.sbyte_3 - right.register.sbyte_3);
+ difference.register.sbyte_4 = (SByte)(left.register.sbyte_4 - right.register.sbyte_4);
+ difference.register.sbyte_5 = (SByte)(left.register.sbyte_5 - right.register.sbyte_5);
+ difference.register.sbyte_6 = (SByte)(left.register.sbyte_6 - right.register.sbyte_6);
+ difference.register.sbyte_7 = (SByte)(left.register.sbyte_7 - right.register.sbyte_7);
+ difference.register.sbyte_8 = (SByte)(left.register.sbyte_8 - right.register.sbyte_8);
+ difference.register.sbyte_9 = (SByte)(left.register.sbyte_9 - right.register.sbyte_9);
+ difference.register.sbyte_10 = (SByte)(left.register.sbyte_10 - right.register.sbyte_10);
+ difference.register.sbyte_11 = (SByte)(left.register.sbyte_11 - right.register.sbyte_11);
+ difference.register.sbyte_12 = (SByte)(left.register.sbyte_12 - right.register.sbyte_12);
+ difference.register.sbyte_13 = (SByte)(left.register.sbyte_13 - right.register.sbyte_13);
+ difference.register.sbyte_14 = (SByte)(left.register.sbyte_14 - right.register.sbyte_14);
+ difference.register.sbyte_15 = (SByte)(left.register.sbyte_15 - right.register.sbyte_15);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ difference.register.uint16_0 = (UInt16)(left.register.uint16_0 - right.register.uint16_0);
+ difference.register.uint16_1 = (UInt16)(left.register.uint16_1 - right.register.uint16_1);
+ difference.register.uint16_2 = (UInt16)(left.register.uint16_2 - right.register.uint16_2);
+ difference.register.uint16_3 = (UInt16)(left.register.uint16_3 - right.register.uint16_3);
+ difference.register.uint16_4 = (UInt16)(left.register.uint16_4 - right.register.uint16_4);
+ difference.register.uint16_5 = (UInt16)(left.register.uint16_5 - right.register.uint16_5);
+ difference.register.uint16_6 = (UInt16)(left.register.uint16_6 - right.register.uint16_6);
+ difference.register.uint16_7 = (UInt16)(left.register.uint16_7 - right.register.uint16_7);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ difference.register.int16_0 = (Int16)(left.register.int16_0 - right.register.int16_0);
+ difference.register.int16_1 = (Int16)(left.register.int16_1 - right.register.int16_1);
+ difference.register.int16_2 = (Int16)(left.register.int16_2 - right.register.int16_2);
+ difference.register.int16_3 = (Int16)(left.register.int16_3 - right.register.int16_3);
+ difference.register.int16_4 = (Int16)(left.register.int16_4 - right.register.int16_4);
+ difference.register.int16_5 = (Int16)(left.register.int16_5 - right.register.int16_5);
+ difference.register.int16_6 = (Int16)(left.register.int16_6 - right.register.int16_6);
+ difference.register.int16_7 = (Int16)(left.register.int16_7 - right.register.int16_7);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ difference.register.uint32_0 = (UInt32)(left.register.uint32_0 - right.register.uint32_0);
+ difference.register.uint32_1 = (UInt32)(left.register.uint32_1 - right.register.uint32_1);
+ difference.register.uint32_2 = (UInt32)(left.register.uint32_2 - right.register.uint32_2);
+ difference.register.uint32_3 = (UInt32)(left.register.uint32_3 - right.register.uint32_3);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ difference.register.int32_0 = (Int32)(left.register.int32_0 - right.register.int32_0);
+ difference.register.int32_1 = (Int32)(left.register.int32_1 - right.register.int32_1);
+ difference.register.int32_2 = (Int32)(left.register.int32_2 - right.register.int32_2);
+ difference.register.int32_3 = (Int32)(left.register.int32_3 - right.register.int32_3);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ difference.register.uint64_0 = (UInt64)(left.register.uint64_0 - right.register.uint64_0);
+ difference.register.uint64_1 = (UInt64)(left.register.uint64_1 - right.register.uint64_1);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ difference.register.int64_0 = (Int64)(left.register.int64_0 - right.register.int64_0);
+ difference.register.int64_1 = (Int64)(left.register.int64_1 - right.register.int64_1);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ difference.register.single_0 = (Single)(left.register.single_0 - right.register.single_0);
+ difference.register.single_1 = (Single)(left.register.single_1 - right.register.single_1);
+ difference.register.single_2 = (Single)(left.register.single_2 - right.register.single_2);
+ difference.register.single_3 = (Single)(left.register.single_3 - right.register.single_3);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ difference.register.double_0 = (Double)(left.register.double_0 - right.register.double_0);
+ difference.register.double_1 = (Double)(left.register.double_1 - right.register.double_1);
+ }
+ return difference;
+ }
+ }
+ }
+
+ // This method is intrinsic only for certain types. It cannot access fields directly unless we are sure the context is unaccelerated.
+ /// <summary>
+ /// Multiplies two vectors together.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The product vector.</returns>
+ public static unsafe Vector<T> operator *(Vector<T> left, Vector<T> right)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Byte)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (SByte)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt16)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int16)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt32)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int32)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt64)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int64)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Single)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Double)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> product = new Vector<T>();
+ if (typeof(T) == typeof(Byte))
+ {
+ product.register.byte_0 = (Byte)(left.register.byte_0 * right.register.byte_0);
+ product.register.byte_1 = (Byte)(left.register.byte_1 * right.register.byte_1);
+ product.register.byte_2 = (Byte)(left.register.byte_2 * right.register.byte_2);
+ product.register.byte_3 = (Byte)(left.register.byte_3 * right.register.byte_3);
+ product.register.byte_4 = (Byte)(left.register.byte_4 * right.register.byte_4);
+ product.register.byte_5 = (Byte)(left.register.byte_5 * right.register.byte_5);
+ product.register.byte_6 = (Byte)(left.register.byte_6 * right.register.byte_6);
+ product.register.byte_7 = (Byte)(left.register.byte_7 * right.register.byte_7);
+ product.register.byte_8 = (Byte)(left.register.byte_8 * right.register.byte_8);
+ product.register.byte_9 = (Byte)(left.register.byte_9 * right.register.byte_9);
+ product.register.byte_10 = (Byte)(left.register.byte_10 * right.register.byte_10);
+ product.register.byte_11 = (Byte)(left.register.byte_11 * right.register.byte_11);
+ product.register.byte_12 = (Byte)(left.register.byte_12 * right.register.byte_12);
+ product.register.byte_13 = (Byte)(left.register.byte_13 * right.register.byte_13);
+ product.register.byte_14 = (Byte)(left.register.byte_14 * right.register.byte_14);
+ product.register.byte_15 = (Byte)(left.register.byte_15 * right.register.byte_15);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ product.register.sbyte_0 = (SByte)(left.register.sbyte_0 * right.register.sbyte_0);
+ product.register.sbyte_1 = (SByte)(left.register.sbyte_1 * right.register.sbyte_1);
+ product.register.sbyte_2 = (SByte)(left.register.sbyte_2 * right.register.sbyte_2);
+ product.register.sbyte_3 = (SByte)(left.register.sbyte_3 * right.register.sbyte_3);
+ product.register.sbyte_4 = (SByte)(left.register.sbyte_4 * right.register.sbyte_4);
+ product.register.sbyte_5 = (SByte)(left.register.sbyte_5 * right.register.sbyte_5);
+ product.register.sbyte_6 = (SByte)(left.register.sbyte_6 * right.register.sbyte_6);
+ product.register.sbyte_7 = (SByte)(left.register.sbyte_7 * right.register.sbyte_7);
+ product.register.sbyte_8 = (SByte)(left.register.sbyte_8 * right.register.sbyte_8);
+ product.register.sbyte_9 = (SByte)(left.register.sbyte_9 * right.register.sbyte_9);
+ product.register.sbyte_10 = (SByte)(left.register.sbyte_10 * right.register.sbyte_10);
+ product.register.sbyte_11 = (SByte)(left.register.sbyte_11 * right.register.sbyte_11);
+ product.register.sbyte_12 = (SByte)(left.register.sbyte_12 * right.register.sbyte_12);
+ product.register.sbyte_13 = (SByte)(left.register.sbyte_13 * right.register.sbyte_13);
+ product.register.sbyte_14 = (SByte)(left.register.sbyte_14 * right.register.sbyte_14);
+ product.register.sbyte_15 = (SByte)(left.register.sbyte_15 * right.register.sbyte_15);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ product.register.uint16_0 = (UInt16)(left.register.uint16_0 * right.register.uint16_0);
+ product.register.uint16_1 = (UInt16)(left.register.uint16_1 * right.register.uint16_1);
+ product.register.uint16_2 = (UInt16)(left.register.uint16_2 * right.register.uint16_2);
+ product.register.uint16_3 = (UInt16)(left.register.uint16_3 * right.register.uint16_3);
+ product.register.uint16_4 = (UInt16)(left.register.uint16_4 * right.register.uint16_4);
+ product.register.uint16_5 = (UInt16)(left.register.uint16_5 * right.register.uint16_5);
+ product.register.uint16_6 = (UInt16)(left.register.uint16_6 * right.register.uint16_6);
+ product.register.uint16_7 = (UInt16)(left.register.uint16_7 * right.register.uint16_7);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ product.register.int16_0 = (Int16)(left.register.int16_0 * right.register.int16_0);
+ product.register.int16_1 = (Int16)(left.register.int16_1 * right.register.int16_1);
+ product.register.int16_2 = (Int16)(left.register.int16_2 * right.register.int16_2);
+ product.register.int16_3 = (Int16)(left.register.int16_3 * right.register.int16_3);
+ product.register.int16_4 = (Int16)(left.register.int16_4 * right.register.int16_4);
+ product.register.int16_5 = (Int16)(left.register.int16_5 * right.register.int16_5);
+ product.register.int16_6 = (Int16)(left.register.int16_6 * right.register.int16_6);
+ product.register.int16_7 = (Int16)(left.register.int16_7 * right.register.int16_7);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ product.register.uint32_0 = (UInt32)(left.register.uint32_0 * right.register.uint32_0);
+ product.register.uint32_1 = (UInt32)(left.register.uint32_1 * right.register.uint32_1);
+ product.register.uint32_2 = (UInt32)(left.register.uint32_2 * right.register.uint32_2);
+ product.register.uint32_3 = (UInt32)(left.register.uint32_3 * right.register.uint32_3);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ product.register.int32_0 = (Int32)(left.register.int32_0 * right.register.int32_0);
+ product.register.int32_1 = (Int32)(left.register.int32_1 * right.register.int32_1);
+ product.register.int32_2 = (Int32)(left.register.int32_2 * right.register.int32_2);
+ product.register.int32_3 = (Int32)(left.register.int32_3 * right.register.int32_3);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ product.register.uint64_0 = (UInt64)(left.register.uint64_0 * right.register.uint64_0);
+ product.register.uint64_1 = (UInt64)(left.register.uint64_1 * right.register.uint64_1);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ product.register.int64_0 = (Int64)(left.register.int64_0 * right.register.int64_0);
+ product.register.int64_1 = (Int64)(left.register.int64_1 * right.register.int64_1);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ product.register.single_0 = (Single)(left.register.single_0 * right.register.single_0);
+ product.register.single_1 = (Single)(left.register.single_1 * right.register.single_1);
+ product.register.single_2 = (Single)(left.register.single_2 * right.register.single_2);
+ product.register.single_3 = (Single)(left.register.single_3 * right.register.single_3);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ product.register.double_0 = (Double)(left.register.double_0 * right.register.double_0);
+ product.register.double_1 = (Double)(left.register.double_1 * right.register.double_1);
+ }
+ return product;
+ }
+ }
+ }
+
+ // This method is intrinsic only for certain types. It cannot access fields directly unless we are sure the context is unaccelerated.
+ /// <summary>
+ /// Multiplies a vector by the given scalar.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <param name="factor">The scalar value.</param>
+ /// <returns>The scaled vector.</returns>
+ public static Vector<T> operator *(Vector<T> value, T factor)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ return new Vector<T>(factor) * value;
+ }
+ else
+ {
+ Vector<T> product = new Vector<T>();
+ if (typeof(T) == typeof(Byte))
+ {
+ product.register.byte_0 = (Byte)(value.register.byte_0 * (Byte)(object)factor);
+ product.register.byte_1 = (Byte)(value.register.byte_1 * (Byte)(object)factor);
+ product.register.byte_2 = (Byte)(value.register.byte_2 * (Byte)(object)factor);
+ product.register.byte_3 = (Byte)(value.register.byte_3 * (Byte)(object)factor);
+ product.register.byte_4 = (Byte)(value.register.byte_4 * (Byte)(object)factor);
+ product.register.byte_5 = (Byte)(value.register.byte_5 * (Byte)(object)factor);
+ product.register.byte_6 = (Byte)(value.register.byte_6 * (Byte)(object)factor);
+ product.register.byte_7 = (Byte)(value.register.byte_7 * (Byte)(object)factor);
+ product.register.byte_8 = (Byte)(value.register.byte_8 * (Byte)(object)factor);
+ product.register.byte_9 = (Byte)(value.register.byte_9 * (Byte)(object)factor);
+ product.register.byte_10 = (Byte)(value.register.byte_10 * (Byte)(object)factor);
+ product.register.byte_11 = (Byte)(value.register.byte_11 * (Byte)(object)factor);
+ product.register.byte_12 = (Byte)(value.register.byte_12 * (Byte)(object)factor);
+ product.register.byte_13 = (Byte)(value.register.byte_13 * (Byte)(object)factor);
+ product.register.byte_14 = (Byte)(value.register.byte_14 * (Byte)(object)factor);
+ product.register.byte_15 = (Byte)(value.register.byte_15 * (Byte)(object)factor);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ product.register.sbyte_0 = (SByte)(value.register.sbyte_0 * (SByte)(object)factor);
+ product.register.sbyte_1 = (SByte)(value.register.sbyte_1 * (SByte)(object)factor);
+ product.register.sbyte_2 = (SByte)(value.register.sbyte_2 * (SByte)(object)factor);
+ product.register.sbyte_3 = (SByte)(value.register.sbyte_3 * (SByte)(object)factor);
+ product.register.sbyte_4 = (SByte)(value.register.sbyte_4 * (SByte)(object)factor);
+ product.register.sbyte_5 = (SByte)(value.register.sbyte_5 * (SByte)(object)factor);
+ product.register.sbyte_6 = (SByte)(value.register.sbyte_6 * (SByte)(object)factor);
+ product.register.sbyte_7 = (SByte)(value.register.sbyte_7 * (SByte)(object)factor);
+ product.register.sbyte_8 = (SByte)(value.register.sbyte_8 * (SByte)(object)factor);
+ product.register.sbyte_9 = (SByte)(value.register.sbyte_9 * (SByte)(object)factor);
+ product.register.sbyte_10 = (SByte)(value.register.sbyte_10 * (SByte)(object)factor);
+ product.register.sbyte_11 = (SByte)(value.register.sbyte_11 * (SByte)(object)factor);
+ product.register.sbyte_12 = (SByte)(value.register.sbyte_12 * (SByte)(object)factor);
+ product.register.sbyte_13 = (SByte)(value.register.sbyte_13 * (SByte)(object)factor);
+ product.register.sbyte_14 = (SByte)(value.register.sbyte_14 * (SByte)(object)factor);
+ product.register.sbyte_15 = (SByte)(value.register.sbyte_15 * (SByte)(object)factor);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ product.register.uint16_0 = (UInt16)(value.register.uint16_0 * (UInt16)(object)factor);
+ product.register.uint16_1 = (UInt16)(value.register.uint16_1 * (UInt16)(object)factor);
+ product.register.uint16_2 = (UInt16)(value.register.uint16_2 * (UInt16)(object)factor);
+ product.register.uint16_3 = (UInt16)(value.register.uint16_3 * (UInt16)(object)factor);
+ product.register.uint16_4 = (UInt16)(value.register.uint16_4 * (UInt16)(object)factor);
+ product.register.uint16_5 = (UInt16)(value.register.uint16_5 * (UInt16)(object)factor);
+ product.register.uint16_6 = (UInt16)(value.register.uint16_6 * (UInt16)(object)factor);
+ product.register.uint16_7 = (UInt16)(value.register.uint16_7 * (UInt16)(object)factor);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ product.register.int16_0 = (Int16)(value.register.int16_0 * (Int16)(object)factor);
+ product.register.int16_1 = (Int16)(value.register.int16_1 * (Int16)(object)factor);
+ product.register.int16_2 = (Int16)(value.register.int16_2 * (Int16)(object)factor);
+ product.register.int16_3 = (Int16)(value.register.int16_3 * (Int16)(object)factor);
+ product.register.int16_4 = (Int16)(value.register.int16_4 * (Int16)(object)factor);
+ product.register.int16_5 = (Int16)(value.register.int16_5 * (Int16)(object)factor);
+ product.register.int16_6 = (Int16)(value.register.int16_6 * (Int16)(object)factor);
+ product.register.int16_7 = (Int16)(value.register.int16_7 * (Int16)(object)factor);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ product.register.uint32_0 = (UInt32)(value.register.uint32_0 * (UInt32)(object)factor);
+ product.register.uint32_1 = (UInt32)(value.register.uint32_1 * (UInt32)(object)factor);
+ product.register.uint32_2 = (UInt32)(value.register.uint32_2 * (UInt32)(object)factor);
+ product.register.uint32_3 = (UInt32)(value.register.uint32_3 * (UInt32)(object)factor);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ product.register.int32_0 = (Int32)(value.register.int32_0 * (Int32)(object)factor);
+ product.register.int32_1 = (Int32)(value.register.int32_1 * (Int32)(object)factor);
+ product.register.int32_2 = (Int32)(value.register.int32_2 * (Int32)(object)factor);
+ product.register.int32_3 = (Int32)(value.register.int32_3 * (Int32)(object)factor);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ product.register.uint64_0 = (UInt64)(value.register.uint64_0 * (UInt64)(object)factor);
+ product.register.uint64_1 = (UInt64)(value.register.uint64_1 * (UInt64)(object)factor);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ product.register.int64_0 = (Int64)(value.register.int64_0 * (Int64)(object)factor);
+ product.register.int64_1 = (Int64)(value.register.int64_1 * (Int64)(object)factor);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ product.register.single_0 = (Single)(value.register.single_0 * (Single)(object)factor);
+ product.register.single_1 = (Single)(value.register.single_1 * (Single)(object)factor);
+ product.register.single_2 = (Single)(value.register.single_2 * (Single)(object)factor);
+ product.register.single_3 = (Single)(value.register.single_3 * (Single)(object)factor);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ product.register.double_0 = (Double)(value.register.double_0 * (Double)(object)factor);
+ product.register.double_1 = (Double)(value.register.double_1 * (Double)(object)factor);
+ }
+ return product;
+ }
+ }
+ }
+
+ // This method is intrinsic only for certain types. It cannot access fields directly unless we are sure the context is unaccelerated.
+ /// <summary>
+ /// Multiplies a vector by the given scalar.
+ /// </summary>
+ /// <param name="factor">The scalar value.</param>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The scaled vector.</returns>
+ public static Vector<T> operator *(T factor, Vector<T> value)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ return new Vector<T>(factor) * value;
+ }
+ else
+ {
+ Vector<T> product = new Vector<T>();
+ if (typeof(T) == typeof(Byte))
+ {
+ product.register.byte_0 = (Byte)(value.register.byte_0 * (Byte)(object)factor);
+ product.register.byte_1 = (Byte)(value.register.byte_1 * (Byte)(object)factor);
+ product.register.byte_2 = (Byte)(value.register.byte_2 * (Byte)(object)factor);
+ product.register.byte_3 = (Byte)(value.register.byte_3 * (Byte)(object)factor);
+ product.register.byte_4 = (Byte)(value.register.byte_4 * (Byte)(object)factor);
+ product.register.byte_5 = (Byte)(value.register.byte_5 * (Byte)(object)factor);
+ product.register.byte_6 = (Byte)(value.register.byte_6 * (Byte)(object)factor);
+ product.register.byte_7 = (Byte)(value.register.byte_7 * (Byte)(object)factor);
+ product.register.byte_8 = (Byte)(value.register.byte_8 * (Byte)(object)factor);
+ product.register.byte_9 = (Byte)(value.register.byte_9 * (Byte)(object)factor);
+ product.register.byte_10 = (Byte)(value.register.byte_10 * (Byte)(object)factor);
+ product.register.byte_11 = (Byte)(value.register.byte_11 * (Byte)(object)factor);
+ product.register.byte_12 = (Byte)(value.register.byte_12 * (Byte)(object)factor);
+ product.register.byte_13 = (Byte)(value.register.byte_13 * (Byte)(object)factor);
+ product.register.byte_14 = (Byte)(value.register.byte_14 * (Byte)(object)factor);
+ product.register.byte_15 = (Byte)(value.register.byte_15 * (Byte)(object)factor);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ product.register.sbyte_0 = (SByte)(value.register.sbyte_0 * (SByte)(object)factor);
+ product.register.sbyte_1 = (SByte)(value.register.sbyte_1 * (SByte)(object)factor);
+ product.register.sbyte_2 = (SByte)(value.register.sbyte_2 * (SByte)(object)factor);
+ product.register.sbyte_3 = (SByte)(value.register.sbyte_3 * (SByte)(object)factor);
+ product.register.sbyte_4 = (SByte)(value.register.sbyte_4 * (SByte)(object)factor);
+ product.register.sbyte_5 = (SByte)(value.register.sbyte_5 * (SByte)(object)factor);
+ product.register.sbyte_6 = (SByte)(value.register.sbyte_6 * (SByte)(object)factor);
+ product.register.sbyte_7 = (SByte)(value.register.sbyte_7 * (SByte)(object)factor);
+ product.register.sbyte_8 = (SByte)(value.register.sbyte_8 * (SByte)(object)factor);
+ product.register.sbyte_9 = (SByte)(value.register.sbyte_9 * (SByte)(object)factor);
+ product.register.sbyte_10 = (SByte)(value.register.sbyte_10 * (SByte)(object)factor);
+ product.register.sbyte_11 = (SByte)(value.register.sbyte_11 * (SByte)(object)factor);
+ product.register.sbyte_12 = (SByte)(value.register.sbyte_12 * (SByte)(object)factor);
+ product.register.sbyte_13 = (SByte)(value.register.sbyte_13 * (SByte)(object)factor);
+ product.register.sbyte_14 = (SByte)(value.register.sbyte_14 * (SByte)(object)factor);
+ product.register.sbyte_15 = (SByte)(value.register.sbyte_15 * (SByte)(object)factor);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ product.register.uint16_0 = (UInt16)(value.register.uint16_0 * (UInt16)(object)factor);
+ product.register.uint16_1 = (UInt16)(value.register.uint16_1 * (UInt16)(object)factor);
+ product.register.uint16_2 = (UInt16)(value.register.uint16_2 * (UInt16)(object)factor);
+ product.register.uint16_3 = (UInt16)(value.register.uint16_3 * (UInt16)(object)factor);
+ product.register.uint16_4 = (UInt16)(value.register.uint16_4 * (UInt16)(object)factor);
+ product.register.uint16_5 = (UInt16)(value.register.uint16_5 * (UInt16)(object)factor);
+ product.register.uint16_6 = (UInt16)(value.register.uint16_6 * (UInt16)(object)factor);
+ product.register.uint16_7 = (UInt16)(value.register.uint16_7 * (UInt16)(object)factor);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ product.register.int16_0 = (Int16)(value.register.int16_0 * (Int16)(object)factor);
+ product.register.int16_1 = (Int16)(value.register.int16_1 * (Int16)(object)factor);
+ product.register.int16_2 = (Int16)(value.register.int16_2 * (Int16)(object)factor);
+ product.register.int16_3 = (Int16)(value.register.int16_3 * (Int16)(object)factor);
+ product.register.int16_4 = (Int16)(value.register.int16_4 * (Int16)(object)factor);
+ product.register.int16_5 = (Int16)(value.register.int16_5 * (Int16)(object)factor);
+ product.register.int16_6 = (Int16)(value.register.int16_6 * (Int16)(object)factor);
+ product.register.int16_7 = (Int16)(value.register.int16_7 * (Int16)(object)factor);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ product.register.uint32_0 = (UInt32)(value.register.uint32_0 * (UInt32)(object)factor);
+ product.register.uint32_1 = (UInt32)(value.register.uint32_1 * (UInt32)(object)factor);
+ product.register.uint32_2 = (UInt32)(value.register.uint32_2 * (UInt32)(object)factor);
+ product.register.uint32_3 = (UInt32)(value.register.uint32_3 * (UInt32)(object)factor);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ product.register.int32_0 = (Int32)(value.register.int32_0 * (Int32)(object)factor);
+ product.register.int32_1 = (Int32)(value.register.int32_1 * (Int32)(object)factor);
+ product.register.int32_2 = (Int32)(value.register.int32_2 * (Int32)(object)factor);
+ product.register.int32_3 = (Int32)(value.register.int32_3 * (Int32)(object)factor);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ product.register.uint64_0 = (UInt64)(value.register.uint64_0 * (UInt64)(object)factor);
+ product.register.uint64_1 = (UInt64)(value.register.uint64_1 * (UInt64)(object)factor);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ product.register.int64_0 = (Int64)(value.register.int64_0 * (Int64)(object)factor);
+ product.register.int64_1 = (Int64)(value.register.int64_1 * (Int64)(object)factor);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ product.register.single_0 = (Single)(value.register.single_0 * (Single)(object)factor);
+ product.register.single_1 = (Single)(value.register.single_1 * (Single)(object)factor);
+ product.register.single_2 = (Single)(value.register.single_2 * (Single)(object)factor);
+ product.register.single_3 = (Single)(value.register.single_3 * (Single)(object)factor);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ product.register.double_0 = (Double)(value.register.double_0 * (Double)(object)factor);
+ product.register.double_1 = (Double)(value.register.double_1 * (Double)(object)factor);
+ }
+ return product;
+ }
+ }
+ }
+
+ // This method is intrinsic only for certain types. It cannot access fields directly unless we are sure the context is unaccelerated.
+ /// <summary>
+ /// Divides the first vector by the second.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The vector resulting from the division.</returns>
+ public static unsafe Vector<T> operator /(Vector<T> left, Vector<T> right)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Byte)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (SByte)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt16)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int16)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt32)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int32)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (UInt64)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int64)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Single)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Double)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> quotient = new Vector<T>();
+ if (typeof(T) == typeof(Byte))
+ {
+ quotient.register.byte_0 = (Byte)(left.register.byte_0 / right.register.byte_0);
+ quotient.register.byte_1 = (Byte)(left.register.byte_1 / right.register.byte_1);
+ quotient.register.byte_2 = (Byte)(left.register.byte_2 / right.register.byte_2);
+ quotient.register.byte_3 = (Byte)(left.register.byte_3 / right.register.byte_3);
+ quotient.register.byte_4 = (Byte)(left.register.byte_4 / right.register.byte_4);
+ quotient.register.byte_5 = (Byte)(left.register.byte_5 / right.register.byte_5);
+ quotient.register.byte_6 = (Byte)(left.register.byte_6 / right.register.byte_6);
+ quotient.register.byte_7 = (Byte)(left.register.byte_7 / right.register.byte_7);
+ quotient.register.byte_8 = (Byte)(left.register.byte_8 / right.register.byte_8);
+ quotient.register.byte_9 = (Byte)(left.register.byte_9 / right.register.byte_9);
+ quotient.register.byte_10 = (Byte)(left.register.byte_10 / right.register.byte_10);
+ quotient.register.byte_11 = (Byte)(left.register.byte_11 / right.register.byte_11);
+ quotient.register.byte_12 = (Byte)(left.register.byte_12 / right.register.byte_12);
+ quotient.register.byte_13 = (Byte)(left.register.byte_13 / right.register.byte_13);
+ quotient.register.byte_14 = (Byte)(left.register.byte_14 / right.register.byte_14);
+ quotient.register.byte_15 = (Byte)(left.register.byte_15 / right.register.byte_15);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ quotient.register.sbyte_0 = (SByte)(left.register.sbyte_0 / right.register.sbyte_0);
+ quotient.register.sbyte_1 = (SByte)(left.register.sbyte_1 / right.register.sbyte_1);
+ quotient.register.sbyte_2 = (SByte)(left.register.sbyte_2 / right.register.sbyte_2);
+ quotient.register.sbyte_3 = (SByte)(left.register.sbyte_3 / right.register.sbyte_3);
+ quotient.register.sbyte_4 = (SByte)(left.register.sbyte_4 / right.register.sbyte_4);
+ quotient.register.sbyte_5 = (SByte)(left.register.sbyte_5 / right.register.sbyte_5);
+ quotient.register.sbyte_6 = (SByte)(left.register.sbyte_6 / right.register.sbyte_6);
+ quotient.register.sbyte_7 = (SByte)(left.register.sbyte_7 / right.register.sbyte_7);
+ quotient.register.sbyte_8 = (SByte)(left.register.sbyte_8 / right.register.sbyte_8);
+ quotient.register.sbyte_9 = (SByte)(left.register.sbyte_9 / right.register.sbyte_9);
+ quotient.register.sbyte_10 = (SByte)(left.register.sbyte_10 / right.register.sbyte_10);
+ quotient.register.sbyte_11 = (SByte)(left.register.sbyte_11 / right.register.sbyte_11);
+ quotient.register.sbyte_12 = (SByte)(left.register.sbyte_12 / right.register.sbyte_12);
+ quotient.register.sbyte_13 = (SByte)(left.register.sbyte_13 / right.register.sbyte_13);
+ quotient.register.sbyte_14 = (SByte)(left.register.sbyte_14 / right.register.sbyte_14);
+ quotient.register.sbyte_15 = (SByte)(left.register.sbyte_15 / right.register.sbyte_15);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ quotient.register.uint16_0 = (UInt16)(left.register.uint16_0 / right.register.uint16_0);
+ quotient.register.uint16_1 = (UInt16)(left.register.uint16_1 / right.register.uint16_1);
+ quotient.register.uint16_2 = (UInt16)(left.register.uint16_2 / right.register.uint16_2);
+ quotient.register.uint16_3 = (UInt16)(left.register.uint16_3 / right.register.uint16_3);
+ quotient.register.uint16_4 = (UInt16)(left.register.uint16_4 / right.register.uint16_4);
+ quotient.register.uint16_5 = (UInt16)(left.register.uint16_5 / right.register.uint16_5);
+ quotient.register.uint16_6 = (UInt16)(left.register.uint16_6 / right.register.uint16_6);
+ quotient.register.uint16_7 = (UInt16)(left.register.uint16_7 / right.register.uint16_7);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ quotient.register.int16_0 = (Int16)(left.register.int16_0 / right.register.int16_0);
+ quotient.register.int16_1 = (Int16)(left.register.int16_1 / right.register.int16_1);
+ quotient.register.int16_2 = (Int16)(left.register.int16_2 / right.register.int16_2);
+ quotient.register.int16_3 = (Int16)(left.register.int16_3 / right.register.int16_3);
+ quotient.register.int16_4 = (Int16)(left.register.int16_4 / right.register.int16_4);
+ quotient.register.int16_5 = (Int16)(left.register.int16_5 / right.register.int16_5);
+ quotient.register.int16_6 = (Int16)(left.register.int16_6 / right.register.int16_6);
+ quotient.register.int16_7 = (Int16)(left.register.int16_7 / right.register.int16_7);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ quotient.register.uint32_0 = (UInt32)(left.register.uint32_0 / right.register.uint32_0);
+ quotient.register.uint32_1 = (UInt32)(left.register.uint32_1 / right.register.uint32_1);
+ quotient.register.uint32_2 = (UInt32)(left.register.uint32_2 / right.register.uint32_2);
+ quotient.register.uint32_3 = (UInt32)(left.register.uint32_3 / right.register.uint32_3);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ quotient.register.int32_0 = (Int32)(left.register.int32_0 / right.register.int32_0);
+ quotient.register.int32_1 = (Int32)(left.register.int32_1 / right.register.int32_1);
+ quotient.register.int32_2 = (Int32)(left.register.int32_2 / right.register.int32_2);
+ quotient.register.int32_3 = (Int32)(left.register.int32_3 / right.register.int32_3);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ quotient.register.uint64_0 = (UInt64)(left.register.uint64_0 / right.register.uint64_0);
+ quotient.register.uint64_1 = (UInt64)(left.register.uint64_1 / right.register.uint64_1);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ quotient.register.int64_0 = (Int64)(left.register.int64_0 / right.register.int64_0);
+ quotient.register.int64_1 = (Int64)(left.register.int64_1 / right.register.int64_1);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ quotient.register.single_0 = (Single)(left.register.single_0 / right.register.single_0);
+ quotient.register.single_1 = (Single)(left.register.single_1 / right.register.single_1);
+ quotient.register.single_2 = (Single)(left.register.single_2 / right.register.single_2);
+ quotient.register.single_3 = (Single)(left.register.single_3 / right.register.single_3);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ quotient.register.double_0 = (Double)(left.register.double_0 / right.register.double_0);
+ quotient.register.double_1 = (Double)(left.register.double_1 / right.register.double_1);
+ }
+ return quotient;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Negates a given vector.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The negated vector.</returns>
+ public static Vector<T> operator -(Vector<T> value)
+ {
+ return Zero - value;
+ }
+ #endregion Arithmetic Operators
+
+ #region Bitwise Operators
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-and operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<T> operator &(Vector<T> left, Vector<T> right)
+ {
+ Vector<T> result = new Vector<T>();
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ Int64* resultBase = &result.register.int64_0;
+ Int64* leftBase = &left.register.int64_0;
+ Int64* rightBase = &right.register.int64_0;
+ for (int g = 0; g < Vector<Int64>.Count; g++)
+ {
+ resultBase[g] = leftBase[g] & rightBase[g];
+ }
+ }
+ else
+ {
+ result.register.int64_0 = left.register.int64_0 & right.register.int64_0;
+ result.register.int64_1 = left.register.int64_1 & right.register.int64_1;
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-or operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<T> operator |(Vector<T> left, Vector<T> right)
+ {
+ Vector<T> result = new Vector<T>();
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ Int64* resultBase = &result.register.int64_0;
+ Int64* leftBase = &left.register.int64_0;
+ Int64* rightBase = &right.register.int64_0;
+ for (int g = 0; g < Vector<Int64>.Count; g++)
+ {
+ resultBase[g] = leftBase[g] | rightBase[g];
+ }
+ }
+ else
+ {
+ result.register.int64_0 = left.register.int64_0 | right.register.int64_0;
+ result.register.int64_1 = left.register.int64_1 | right.register.int64_1;
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-exclusive-or operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<T> operator ^(Vector<T> left, Vector<T> right)
+ {
+ Vector<T> result = new Vector<T>();
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ Int64* resultBase = &result.register.int64_0;
+ Int64* leftBase = &left.register.int64_0;
+ Int64* rightBase = &right.register.int64_0;
+ for (int g = 0; g < Vector<Int64>.Count; g++)
+ {
+ resultBase[g] = leftBase[g] ^ rightBase[g];
+ }
+ }
+ else
+ {
+ result.register.int64_0 = left.register.int64_0 ^ right.register.int64_0;
+ result.register.int64_1 = left.register.int64_1 ^ right.register.int64_1;
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements are obtained by taking the one's complement of the given vector's elements.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The one's complement vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> operator ~(Vector<T> value)
+ {
+ return s_allOnes ^ value;
+ }
+ #endregion Bitwise Operators
+
+ #region Logical Operators
+ /// <summary>
+ /// Returns a boolean indicating whether each pair of elements in the given vectors are equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The first vector to compare.</param>
+ /// <returns>True if all elements are equal; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(Vector<T> left, Vector<T> right)
+ {
+ return left.Equals(right);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether any single pair of elements in the given vectors are not equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if left and right are not equal; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(Vector<T> left, Vector<T> right)
+ {
+ return !(left == right);
+ }
+ #endregion Logical Operators
+
+ #region Conversions
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [Intrinsic]
+ public static explicit operator Vector<Byte>(Vector<T> value)
+ {
+ return new Vector<Byte>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static explicit operator Vector<SByte>(Vector<T> value)
+ {
+ return new Vector<SByte>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static explicit operator Vector<UInt16>(Vector<T> value)
+ {
+ return new Vector<UInt16>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [Intrinsic]
+ public static explicit operator Vector<Int16>(Vector<T> value)
+ {
+ return new Vector<Int16>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static explicit operator Vector<UInt32>(Vector<T> value)
+ {
+ return new Vector<UInt32>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [Intrinsic]
+ public static explicit operator Vector<Int32>(Vector<T> value)
+ {
+ return new Vector<Int32>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static explicit operator Vector<UInt64>(Vector<T> value)
+ {
+ return new Vector<UInt64>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [Intrinsic]
+ public static explicit operator Vector<Int64>(Vector<T> value)
+ {
+ return new Vector<Int64>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [Intrinsic]
+ public static explicit operator Vector<Single>(Vector<T> value)
+ {
+ return new Vector<Single>(ref value.register);
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [Intrinsic]
+ public static explicit operator Vector<Double>(Vector<T> value)
+ {
+ return new Vector<Double>(ref value.register);
+ }
+
+ #endregion Conversions
+
+ #region Internal Comparison Methods
+ [Intrinsic]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe Vector<T> Equals(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Register register = new Register();
+ if (typeof(T) == typeof(Byte))
+ {
+ register.byte_0 = left.register.byte_0 == right.register.byte_0 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_1 = left.register.byte_1 == right.register.byte_1 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_2 = left.register.byte_2 == right.register.byte_2 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_3 = left.register.byte_3 == right.register.byte_3 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_4 = left.register.byte_4 == right.register.byte_4 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_5 = left.register.byte_5 == right.register.byte_5 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_6 = left.register.byte_6 == right.register.byte_6 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_7 = left.register.byte_7 == right.register.byte_7 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_8 = left.register.byte_8 == right.register.byte_8 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_9 = left.register.byte_9 == right.register.byte_9 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_10 = left.register.byte_10 == right.register.byte_10 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_11 = left.register.byte_11 == right.register.byte_11 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_12 = left.register.byte_12 == right.register.byte_12 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_13 = left.register.byte_13 == right.register.byte_13 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_14 = left.register.byte_14 == right.register.byte_14 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_15 = left.register.byte_15 == right.register.byte_15 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ register.sbyte_0 = left.register.sbyte_0 == right.register.sbyte_0 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_1 = left.register.sbyte_1 == right.register.sbyte_1 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_2 = left.register.sbyte_2 == right.register.sbyte_2 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_3 = left.register.sbyte_3 == right.register.sbyte_3 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_4 = left.register.sbyte_4 == right.register.sbyte_4 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_5 = left.register.sbyte_5 == right.register.sbyte_5 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_6 = left.register.sbyte_6 == right.register.sbyte_6 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_7 = left.register.sbyte_7 == right.register.sbyte_7 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_8 = left.register.sbyte_8 == right.register.sbyte_8 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_9 = left.register.sbyte_9 == right.register.sbyte_9 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_10 = left.register.sbyte_10 == right.register.sbyte_10 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_11 = left.register.sbyte_11 == right.register.sbyte_11 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_12 = left.register.sbyte_12 == right.register.sbyte_12 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_13 = left.register.sbyte_13 == right.register.sbyte_13 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_14 = left.register.sbyte_14 == right.register.sbyte_14 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_15 = left.register.sbyte_15 == right.register.sbyte_15 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ register.uint16_0 = left.register.uint16_0 == right.register.uint16_0 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_1 = left.register.uint16_1 == right.register.uint16_1 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_2 = left.register.uint16_2 == right.register.uint16_2 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_3 = left.register.uint16_3 == right.register.uint16_3 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_4 = left.register.uint16_4 == right.register.uint16_4 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_5 = left.register.uint16_5 == right.register.uint16_5 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_6 = left.register.uint16_6 == right.register.uint16_6 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_7 = left.register.uint16_7 == right.register.uint16_7 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ register.int16_0 = left.register.int16_0 == right.register.int16_0 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_1 = left.register.int16_1 == right.register.int16_1 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_2 = left.register.int16_2 == right.register.int16_2 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_3 = left.register.int16_3 == right.register.int16_3 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_4 = left.register.int16_4 == right.register.int16_4 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_5 = left.register.int16_5 == right.register.int16_5 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_6 = left.register.int16_6 == right.register.int16_6 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_7 = left.register.int16_7 == right.register.int16_7 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ register.uint32_0 = left.register.uint32_0 == right.register.uint32_0 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_1 = left.register.uint32_1 == right.register.uint32_1 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_2 = left.register.uint32_2 == right.register.uint32_2 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_3 = left.register.uint32_3 == right.register.uint32_3 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ register.int32_0 = left.register.int32_0 == right.register.int32_0 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_1 = left.register.int32_1 == right.register.int32_1 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_2 = left.register.int32_2 == right.register.int32_2 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_3 = left.register.int32_3 == right.register.int32_3 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ register.uint64_0 = left.register.uint64_0 == right.register.uint64_0 ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ register.uint64_1 = left.register.uint64_1 == right.register.uint64_1 ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ register.int64_0 = left.register.int64_0 == right.register.int64_0 ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ register.int64_1 = left.register.int64_1 == right.register.int64_1 ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ register.single_0 = left.register.single_0 == right.register.single_0 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_1 = left.register.single_1 == right.register.single_1 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_2 = left.register.single_2 == right.register.single_2 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_3 = left.register.single_3 == right.register.single_3 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ register.double_0 = left.register.double_0 == right.register.double_0 ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ register.double_1 = left.register.double_1 == right.register.double_1 ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ return new Vector<T>(ref register);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe Vector<T> LessThan(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Register register = new Register();
+ if (typeof(T) == typeof(Byte))
+ {
+ register.byte_0 = left.register.byte_0 < right.register.byte_0 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_1 = left.register.byte_1 < right.register.byte_1 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_2 = left.register.byte_2 < right.register.byte_2 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_3 = left.register.byte_3 < right.register.byte_3 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_4 = left.register.byte_4 < right.register.byte_4 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_5 = left.register.byte_5 < right.register.byte_5 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_6 = left.register.byte_6 < right.register.byte_6 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_7 = left.register.byte_7 < right.register.byte_7 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_8 = left.register.byte_8 < right.register.byte_8 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_9 = left.register.byte_9 < right.register.byte_9 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_10 = left.register.byte_10 < right.register.byte_10 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_11 = left.register.byte_11 < right.register.byte_11 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_12 = left.register.byte_12 < right.register.byte_12 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_13 = left.register.byte_13 < right.register.byte_13 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_14 = left.register.byte_14 < right.register.byte_14 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_15 = left.register.byte_15 < right.register.byte_15 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ register.sbyte_0 = left.register.sbyte_0 < right.register.sbyte_0 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_1 = left.register.sbyte_1 < right.register.sbyte_1 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_2 = left.register.sbyte_2 < right.register.sbyte_2 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_3 = left.register.sbyte_3 < right.register.sbyte_3 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_4 = left.register.sbyte_4 < right.register.sbyte_4 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_5 = left.register.sbyte_5 < right.register.sbyte_5 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_6 = left.register.sbyte_6 < right.register.sbyte_6 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_7 = left.register.sbyte_7 < right.register.sbyte_7 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_8 = left.register.sbyte_8 < right.register.sbyte_8 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_9 = left.register.sbyte_9 < right.register.sbyte_9 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_10 = left.register.sbyte_10 < right.register.sbyte_10 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_11 = left.register.sbyte_11 < right.register.sbyte_11 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_12 = left.register.sbyte_12 < right.register.sbyte_12 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_13 = left.register.sbyte_13 < right.register.sbyte_13 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_14 = left.register.sbyte_14 < right.register.sbyte_14 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_15 = left.register.sbyte_15 < right.register.sbyte_15 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ register.uint16_0 = left.register.uint16_0 < right.register.uint16_0 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_1 = left.register.uint16_1 < right.register.uint16_1 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_2 = left.register.uint16_2 < right.register.uint16_2 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_3 = left.register.uint16_3 < right.register.uint16_3 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_4 = left.register.uint16_4 < right.register.uint16_4 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_5 = left.register.uint16_5 < right.register.uint16_5 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_6 = left.register.uint16_6 < right.register.uint16_6 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_7 = left.register.uint16_7 < right.register.uint16_7 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ register.int16_0 = left.register.int16_0 < right.register.int16_0 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_1 = left.register.int16_1 < right.register.int16_1 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_2 = left.register.int16_2 < right.register.int16_2 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_3 = left.register.int16_3 < right.register.int16_3 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_4 = left.register.int16_4 < right.register.int16_4 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_5 = left.register.int16_5 < right.register.int16_5 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_6 = left.register.int16_6 < right.register.int16_6 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_7 = left.register.int16_7 < right.register.int16_7 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ register.uint32_0 = left.register.uint32_0 < right.register.uint32_0 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_1 = left.register.uint32_1 < right.register.uint32_1 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_2 = left.register.uint32_2 < right.register.uint32_2 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_3 = left.register.uint32_3 < right.register.uint32_3 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ register.int32_0 = left.register.int32_0 < right.register.int32_0 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_1 = left.register.int32_1 < right.register.int32_1 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_2 = left.register.int32_2 < right.register.int32_2 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_3 = left.register.int32_3 < right.register.int32_3 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ register.uint64_0 = left.register.uint64_0 < right.register.uint64_0 ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ register.uint64_1 = left.register.uint64_1 < right.register.uint64_1 ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ register.int64_0 = left.register.int64_0 < right.register.int64_0 ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ register.int64_1 = left.register.int64_1 < right.register.int64_1 ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ register.single_0 = left.register.single_0 < right.register.single_0 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_1 = left.register.single_1 < right.register.single_1 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_2 = left.register.single_2 < right.register.single_2 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_3 = left.register.single_3 < right.register.single_3 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ register.double_0 = left.register.double_0 < right.register.double_0 ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ register.double_1 = left.register.double_1 < right.register.double_1 ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ return new Vector<T>(ref register);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe Vector<T> GreaterThan(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Register register = new Register();
+ if (typeof(T) == typeof(Byte))
+ {
+ register.byte_0 = left.register.byte_0 > right.register.byte_0 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_1 = left.register.byte_1 > right.register.byte_1 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_2 = left.register.byte_2 > right.register.byte_2 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_3 = left.register.byte_3 > right.register.byte_3 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_4 = left.register.byte_4 > right.register.byte_4 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_5 = left.register.byte_5 > right.register.byte_5 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_6 = left.register.byte_6 > right.register.byte_6 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_7 = left.register.byte_7 > right.register.byte_7 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_8 = left.register.byte_8 > right.register.byte_8 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_9 = left.register.byte_9 > right.register.byte_9 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_10 = left.register.byte_10 > right.register.byte_10 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_11 = left.register.byte_11 > right.register.byte_11 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_12 = left.register.byte_12 > right.register.byte_12 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_13 = left.register.byte_13 > right.register.byte_13 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_14 = left.register.byte_14 > right.register.byte_14 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ register.byte_15 = left.register.byte_15 > right.register.byte_15 ? ConstantHelper.GetByteWithAllBitsSet() : (Byte)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ register.sbyte_0 = left.register.sbyte_0 > right.register.sbyte_0 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_1 = left.register.sbyte_1 > right.register.sbyte_1 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_2 = left.register.sbyte_2 > right.register.sbyte_2 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_3 = left.register.sbyte_3 > right.register.sbyte_3 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_4 = left.register.sbyte_4 > right.register.sbyte_4 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_5 = left.register.sbyte_5 > right.register.sbyte_5 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_6 = left.register.sbyte_6 > right.register.sbyte_6 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_7 = left.register.sbyte_7 > right.register.sbyte_7 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_8 = left.register.sbyte_8 > right.register.sbyte_8 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_9 = left.register.sbyte_9 > right.register.sbyte_9 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_10 = left.register.sbyte_10 > right.register.sbyte_10 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_11 = left.register.sbyte_11 > right.register.sbyte_11 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_12 = left.register.sbyte_12 > right.register.sbyte_12 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_13 = left.register.sbyte_13 > right.register.sbyte_13 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_14 = left.register.sbyte_14 > right.register.sbyte_14 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ register.sbyte_15 = left.register.sbyte_15 > right.register.sbyte_15 ? ConstantHelper.GetSByteWithAllBitsSet() : (SByte)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ register.uint16_0 = left.register.uint16_0 > right.register.uint16_0 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_1 = left.register.uint16_1 > right.register.uint16_1 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_2 = left.register.uint16_2 > right.register.uint16_2 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_3 = left.register.uint16_3 > right.register.uint16_3 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_4 = left.register.uint16_4 > right.register.uint16_4 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_5 = left.register.uint16_5 > right.register.uint16_5 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_6 = left.register.uint16_6 > right.register.uint16_6 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ register.uint16_7 = left.register.uint16_7 > right.register.uint16_7 ? ConstantHelper.GetUInt16WithAllBitsSet() : (UInt16)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ register.int16_0 = left.register.int16_0 > right.register.int16_0 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_1 = left.register.int16_1 > right.register.int16_1 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_2 = left.register.int16_2 > right.register.int16_2 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_3 = left.register.int16_3 > right.register.int16_3 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_4 = left.register.int16_4 > right.register.int16_4 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_5 = left.register.int16_5 > right.register.int16_5 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_6 = left.register.int16_6 > right.register.int16_6 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ register.int16_7 = left.register.int16_7 > right.register.int16_7 ? ConstantHelper.GetInt16WithAllBitsSet() : (Int16)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ register.uint32_0 = left.register.uint32_0 > right.register.uint32_0 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_1 = left.register.uint32_1 > right.register.uint32_1 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_2 = left.register.uint32_2 > right.register.uint32_2 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ register.uint32_3 = left.register.uint32_3 > right.register.uint32_3 ? ConstantHelper.GetUInt32WithAllBitsSet() : (UInt32)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ register.int32_0 = left.register.int32_0 > right.register.int32_0 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_1 = left.register.int32_1 > right.register.int32_1 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_2 = left.register.int32_2 > right.register.int32_2 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ register.int32_3 = left.register.int32_3 > right.register.int32_3 ? ConstantHelper.GetInt32WithAllBitsSet() : (Int32)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ register.uint64_0 = left.register.uint64_0 > right.register.uint64_0 ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ register.uint64_1 = left.register.uint64_1 > right.register.uint64_1 ? ConstantHelper.GetUInt64WithAllBitsSet() : (UInt64)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ register.int64_0 = left.register.int64_0 > right.register.int64_0 ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ register.int64_1 = left.register.int64_1 > right.register.int64_1 ? ConstantHelper.GetInt64WithAllBitsSet() : (Int64)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ register.single_0 = left.register.single_0 > right.register.single_0 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_1 = left.register.single_1 > right.register.single_1 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_2 = left.register.single_2 > right.register.single_2 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ register.single_3 = left.register.single_3 > right.register.single_3 ? ConstantHelper.GetSingleWithAllBitsSet() : (Single)0;
+ return new Vector<T>(ref register);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ register.double_0 = left.register.double_0 > right.register.double_0 ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ register.double_1 = left.register.double_1 > right.register.double_1 ? ConstantHelper.GetDoubleWithAllBitsSet() : (Double)0;
+ return new Vector<T>(ref register);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static Vector<T> GreaterThanOrEqual(Vector<T> left, Vector<T> right)
+ {
+ return Equals(left, right) | GreaterThan(left, right);
+ }
+
+ [Intrinsic]
+ internal static Vector<T> LessThanOrEqual(Vector<T> left, Vector<T> right)
+ {
+ return Equals(left, right) | LessThan(left, right);
+ }
+
+ [Intrinsic]
+ internal static Vector<T> ConditionalSelect(Vector<T> condition, Vector<T> left, Vector<T> right)
+ {
+ return (left & condition) | (Vector.AndNot(right, condition));
+ }
+ #endregion Comparison Methods
+
+ #region Internal Math Methods
+ [Intrinsic]
+ internal static unsafe Vector<T> Abs(Vector<T> value)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return value;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return value;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return value;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return value;
+ }
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (SByte)(object)(Math.Abs((SByte)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int16)(object)(Math.Abs((Int16)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int32)(object)(Math.Abs((Int32)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Int64)(object)(Math.Abs((Int64)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Single)(object)(Math.Abs((Single)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (Double)(object)(Math.Abs((Double)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ if (typeof(T) == typeof(SByte))
+ {
+ value.register.sbyte_0 = (SByte)(Math.Abs(value.register.sbyte_0));
+ value.register.sbyte_1 = (SByte)(Math.Abs(value.register.sbyte_1));
+ value.register.sbyte_2 = (SByte)(Math.Abs(value.register.sbyte_2));
+ value.register.sbyte_3 = (SByte)(Math.Abs(value.register.sbyte_3));
+ value.register.sbyte_4 = (SByte)(Math.Abs(value.register.sbyte_4));
+ value.register.sbyte_5 = (SByte)(Math.Abs(value.register.sbyte_5));
+ value.register.sbyte_6 = (SByte)(Math.Abs(value.register.sbyte_6));
+ value.register.sbyte_7 = (SByte)(Math.Abs(value.register.sbyte_7));
+ value.register.sbyte_8 = (SByte)(Math.Abs(value.register.sbyte_8));
+ value.register.sbyte_9 = (SByte)(Math.Abs(value.register.sbyte_9));
+ value.register.sbyte_10 = (SByte)(Math.Abs(value.register.sbyte_10));
+ value.register.sbyte_11 = (SByte)(Math.Abs(value.register.sbyte_11));
+ value.register.sbyte_12 = (SByte)(Math.Abs(value.register.sbyte_12));
+ value.register.sbyte_13 = (SByte)(Math.Abs(value.register.sbyte_13));
+ value.register.sbyte_14 = (SByte)(Math.Abs(value.register.sbyte_14));
+ value.register.sbyte_15 = (SByte)(Math.Abs(value.register.sbyte_15));
+ return value;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ value.register.int16_0 = (Int16)(Math.Abs(value.register.int16_0));
+ value.register.int16_1 = (Int16)(Math.Abs(value.register.int16_1));
+ value.register.int16_2 = (Int16)(Math.Abs(value.register.int16_2));
+ value.register.int16_3 = (Int16)(Math.Abs(value.register.int16_3));
+ value.register.int16_4 = (Int16)(Math.Abs(value.register.int16_4));
+ value.register.int16_5 = (Int16)(Math.Abs(value.register.int16_5));
+ value.register.int16_6 = (Int16)(Math.Abs(value.register.int16_6));
+ value.register.int16_7 = (Int16)(Math.Abs(value.register.int16_7));
+ return value;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ value.register.int32_0 = (Int32)(Math.Abs(value.register.int32_0));
+ value.register.int32_1 = (Int32)(Math.Abs(value.register.int32_1));
+ value.register.int32_2 = (Int32)(Math.Abs(value.register.int32_2));
+ value.register.int32_3 = (Int32)(Math.Abs(value.register.int32_3));
+ return value;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ value.register.int64_0 = (Int64)(Math.Abs(value.register.int64_0));
+ value.register.int64_1 = (Int64)(Math.Abs(value.register.int64_1));
+ return value;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ value.register.single_0 = (Single)(Math.Abs(value.register.single_0));
+ value.register.single_1 = (Single)(Math.Abs(value.register.single_1));
+ value.register.single_2 = (Single)(Math.Abs(value.register.single_2));
+ value.register.single_3 = (Single)(Math.Abs(value.register.single_3));
+ return value;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ value.register.double_0 = (Double)(Math.Abs(value.register.double_0));
+ value.register.double_1 = (Double)(Math.Abs(value.register.double_1));
+ return value;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static unsafe Vector<T> Min(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (Byte)(object)left[g] : (Byte)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (SByte)(object)left[g] : (SByte)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (UInt16)(object)left[g] : (UInt16)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (Int16)(object)left[g] : (Int16)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (UInt32)(object)left[g] : (UInt32)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (Int32)(object)left[g] : (Int32)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (UInt64)(object)left[g] : (UInt64)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (Int64)(object)left[g] : (Int64)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (Single)(object)left[g] : (Single)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (Double)(object)left[g] : (Double)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> vec = new Vector<T>();
+ if (typeof(T) == typeof(Byte))
+ {
+ vec.register.byte_0 = left.register.byte_0 < right.register.byte_0 ? left.register.byte_0 : right.register.byte_0;
+ vec.register.byte_1 = left.register.byte_1 < right.register.byte_1 ? left.register.byte_1 : right.register.byte_1;
+ vec.register.byte_2 = left.register.byte_2 < right.register.byte_2 ? left.register.byte_2 : right.register.byte_2;
+ vec.register.byte_3 = left.register.byte_3 < right.register.byte_3 ? left.register.byte_3 : right.register.byte_3;
+ vec.register.byte_4 = left.register.byte_4 < right.register.byte_4 ? left.register.byte_4 : right.register.byte_4;
+ vec.register.byte_5 = left.register.byte_5 < right.register.byte_5 ? left.register.byte_5 : right.register.byte_5;
+ vec.register.byte_6 = left.register.byte_6 < right.register.byte_6 ? left.register.byte_6 : right.register.byte_6;
+ vec.register.byte_7 = left.register.byte_7 < right.register.byte_7 ? left.register.byte_7 : right.register.byte_7;
+ vec.register.byte_8 = left.register.byte_8 < right.register.byte_8 ? left.register.byte_8 : right.register.byte_8;
+ vec.register.byte_9 = left.register.byte_9 < right.register.byte_9 ? left.register.byte_9 : right.register.byte_9;
+ vec.register.byte_10 = left.register.byte_10 < right.register.byte_10 ? left.register.byte_10 : right.register.byte_10;
+ vec.register.byte_11 = left.register.byte_11 < right.register.byte_11 ? left.register.byte_11 : right.register.byte_11;
+ vec.register.byte_12 = left.register.byte_12 < right.register.byte_12 ? left.register.byte_12 : right.register.byte_12;
+ vec.register.byte_13 = left.register.byte_13 < right.register.byte_13 ? left.register.byte_13 : right.register.byte_13;
+ vec.register.byte_14 = left.register.byte_14 < right.register.byte_14 ? left.register.byte_14 : right.register.byte_14;
+ vec.register.byte_15 = left.register.byte_15 < right.register.byte_15 ? left.register.byte_15 : right.register.byte_15;
+ return vec;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ vec.register.sbyte_0 = left.register.sbyte_0 < right.register.sbyte_0 ? left.register.sbyte_0 : right.register.sbyte_0;
+ vec.register.sbyte_1 = left.register.sbyte_1 < right.register.sbyte_1 ? left.register.sbyte_1 : right.register.sbyte_1;
+ vec.register.sbyte_2 = left.register.sbyte_2 < right.register.sbyte_2 ? left.register.sbyte_2 : right.register.sbyte_2;
+ vec.register.sbyte_3 = left.register.sbyte_3 < right.register.sbyte_3 ? left.register.sbyte_3 : right.register.sbyte_3;
+ vec.register.sbyte_4 = left.register.sbyte_4 < right.register.sbyte_4 ? left.register.sbyte_4 : right.register.sbyte_4;
+ vec.register.sbyte_5 = left.register.sbyte_5 < right.register.sbyte_5 ? left.register.sbyte_5 : right.register.sbyte_5;
+ vec.register.sbyte_6 = left.register.sbyte_6 < right.register.sbyte_6 ? left.register.sbyte_6 : right.register.sbyte_6;
+ vec.register.sbyte_7 = left.register.sbyte_7 < right.register.sbyte_7 ? left.register.sbyte_7 : right.register.sbyte_7;
+ vec.register.sbyte_8 = left.register.sbyte_8 < right.register.sbyte_8 ? left.register.sbyte_8 : right.register.sbyte_8;
+ vec.register.sbyte_9 = left.register.sbyte_9 < right.register.sbyte_9 ? left.register.sbyte_9 : right.register.sbyte_9;
+ vec.register.sbyte_10 = left.register.sbyte_10 < right.register.sbyte_10 ? left.register.sbyte_10 : right.register.sbyte_10;
+ vec.register.sbyte_11 = left.register.sbyte_11 < right.register.sbyte_11 ? left.register.sbyte_11 : right.register.sbyte_11;
+ vec.register.sbyte_12 = left.register.sbyte_12 < right.register.sbyte_12 ? left.register.sbyte_12 : right.register.sbyte_12;
+ vec.register.sbyte_13 = left.register.sbyte_13 < right.register.sbyte_13 ? left.register.sbyte_13 : right.register.sbyte_13;
+ vec.register.sbyte_14 = left.register.sbyte_14 < right.register.sbyte_14 ? left.register.sbyte_14 : right.register.sbyte_14;
+ vec.register.sbyte_15 = left.register.sbyte_15 < right.register.sbyte_15 ? left.register.sbyte_15 : right.register.sbyte_15;
+ return vec;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ vec.register.uint16_0 = left.register.uint16_0 < right.register.uint16_0 ? left.register.uint16_0 : right.register.uint16_0;
+ vec.register.uint16_1 = left.register.uint16_1 < right.register.uint16_1 ? left.register.uint16_1 : right.register.uint16_1;
+ vec.register.uint16_2 = left.register.uint16_2 < right.register.uint16_2 ? left.register.uint16_2 : right.register.uint16_2;
+ vec.register.uint16_3 = left.register.uint16_3 < right.register.uint16_3 ? left.register.uint16_3 : right.register.uint16_3;
+ vec.register.uint16_4 = left.register.uint16_4 < right.register.uint16_4 ? left.register.uint16_4 : right.register.uint16_4;
+ vec.register.uint16_5 = left.register.uint16_5 < right.register.uint16_5 ? left.register.uint16_5 : right.register.uint16_5;
+ vec.register.uint16_6 = left.register.uint16_6 < right.register.uint16_6 ? left.register.uint16_6 : right.register.uint16_6;
+ vec.register.uint16_7 = left.register.uint16_7 < right.register.uint16_7 ? left.register.uint16_7 : right.register.uint16_7;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ vec.register.int16_0 = left.register.int16_0 < right.register.int16_0 ? left.register.int16_0 : right.register.int16_0;
+ vec.register.int16_1 = left.register.int16_1 < right.register.int16_1 ? left.register.int16_1 : right.register.int16_1;
+ vec.register.int16_2 = left.register.int16_2 < right.register.int16_2 ? left.register.int16_2 : right.register.int16_2;
+ vec.register.int16_3 = left.register.int16_3 < right.register.int16_3 ? left.register.int16_3 : right.register.int16_3;
+ vec.register.int16_4 = left.register.int16_4 < right.register.int16_4 ? left.register.int16_4 : right.register.int16_4;
+ vec.register.int16_5 = left.register.int16_5 < right.register.int16_5 ? left.register.int16_5 : right.register.int16_5;
+ vec.register.int16_6 = left.register.int16_6 < right.register.int16_6 ? left.register.int16_6 : right.register.int16_6;
+ vec.register.int16_7 = left.register.int16_7 < right.register.int16_7 ? left.register.int16_7 : right.register.int16_7;
+ return vec;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ vec.register.uint32_0 = left.register.uint32_0 < right.register.uint32_0 ? left.register.uint32_0 : right.register.uint32_0;
+ vec.register.uint32_1 = left.register.uint32_1 < right.register.uint32_1 ? left.register.uint32_1 : right.register.uint32_1;
+ vec.register.uint32_2 = left.register.uint32_2 < right.register.uint32_2 ? left.register.uint32_2 : right.register.uint32_2;
+ vec.register.uint32_3 = left.register.uint32_3 < right.register.uint32_3 ? left.register.uint32_3 : right.register.uint32_3;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ vec.register.int32_0 = left.register.int32_0 < right.register.int32_0 ? left.register.int32_0 : right.register.int32_0;
+ vec.register.int32_1 = left.register.int32_1 < right.register.int32_1 ? left.register.int32_1 : right.register.int32_1;
+ vec.register.int32_2 = left.register.int32_2 < right.register.int32_2 ? left.register.int32_2 : right.register.int32_2;
+ vec.register.int32_3 = left.register.int32_3 < right.register.int32_3 ? left.register.int32_3 : right.register.int32_3;
+ return vec;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ vec.register.uint64_0 = left.register.uint64_0 < right.register.uint64_0 ? left.register.uint64_0 : right.register.uint64_0;
+ vec.register.uint64_1 = left.register.uint64_1 < right.register.uint64_1 ? left.register.uint64_1 : right.register.uint64_1;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ vec.register.int64_0 = left.register.int64_0 < right.register.int64_0 ? left.register.int64_0 : right.register.int64_0;
+ vec.register.int64_1 = left.register.int64_1 < right.register.int64_1 ? left.register.int64_1 : right.register.int64_1;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ vec.register.single_0 = left.register.single_0 < right.register.single_0 ? left.register.single_0 : right.register.single_0;
+ vec.register.single_1 = left.register.single_1 < right.register.single_1 ? left.register.single_1 : right.register.single_1;
+ vec.register.single_2 = left.register.single_2 < right.register.single_2 ? left.register.single_2 : right.register.single_2;
+ vec.register.single_3 = left.register.single_3 < right.register.single_3 ? left.register.single_3 : right.register.single_3;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ vec.register.double_0 = left.register.double_0 < right.register.double_0 ? left.register.double_0 : right.register.double_0;
+ vec.register.double_1 = left.register.double_1 < right.register.double_1 ? left.register.double_1 : right.register.double_1;
+ return vec;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static unsafe Vector<T> Max(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (Byte)(object)left[g] : (Byte)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (SByte)(object)left[g] : (SByte)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (UInt16)(object)left[g] : (UInt16)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (Int16)(object)left[g] : (Int16)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (UInt32)(object)left[g] : (UInt32)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (Int32)(object)left[g] : (Int32)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (UInt64)(object)left[g] : (UInt64)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (Int64)(object)left[g] : (Int64)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (Single)(object)left[g] : (Single)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (Double)(object)left[g] : (Double)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> vec = new Vector<T>();
+ if (typeof(T) == typeof(Byte))
+ {
+ vec.register.byte_0 = left.register.byte_0 > right.register.byte_0 ? left.register.byte_0 : right.register.byte_0;
+ vec.register.byte_1 = left.register.byte_1 > right.register.byte_1 ? left.register.byte_1 : right.register.byte_1;
+ vec.register.byte_2 = left.register.byte_2 > right.register.byte_2 ? left.register.byte_2 : right.register.byte_2;
+ vec.register.byte_3 = left.register.byte_3 > right.register.byte_3 ? left.register.byte_3 : right.register.byte_3;
+ vec.register.byte_4 = left.register.byte_4 > right.register.byte_4 ? left.register.byte_4 : right.register.byte_4;
+ vec.register.byte_5 = left.register.byte_5 > right.register.byte_5 ? left.register.byte_5 : right.register.byte_5;
+ vec.register.byte_6 = left.register.byte_6 > right.register.byte_6 ? left.register.byte_6 : right.register.byte_6;
+ vec.register.byte_7 = left.register.byte_7 > right.register.byte_7 ? left.register.byte_7 : right.register.byte_7;
+ vec.register.byte_8 = left.register.byte_8 > right.register.byte_8 ? left.register.byte_8 : right.register.byte_8;
+ vec.register.byte_9 = left.register.byte_9 > right.register.byte_9 ? left.register.byte_9 : right.register.byte_9;
+ vec.register.byte_10 = left.register.byte_10 > right.register.byte_10 ? left.register.byte_10 : right.register.byte_10;
+ vec.register.byte_11 = left.register.byte_11 > right.register.byte_11 ? left.register.byte_11 : right.register.byte_11;
+ vec.register.byte_12 = left.register.byte_12 > right.register.byte_12 ? left.register.byte_12 : right.register.byte_12;
+ vec.register.byte_13 = left.register.byte_13 > right.register.byte_13 ? left.register.byte_13 : right.register.byte_13;
+ vec.register.byte_14 = left.register.byte_14 > right.register.byte_14 ? left.register.byte_14 : right.register.byte_14;
+ vec.register.byte_15 = left.register.byte_15 > right.register.byte_15 ? left.register.byte_15 : right.register.byte_15;
+ return vec;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ vec.register.sbyte_0 = left.register.sbyte_0 > right.register.sbyte_0 ? left.register.sbyte_0 : right.register.sbyte_0;
+ vec.register.sbyte_1 = left.register.sbyte_1 > right.register.sbyte_1 ? left.register.sbyte_1 : right.register.sbyte_1;
+ vec.register.sbyte_2 = left.register.sbyte_2 > right.register.sbyte_2 ? left.register.sbyte_2 : right.register.sbyte_2;
+ vec.register.sbyte_3 = left.register.sbyte_3 > right.register.sbyte_3 ? left.register.sbyte_3 : right.register.sbyte_3;
+ vec.register.sbyte_4 = left.register.sbyte_4 > right.register.sbyte_4 ? left.register.sbyte_4 : right.register.sbyte_4;
+ vec.register.sbyte_5 = left.register.sbyte_5 > right.register.sbyte_5 ? left.register.sbyte_5 : right.register.sbyte_5;
+ vec.register.sbyte_6 = left.register.sbyte_6 > right.register.sbyte_6 ? left.register.sbyte_6 : right.register.sbyte_6;
+ vec.register.sbyte_7 = left.register.sbyte_7 > right.register.sbyte_7 ? left.register.sbyte_7 : right.register.sbyte_7;
+ vec.register.sbyte_8 = left.register.sbyte_8 > right.register.sbyte_8 ? left.register.sbyte_8 : right.register.sbyte_8;
+ vec.register.sbyte_9 = left.register.sbyte_9 > right.register.sbyte_9 ? left.register.sbyte_9 : right.register.sbyte_9;
+ vec.register.sbyte_10 = left.register.sbyte_10 > right.register.sbyte_10 ? left.register.sbyte_10 : right.register.sbyte_10;
+ vec.register.sbyte_11 = left.register.sbyte_11 > right.register.sbyte_11 ? left.register.sbyte_11 : right.register.sbyte_11;
+ vec.register.sbyte_12 = left.register.sbyte_12 > right.register.sbyte_12 ? left.register.sbyte_12 : right.register.sbyte_12;
+ vec.register.sbyte_13 = left.register.sbyte_13 > right.register.sbyte_13 ? left.register.sbyte_13 : right.register.sbyte_13;
+ vec.register.sbyte_14 = left.register.sbyte_14 > right.register.sbyte_14 ? left.register.sbyte_14 : right.register.sbyte_14;
+ vec.register.sbyte_15 = left.register.sbyte_15 > right.register.sbyte_15 ? left.register.sbyte_15 : right.register.sbyte_15;
+ return vec;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ vec.register.uint16_0 = left.register.uint16_0 > right.register.uint16_0 ? left.register.uint16_0 : right.register.uint16_0;
+ vec.register.uint16_1 = left.register.uint16_1 > right.register.uint16_1 ? left.register.uint16_1 : right.register.uint16_1;
+ vec.register.uint16_2 = left.register.uint16_2 > right.register.uint16_2 ? left.register.uint16_2 : right.register.uint16_2;
+ vec.register.uint16_3 = left.register.uint16_3 > right.register.uint16_3 ? left.register.uint16_3 : right.register.uint16_3;
+ vec.register.uint16_4 = left.register.uint16_4 > right.register.uint16_4 ? left.register.uint16_4 : right.register.uint16_4;
+ vec.register.uint16_5 = left.register.uint16_5 > right.register.uint16_5 ? left.register.uint16_5 : right.register.uint16_5;
+ vec.register.uint16_6 = left.register.uint16_6 > right.register.uint16_6 ? left.register.uint16_6 : right.register.uint16_6;
+ vec.register.uint16_7 = left.register.uint16_7 > right.register.uint16_7 ? left.register.uint16_7 : right.register.uint16_7;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ vec.register.int16_0 = left.register.int16_0 > right.register.int16_0 ? left.register.int16_0 : right.register.int16_0;
+ vec.register.int16_1 = left.register.int16_1 > right.register.int16_1 ? left.register.int16_1 : right.register.int16_1;
+ vec.register.int16_2 = left.register.int16_2 > right.register.int16_2 ? left.register.int16_2 : right.register.int16_2;
+ vec.register.int16_3 = left.register.int16_3 > right.register.int16_3 ? left.register.int16_3 : right.register.int16_3;
+ vec.register.int16_4 = left.register.int16_4 > right.register.int16_4 ? left.register.int16_4 : right.register.int16_4;
+ vec.register.int16_5 = left.register.int16_5 > right.register.int16_5 ? left.register.int16_5 : right.register.int16_5;
+ vec.register.int16_6 = left.register.int16_6 > right.register.int16_6 ? left.register.int16_6 : right.register.int16_6;
+ vec.register.int16_7 = left.register.int16_7 > right.register.int16_7 ? left.register.int16_7 : right.register.int16_7;
+ return vec;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ vec.register.uint32_0 = left.register.uint32_0 > right.register.uint32_0 ? left.register.uint32_0 : right.register.uint32_0;
+ vec.register.uint32_1 = left.register.uint32_1 > right.register.uint32_1 ? left.register.uint32_1 : right.register.uint32_1;
+ vec.register.uint32_2 = left.register.uint32_2 > right.register.uint32_2 ? left.register.uint32_2 : right.register.uint32_2;
+ vec.register.uint32_3 = left.register.uint32_3 > right.register.uint32_3 ? left.register.uint32_3 : right.register.uint32_3;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ vec.register.int32_0 = left.register.int32_0 > right.register.int32_0 ? left.register.int32_0 : right.register.int32_0;
+ vec.register.int32_1 = left.register.int32_1 > right.register.int32_1 ? left.register.int32_1 : right.register.int32_1;
+ vec.register.int32_2 = left.register.int32_2 > right.register.int32_2 ? left.register.int32_2 : right.register.int32_2;
+ vec.register.int32_3 = left.register.int32_3 > right.register.int32_3 ? left.register.int32_3 : right.register.int32_3;
+ return vec;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ vec.register.uint64_0 = left.register.uint64_0 > right.register.uint64_0 ? left.register.uint64_0 : right.register.uint64_0;
+ vec.register.uint64_1 = left.register.uint64_1 > right.register.uint64_1 ? left.register.uint64_1 : right.register.uint64_1;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ vec.register.int64_0 = left.register.int64_0 > right.register.int64_0 ? left.register.int64_0 : right.register.int64_0;
+ vec.register.int64_1 = left.register.int64_1 > right.register.int64_1 ? left.register.int64_1 : right.register.int64_1;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ vec.register.single_0 = left.register.single_0 > right.register.single_0 ? left.register.single_0 : right.register.single_0;
+ vec.register.single_1 = left.register.single_1 > right.register.single_1 ? left.register.single_1 : right.register.single_1;
+ vec.register.single_2 = left.register.single_2 > right.register.single_2 ? left.register.single_2 : right.register.single_2;
+ vec.register.single_3 = left.register.single_3 > right.register.single_3 ? left.register.single_3 : right.register.single_3;
+ return vec;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ vec.register.double_0 = left.register.double_0 > right.register.double_0 ? left.register.double_0 : right.register.double_0;
+ vec.register.double_1 = left.register.double_1 > right.register.double_1 ? left.register.double_1 : right.register.double_1;
+ return vec;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static T DotProduct(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ T product = default;
+ for (int g = 0; g < Count; g++)
+ {
+ product = ScalarAdd(product, ScalarMultiply(left[g], right[g]));
+ }
+ return product;
+ }
+ else
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte product = 0;
+ product += (Byte)(left.register.byte_0 * right.register.byte_0);
+ product += (Byte)(left.register.byte_1 * right.register.byte_1);
+ product += (Byte)(left.register.byte_2 * right.register.byte_2);
+ product += (Byte)(left.register.byte_3 * right.register.byte_3);
+ product += (Byte)(left.register.byte_4 * right.register.byte_4);
+ product += (Byte)(left.register.byte_5 * right.register.byte_5);
+ product += (Byte)(left.register.byte_6 * right.register.byte_6);
+ product += (Byte)(left.register.byte_7 * right.register.byte_7);
+ product += (Byte)(left.register.byte_8 * right.register.byte_8);
+ product += (Byte)(left.register.byte_9 * right.register.byte_9);
+ product += (Byte)(left.register.byte_10 * right.register.byte_10);
+ product += (Byte)(left.register.byte_11 * right.register.byte_11);
+ product += (Byte)(left.register.byte_12 * right.register.byte_12);
+ product += (Byte)(left.register.byte_13 * right.register.byte_13);
+ product += (Byte)(left.register.byte_14 * right.register.byte_14);
+ product += (Byte)(left.register.byte_15 * right.register.byte_15);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte product = 0;
+ product += (SByte)(left.register.sbyte_0 * right.register.sbyte_0);
+ product += (SByte)(left.register.sbyte_1 * right.register.sbyte_1);
+ product += (SByte)(left.register.sbyte_2 * right.register.sbyte_2);
+ product += (SByte)(left.register.sbyte_3 * right.register.sbyte_3);
+ product += (SByte)(left.register.sbyte_4 * right.register.sbyte_4);
+ product += (SByte)(left.register.sbyte_5 * right.register.sbyte_5);
+ product += (SByte)(left.register.sbyte_6 * right.register.sbyte_6);
+ product += (SByte)(left.register.sbyte_7 * right.register.sbyte_7);
+ product += (SByte)(left.register.sbyte_8 * right.register.sbyte_8);
+ product += (SByte)(left.register.sbyte_9 * right.register.sbyte_9);
+ product += (SByte)(left.register.sbyte_10 * right.register.sbyte_10);
+ product += (SByte)(left.register.sbyte_11 * right.register.sbyte_11);
+ product += (SByte)(left.register.sbyte_12 * right.register.sbyte_12);
+ product += (SByte)(left.register.sbyte_13 * right.register.sbyte_13);
+ product += (SByte)(left.register.sbyte_14 * right.register.sbyte_14);
+ product += (SByte)(left.register.sbyte_15 * right.register.sbyte_15);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16 product = 0;
+ product += (UInt16)(left.register.uint16_0 * right.register.uint16_0);
+ product += (UInt16)(left.register.uint16_1 * right.register.uint16_1);
+ product += (UInt16)(left.register.uint16_2 * right.register.uint16_2);
+ product += (UInt16)(left.register.uint16_3 * right.register.uint16_3);
+ product += (UInt16)(left.register.uint16_4 * right.register.uint16_4);
+ product += (UInt16)(left.register.uint16_5 * right.register.uint16_5);
+ product += (UInt16)(left.register.uint16_6 * right.register.uint16_6);
+ product += (UInt16)(left.register.uint16_7 * right.register.uint16_7);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16 product = 0;
+ product += (Int16)(left.register.int16_0 * right.register.int16_0);
+ product += (Int16)(left.register.int16_1 * right.register.int16_1);
+ product += (Int16)(left.register.int16_2 * right.register.int16_2);
+ product += (Int16)(left.register.int16_3 * right.register.int16_3);
+ product += (Int16)(left.register.int16_4 * right.register.int16_4);
+ product += (Int16)(left.register.int16_5 * right.register.int16_5);
+ product += (Int16)(left.register.int16_6 * right.register.int16_6);
+ product += (Int16)(left.register.int16_7 * right.register.int16_7);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32 product = 0;
+ product += (UInt32)(left.register.uint32_0 * right.register.uint32_0);
+ product += (UInt32)(left.register.uint32_1 * right.register.uint32_1);
+ product += (UInt32)(left.register.uint32_2 * right.register.uint32_2);
+ product += (UInt32)(left.register.uint32_3 * right.register.uint32_3);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32 product = 0;
+ product += (Int32)(left.register.int32_0 * right.register.int32_0);
+ product += (Int32)(left.register.int32_1 * right.register.int32_1);
+ product += (Int32)(left.register.int32_2 * right.register.int32_2);
+ product += (Int32)(left.register.int32_3 * right.register.int32_3);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64 product = 0;
+ product += (UInt64)(left.register.uint64_0 * right.register.uint64_0);
+ product += (UInt64)(left.register.uint64_1 * right.register.uint64_1);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64 product = 0;
+ product += (Int64)(left.register.int64_0 * right.register.int64_0);
+ product += (Int64)(left.register.int64_1 * right.register.int64_1);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single product = 0;
+ product += (Single)(left.register.single_0 * right.register.single_0);
+ product += (Single)(left.register.single_1 * right.register.single_1);
+ product += (Single)(left.register.single_2 * right.register.single_2);
+ product += (Single)(left.register.single_3 * right.register.single_3);
+ return (T)(object)product;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double product = 0;
+ product += (Double)(left.register.double_0 * right.register.double_0);
+ product += (Double)(left.register.double_1 * right.register.double_1);
+ return (T)(object)product;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static unsafe Vector<T> SquareRoot(Vector<T> value)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte* dataPtr = stackalloc Byte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((Byte)Math.Sqrt((Byte)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte* dataPtr = stackalloc SByte[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((SByte)Math.Sqrt((SByte)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16* dataPtr = stackalloc UInt16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((UInt16)Math.Sqrt((UInt16)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16* dataPtr = stackalloc Int16[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((Int16)Math.Sqrt((Int16)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32* dataPtr = stackalloc UInt32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((UInt32)Math.Sqrt((UInt32)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32* dataPtr = stackalloc Int32[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((Int32)Math.Sqrt((Int32)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64* dataPtr = stackalloc UInt64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((UInt64)Math.Sqrt((UInt64)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64* dataPtr = stackalloc Int64[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((Int64)Math.Sqrt((Int64)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single* dataPtr = stackalloc Single[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((Single)Math.Sqrt((Single)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double* dataPtr = stackalloc Double[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((Double)Math.Sqrt((Double)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ value.register.byte_0 = (Byte)Math.Sqrt(value.register.byte_0);
+ value.register.byte_1 = (Byte)Math.Sqrt(value.register.byte_1);
+ value.register.byte_2 = (Byte)Math.Sqrt(value.register.byte_2);
+ value.register.byte_3 = (Byte)Math.Sqrt(value.register.byte_3);
+ value.register.byte_4 = (Byte)Math.Sqrt(value.register.byte_4);
+ value.register.byte_5 = (Byte)Math.Sqrt(value.register.byte_5);
+ value.register.byte_6 = (Byte)Math.Sqrt(value.register.byte_6);
+ value.register.byte_7 = (Byte)Math.Sqrt(value.register.byte_7);
+ value.register.byte_8 = (Byte)Math.Sqrt(value.register.byte_8);
+ value.register.byte_9 = (Byte)Math.Sqrt(value.register.byte_9);
+ value.register.byte_10 = (Byte)Math.Sqrt(value.register.byte_10);
+ value.register.byte_11 = (Byte)Math.Sqrt(value.register.byte_11);
+ value.register.byte_12 = (Byte)Math.Sqrt(value.register.byte_12);
+ value.register.byte_13 = (Byte)Math.Sqrt(value.register.byte_13);
+ value.register.byte_14 = (Byte)Math.Sqrt(value.register.byte_14);
+ value.register.byte_15 = (Byte)Math.Sqrt(value.register.byte_15);
+ return value;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ value.register.sbyte_0 = (SByte)Math.Sqrt(value.register.sbyte_0);
+ value.register.sbyte_1 = (SByte)Math.Sqrt(value.register.sbyte_1);
+ value.register.sbyte_2 = (SByte)Math.Sqrt(value.register.sbyte_2);
+ value.register.sbyte_3 = (SByte)Math.Sqrt(value.register.sbyte_3);
+ value.register.sbyte_4 = (SByte)Math.Sqrt(value.register.sbyte_4);
+ value.register.sbyte_5 = (SByte)Math.Sqrt(value.register.sbyte_5);
+ value.register.sbyte_6 = (SByte)Math.Sqrt(value.register.sbyte_6);
+ value.register.sbyte_7 = (SByte)Math.Sqrt(value.register.sbyte_7);
+ value.register.sbyte_8 = (SByte)Math.Sqrt(value.register.sbyte_8);
+ value.register.sbyte_9 = (SByte)Math.Sqrt(value.register.sbyte_9);
+ value.register.sbyte_10 = (SByte)Math.Sqrt(value.register.sbyte_10);
+ value.register.sbyte_11 = (SByte)Math.Sqrt(value.register.sbyte_11);
+ value.register.sbyte_12 = (SByte)Math.Sqrt(value.register.sbyte_12);
+ value.register.sbyte_13 = (SByte)Math.Sqrt(value.register.sbyte_13);
+ value.register.sbyte_14 = (SByte)Math.Sqrt(value.register.sbyte_14);
+ value.register.sbyte_15 = (SByte)Math.Sqrt(value.register.sbyte_15);
+ return value;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ value.register.uint16_0 = (UInt16)Math.Sqrt(value.register.uint16_0);
+ value.register.uint16_1 = (UInt16)Math.Sqrt(value.register.uint16_1);
+ value.register.uint16_2 = (UInt16)Math.Sqrt(value.register.uint16_2);
+ value.register.uint16_3 = (UInt16)Math.Sqrt(value.register.uint16_3);
+ value.register.uint16_4 = (UInt16)Math.Sqrt(value.register.uint16_4);
+ value.register.uint16_5 = (UInt16)Math.Sqrt(value.register.uint16_5);
+ value.register.uint16_6 = (UInt16)Math.Sqrt(value.register.uint16_6);
+ value.register.uint16_7 = (UInt16)Math.Sqrt(value.register.uint16_7);
+ return value;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ value.register.int16_0 = (Int16)Math.Sqrt(value.register.int16_0);
+ value.register.int16_1 = (Int16)Math.Sqrt(value.register.int16_1);
+ value.register.int16_2 = (Int16)Math.Sqrt(value.register.int16_2);
+ value.register.int16_3 = (Int16)Math.Sqrt(value.register.int16_3);
+ value.register.int16_4 = (Int16)Math.Sqrt(value.register.int16_4);
+ value.register.int16_5 = (Int16)Math.Sqrt(value.register.int16_5);
+ value.register.int16_6 = (Int16)Math.Sqrt(value.register.int16_6);
+ value.register.int16_7 = (Int16)Math.Sqrt(value.register.int16_7);
+ return value;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ value.register.uint32_0 = (UInt32)Math.Sqrt(value.register.uint32_0);
+ value.register.uint32_1 = (UInt32)Math.Sqrt(value.register.uint32_1);
+ value.register.uint32_2 = (UInt32)Math.Sqrt(value.register.uint32_2);
+ value.register.uint32_3 = (UInt32)Math.Sqrt(value.register.uint32_3);
+ return value;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ value.register.int32_0 = (Int32)Math.Sqrt(value.register.int32_0);
+ value.register.int32_1 = (Int32)Math.Sqrt(value.register.int32_1);
+ value.register.int32_2 = (Int32)Math.Sqrt(value.register.int32_2);
+ value.register.int32_3 = (Int32)Math.Sqrt(value.register.int32_3);
+ return value;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ value.register.uint64_0 = (UInt64)Math.Sqrt(value.register.uint64_0);
+ value.register.uint64_1 = (UInt64)Math.Sqrt(value.register.uint64_1);
+ return value;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ value.register.int64_0 = (Int64)Math.Sqrt(value.register.int64_0);
+ value.register.int64_1 = (Int64)Math.Sqrt(value.register.int64_1);
+ return value;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ value.register.single_0 = (Single)Math.Sqrt(value.register.single_0);
+ value.register.single_1 = (Single)Math.Sqrt(value.register.single_1);
+ value.register.single_2 = (Single)Math.Sqrt(value.register.single_2);
+ value.register.single_3 = (Single)Math.Sqrt(value.register.single_3);
+ return value;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ value.register.double_0 = (Double)Math.Sqrt(value.register.double_0);
+ value.register.double_1 = (Double)Math.Sqrt(value.register.double_1);
+ return value;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+ #endregion Internal Math Methods
+
+ #region Helper Methods
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static bool ScalarEquals(T left, T right)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return (Byte)(object)left == (Byte)(object)right;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return (SByte)(object)left == (SByte)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return (UInt16)(object)left == (UInt16)(object)right;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return (Int16)(object)left == (Int16)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return (UInt32)(object)left == (UInt32)(object)right;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return (Int32)(object)left == (Int32)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return (UInt64)(object)left == (UInt64)(object)right;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return (Int64)(object)left == (Int64)(object)right;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return (Single)(object)left == (Single)(object)right;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return (Double)(object)left == (Double)(object)right;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static bool ScalarLessThan(T left, T right)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return (Byte)(object)left < (Byte)(object)right;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return (SByte)(object)left < (SByte)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return (UInt16)(object)left < (UInt16)(object)right;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return (Int16)(object)left < (Int16)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return (UInt32)(object)left < (UInt32)(object)right;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return (Int32)(object)left < (Int32)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return (UInt64)(object)left < (UInt64)(object)right;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return (Int64)(object)left < (Int64)(object)right;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return (Single)(object)left < (Single)(object)right;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return (Double)(object)left < (Double)(object)right;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static bool ScalarGreaterThan(T left, T right)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return (Byte)(object)left > (Byte)(object)right;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return (SByte)(object)left > (SByte)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return (UInt16)(object)left > (UInt16)(object)right;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return (Int16)(object)left > (Int16)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return (UInt32)(object)left > (UInt32)(object)right;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return (Int32)(object)left > (Int32)(object)right;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return (UInt64)(object)left > (UInt64)(object)right;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return (Int64)(object)left > (Int64)(object)right;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return (Single)(object)left > (Single)(object)right;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return (Double)(object)left > (Double)(object)right;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T ScalarAdd(T left, T right)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return (T)(object)unchecked((Byte)((Byte)(object)left + (Byte)(object)right));
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return (T)(object)unchecked((SByte)((SByte)(object)left + (SByte)(object)right));
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return (T)(object)unchecked((UInt16)((UInt16)(object)left + (UInt16)(object)right));
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return (T)(object)unchecked((Int16)((Int16)(object)left + (Int16)(object)right));
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return (T)(object)unchecked((UInt32)((UInt32)(object)left + (UInt32)(object)right));
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return (T)(object)unchecked((Int32)((Int32)(object)left + (Int32)(object)right));
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return (T)(object)unchecked((UInt64)((UInt64)(object)left + (UInt64)(object)right));
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return (T)(object)unchecked((Int64)((Int64)(object)left + (Int64)(object)right));
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return (T)(object)unchecked((Single)((Single)(object)left + (Single)(object)right));
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return (T)(object)unchecked((Double)((Double)(object)left + (Double)(object)right));
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T ScalarSubtract(T left, T right)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return (T)(object)(Byte)((Byte)(object)left - (Byte)(object)right);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return (T)(object)(SByte)((SByte)(object)left - (SByte)(object)right);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return (T)(object)(UInt16)((UInt16)(object)left - (UInt16)(object)right);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return (T)(object)(Int16)((Int16)(object)left - (Int16)(object)right);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return (T)(object)(UInt32)((UInt32)(object)left - (UInt32)(object)right);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return (T)(object)(Int32)((Int32)(object)left - (Int32)(object)right);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return (T)(object)(UInt64)((UInt64)(object)left - (UInt64)(object)right);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return (T)(object)(Int64)((Int64)(object)left - (Int64)(object)right);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return (T)(object)(Single)((Single)(object)left - (Single)(object)right);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return (T)(object)(Double)((Double)(object)left - (Double)(object)right);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T ScalarMultiply(T left, T right)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return (T)(object)unchecked((Byte)((Byte)(object)left * (Byte)(object)right));
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return (T)(object)unchecked((SByte)((SByte)(object)left * (SByte)(object)right));
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return (T)(object)unchecked((UInt16)((UInt16)(object)left * (UInt16)(object)right));
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return (T)(object)unchecked((Int16)((Int16)(object)left * (Int16)(object)right));
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return (T)(object)unchecked((UInt32)((UInt32)(object)left * (UInt32)(object)right));
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return (T)(object)unchecked((Int32)((Int32)(object)left * (Int32)(object)right));
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return (T)(object)unchecked((UInt64)((UInt64)(object)left * (UInt64)(object)right));
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return (T)(object)unchecked((Int64)((Int64)(object)left * (Int64)(object)right));
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return (T)(object)unchecked((Single)((Single)(object)left * (Single)(object)right));
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return (T)(object)unchecked((Double)((Double)(object)left * (Double)(object)right));
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T ScalarDivide(T left, T right)
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return (T)(object)(Byte)((Byte)(object)left / (Byte)(object)right);
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return (T)(object)(SByte)((SByte)(object)left / (SByte)(object)right);
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return (T)(object)(UInt16)((UInt16)(object)left / (UInt16)(object)right);
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return (T)(object)(Int16)((Int16)(object)left / (Int16)(object)right);
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return (T)(object)(UInt32)((UInt32)(object)left / (UInt32)(object)right);
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return (T)(object)(Int32)((Int32)(object)left / (Int32)(object)right);
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return (T)(object)(UInt64)((UInt64)(object)left / (UInt64)(object)right);
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return (T)(object)(Int64)((Int64)(object)left / (Int64)(object)right);
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return (T)(object)(Single)((Single)(object)left / (Single)(object)right);
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return (T)(object)(Double)((Double)(object)left / (Double)(object)right);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T GetOneValue()
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ Byte value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ SByte value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ UInt16 value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ Int16 value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ UInt32 value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ Int32 value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ UInt64 value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ Int64 value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ Single value = 1;
+ return (T)(object)value;
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ Double value = 1;
+ return (T)(object)value;
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T GetAllBitsSetValue()
+ {
+ if (typeof(T) == typeof(Byte))
+ {
+ return (T)(object)ConstantHelper.GetByteWithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(SByte))
+ {
+ return (T)(object)ConstantHelper.GetSByteWithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(UInt16))
+ {
+ return (T)(object)ConstantHelper.GetUInt16WithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(Int16))
+ {
+ return (T)(object)ConstantHelper.GetInt16WithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(UInt32))
+ {
+ return (T)(object)ConstantHelper.GetUInt32WithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(Int32))
+ {
+ return (T)(object)ConstantHelper.GetInt32WithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(UInt64))
+ {
+ return (T)(object)ConstantHelper.GetUInt64WithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(Int64))
+ {
+ return (T)(object)ConstantHelper.GetInt64WithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(Single))
+ {
+ return (T)(object)ConstantHelper.GetSingleWithAllBitsSet();
+ }
+ else if (typeof(T) == typeof(Double))
+ {
+ return (T)(object)ConstantHelper.GetDoubleWithAllBitsSet();
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ #endregion
+ }
+
+ [Intrinsic]
+ public static partial class Vector
+ {
+ #region Widen/Narrow
+ /// <summary>
+ /// Widens a Vector{Byte} into two Vector{UInt16}'s.
+ /// <param name="source">The source vector whose elements are widened into the outputs.</param>
+ /// <param name="low">The first output vector, whose elements will contain the widened elements from lower indices in the source vector.</param>
+ /// <param name="high">The second output vector, whose elements will contain the widened elements from higher indices in the source vector.</param>
+ /// </summary>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe void Widen(Vector<Byte> source, out Vector<UInt16> low, out Vector<UInt16> high)
+ {
+ int elements = Vector<Byte>.Count;
+ UInt16* lowPtr = stackalloc UInt16[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ lowPtr[i] = (UInt16)source[i];
+ }
+ UInt16* highPtr = stackalloc UInt16[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ highPtr[i] = (UInt16)source[i + (elements / 2)];
+ }
+
+ low = new Vector<UInt16>(lowPtr);
+ high = new Vector<UInt16>(highPtr);
+ }
+
+ /// <summary>
+ /// Widens a Vector{UInt16} into two Vector{UInt32}'s.
+ /// <param name="source">The source vector whose elements are widened into the outputs.</param>
+ /// <param name="low">The first output vector, whose elements will contain the widened elements from lower indices in the source vector.</param>
+ /// <param name="high">The second output vector, whose elements will contain the widened elements from higher indices in the source vector.</param>
+ /// </summary>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe void Widen(Vector<UInt16> source, out Vector<UInt32> low, out Vector<UInt32> high)
+ {
+ int elements = Vector<UInt16>.Count;
+ UInt32* lowPtr = stackalloc UInt32[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ lowPtr[i] = (UInt32)source[i];
+ }
+ UInt32* highPtr = stackalloc UInt32[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ highPtr[i] = (UInt32)source[i + (elements / 2)];
+ }
+
+ low = new Vector<UInt32>(lowPtr);
+ high = new Vector<UInt32>(highPtr);
+ }
+
+ /// <summary>
+ /// Widens a Vector{UInt32} into two Vector{UInt64}'s.
+ /// <param name="source">The source vector whose elements are widened into the outputs.</param>
+ /// <param name="low">The first output vector, whose elements will contain the widened elements from lower indices in the source vector.</param>
+ /// <param name="high">The second output vector, whose elements will contain the widened elements from higher indices in the source vector.</param>
+ /// </summary>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe void Widen(Vector<UInt32> source, out Vector<UInt64> low, out Vector<UInt64> high)
+ {
+ int elements = Vector<UInt32>.Count;
+ UInt64* lowPtr = stackalloc UInt64[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ lowPtr[i] = (UInt64)source[i];
+ }
+ UInt64* highPtr = stackalloc UInt64[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ highPtr[i] = (UInt64)source[i + (elements / 2)];
+ }
+
+ low = new Vector<UInt64>(lowPtr);
+ high = new Vector<UInt64>(highPtr);
+ }
+
+ /// <summary>
+ /// Widens a Vector{SByte} into two Vector{Int16}'s.
+ /// <param name="source">The source vector whose elements are widened into the outputs.</param>
+ /// <param name="low">The first output vector, whose elements will contain the widened elements from lower indices in the source vector.</param>
+ /// <param name="high">The second output vector, whose elements will contain the widened elements from higher indices in the source vector.</param>
+ /// </summary>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe void Widen(Vector<SByte> source, out Vector<Int16> low, out Vector<Int16> high)
+ {
+ int elements = Vector<SByte>.Count;
+ Int16* lowPtr = stackalloc Int16[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ lowPtr[i] = (Int16)source[i];
+ }
+ Int16* highPtr = stackalloc Int16[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ highPtr[i] = (Int16)source[i + (elements / 2)];
+ }
+
+ low = new Vector<Int16>(lowPtr);
+ high = new Vector<Int16>(highPtr);
+ }
+
+ /// <summary>
+ /// Widens a Vector{Int16} into two Vector{Int32}'s.
+ /// <param name="source">The source vector whose elements are widened into the outputs.</param>
+ /// <param name="low">The first output vector, whose elements will contain the widened elements from lower indices in the source vector.</param>
+ /// <param name="high">The second output vector, whose elements will contain the widened elements from higher indices in the source vector.</param>
+ /// </summary>
+ [Intrinsic]
+ public static unsafe void Widen(Vector<Int16> source, out Vector<Int32> low, out Vector<Int32> high)
+ {
+ int elements = Vector<Int16>.Count;
+ Int32* lowPtr = stackalloc Int32[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ lowPtr[i] = (Int32)source[i];
+ }
+ Int32* highPtr = stackalloc Int32[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ highPtr[i] = (Int32)source[i + (elements / 2)];
+ }
+
+ low = new Vector<Int32>(lowPtr);
+ high = new Vector<Int32>(highPtr);
+ }
+
+ /// <summary>
+ /// Widens a Vector{Int32} into two Vector{Int64}'s.
+ /// <param name="source">The source vector whose elements are widened into the outputs.</param>
+ /// <param name="low">The first output vector, whose elements will contain the widened elements from lower indices in the source vector.</param>
+ /// <param name="high">The second output vector, whose elements will contain the widened elements from higher indices in the source vector.</param>
+ /// </summary>
+ [Intrinsic]
+ public static unsafe void Widen(Vector<Int32> source, out Vector<Int64> low, out Vector<Int64> high)
+ {
+ int elements = Vector<Int32>.Count;
+ Int64* lowPtr = stackalloc Int64[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ lowPtr[i] = (Int64)source[i];
+ }
+ Int64* highPtr = stackalloc Int64[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ highPtr[i] = (Int64)source[i + (elements / 2)];
+ }
+
+ low = new Vector<Int64>(lowPtr);
+ high = new Vector<Int64>(highPtr);
+ }
+
+ /// <summary>
+ /// Widens a Vector{Single} into two Vector{Double}'s.
+ /// <param name="source">The source vector whose elements are widened into the outputs.</param>
+ /// <param name="low">The first output vector, whose elements will contain the widened elements from lower indices in the source vector.</param>
+ /// <param name="high">The second output vector, whose elements will contain the widened elements from higher indices in the source vector.</param>
+ /// </summary>
+ [Intrinsic]
+ public static unsafe void Widen(Vector<Single> source, out Vector<Double> low, out Vector<Double> high)
+ {
+ int elements = Vector<Single>.Count;
+ Double* lowPtr = stackalloc Double[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ lowPtr[i] = (Double)source[i];
+ }
+ Double* highPtr = stackalloc Double[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ highPtr[i] = (Double)source[i + (elements / 2)];
+ }
+
+ low = new Vector<Double>(lowPtr);
+ high = new Vector<Double>(highPtr);
+ }
+
+ /// <summary>
+ /// Narrows two Vector{UInt16}'s into one Vector{Byte}.
+ /// <param name="low">The first source vector, whose elements become the lower-index elements of the return value.</param>
+ /// <param name="high">The second source vector, whose elements become the higher-index elements of the return value.</param>
+ /// <returns>A Vector{Byte} containing elements narrowed from the source vectors.</returns>
+ /// </summary>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe Vector<Byte> Narrow(Vector<UInt16> low, Vector<UInt16> high)
+ {
+ unchecked
+ {
+ int elements = Vector<Byte>.Count;
+ Byte* retPtr = stackalloc Byte[elements];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i] = (Byte)low[i];
+ }
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i + (elements / 2)] = (Byte)high[i];
+ }
+
+ return new Vector<Byte>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Narrows two Vector{UInt32}'s into one Vector{UInt16}.
+ /// <param name="low">The first source vector, whose elements become the lower-index elements of the return value.</param>
+ /// <param name="high">The second source vector, whose elements become the higher-index elements of the return value.</param>
+ /// <returns>A Vector{UInt16} containing elements narrowed from the source vectors.</returns>
+ /// </summary>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe Vector<UInt16> Narrow(Vector<UInt32> low, Vector<UInt32> high)
+ {
+ unchecked
+ {
+ int elements = Vector<UInt16>.Count;
+ UInt16* retPtr = stackalloc UInt16[elements];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i] = (UInt16)low[i];
+ }
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i + (elements / 2)] = (UInt16)high[i];
+ }
+
+ return new Vector<UInt16>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Narrows two Vector{UInt64}'s into one Vector{UInt32}.
+ /// <param name="low">The first source vector, whose elements become the lower-index elements of the return value.</param>
+ /// <param name="high">The second source vector, whose elements become the higher-index elements of the return value.</param>
+ /// <returns>A Vector{UInt32} containing elements narrowed from the source vectors.</returns>
+ /// </summary>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe Vector<UInt32> Narrow(Vector<UInt64> low, Vector<UInt64> high)
+ {
+ unchecked
+ {
+ int elements = Vector<UInt32>.Count;
+ UInt32* retPtr = stackalloc UInt32[elements];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i] = (UInt32)low[i];
+ }
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i + (elements / 2)] = (UInt32)high[i];
+ }
+
+ return new Vector<UInt32>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Narrows two Vector{Int16}'s into one Vector{SByte}.
+ /// <param name="low">The first source vector, whose elements become the lower-index elements of the return value.</param>
+ /// <param name="high">The second source vector, whose elements become the higher-index elements of the return value.</param>
+ /// <returns>A Vector{SByte} containing elements narrowed from the source vectors.</returns>
+ /// </summary>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe Vector<SByte> Narrow(Vector<Int16> low, Vector<Int16> high)
+ {
+ unchecked
+ {
+ int elements = Vector<SByte>.Count;
+ SByte* retPtr = stackalloc SByte[elements];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i] = (SByte)low[i];
+ }
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i + (elements / 2)] = (SByte)high[i];
+ }
+
+ return new Vector<SByte>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Narrows two Vector{Int32}'s into one Vector{Int16}.
+ /// <param name="low">The first source vector, whose elements become the lower-index elements of the return value.</param>
+ /// <param name="high">The second source vector, whose elements become the higher-index elements of the return value.</param>
+ /// <returns>A Vector{Int16} containing elements narrowed from the source vectors.</returns>
+ /// </summary>
+ [Intrinsic]
+ public static unsafe Vector<Int16> Narrow(Vector<Int32> low, Vector<Int32> high)
+ {
+ unchecked
+ {
+ int elements = Vector<Int16>.Count;
+ Int16* retPtr = stackalloc Int16[elements];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i] = (Int16)low[i];
+ }
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i + (elements / 2)] = (Int16)high[i];
+ }
+
+ return new Vector<Int16>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Narrows two Vector{Int64}'s into one Vector{Int32}.
+ /// <param name="low">The first source vector, whose elements become the lower-index elements of the return value.</param>
+ /// <param name="high">The second source vector, whose elements become the higher-index elements of the return value.</param>
+ /// <returns>A Vector{Int32} containing elements narrowed from the source vectors.</returns>
+ /// </summary>
+ [Intrinsic]
+ public static unsafe Vector<Int32> Narrow(Vector<Int64> low, Vector<Int64> high)
+ {
+ unchecked
+ {
+ int elements = Vector<Int32>.Count;
+ Int32* retPtr = stackalloc Int32[elements];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i] = (Int32)low[i];
+ }
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i + (elements / 2)] = (Int32)high[i];
+ }
+
+ return new Vector<Int32>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Narrows two Vector{Double}'s into one Vector{Single}.
+ /// <param name="low">The first source vector, whose elements become the lower-index elements of the return value.</param>
+ /// <param name="high">The second source vector, whose elements become the higher-index elements of the return value.</param>
+ /// <returns>A Vector{Single} containing elements narrowed from the source vectors.</returns>
+ /// </summary>
+ [Intrinsic]
+ public static unsafe Vector<Single> Narrow(Vector<Double> low, Vector<Double> high)
+ {
+ unchecked
+ {
+ int elements = Vector<Single>.Count;
+ Single* retPtr = stackalloc Single[elements];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i] = (Single)low[i];
+ }
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i + (elements / 2)] = (Single)high[i];
+ }
+
+ return new Vector<Single>(retPtr);
+ }
+ }
+
+ #endregion Widen/Narrow
+
+ #region Same-Size Conversion
+ /// <summary>
+ /// Converts a Vector{Int32} to a Vector{Single}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<Single> ConvertToSingle(Vector<Int32> value)
+ {
+ unchecked
+ {
+ int elements = Vector<Single>.Count;
+ Single* retPtr = stackalloc Single[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (Single)value[i];
+ }
+
+ return new Vector<Single>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Converts a Vector{UInt32} to a Vector{Single}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe Vector<Single> ConvertToSingle(Vector<UInt32> value)
+ {
+ unchecked
+ {
+ int elements = Vector<Single>.Count;
+ Single* retPtr = stackalloc Single[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (Single)value[i];
+ }
+
+ return new Vector<Single>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Converts a Vector{Int64} to a Vector{Double}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<Double> ConvertToDouble(Vector<Int64> value)
+ {
+ unchecked
+ {
+ int elements = Vector<Double>.Count;
+ Double* retPtr = stackalloc Double[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (Double)value[i];
+ }
+
+ return new Vector<Double>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Converts a Vector{UInt64} to a Vector{Double}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe Vector<Double> ConvertToDouble(Vector<UInt64> value)
+ {
+ unchecked
+ {
+ int elements = Vector<Double>.Count;
+ Double* retPtr = stackalloc Double[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (Double)value[i];
+ }
+
+ return new Vector<Double>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Converts a Vector{Single} to a Vector{Int32}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<Int32> ConvertToInt32(Vector<Single> value)
+ {
+ unchecked
+ {
+ int elements = Vector<Int32>.Count;
+ Int32* retPtr = stackalloc Int32[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (Int32)value[i];
+ }
+
+ return new Vector<Int32>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Converts a Vector{Single} to a Vector{UInt32}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe Vector<UInt32> ConvertToUInt32(Vector<Single> value)
+ {
+ unchecked
+ {
+ int elements = Vector<UInt32>.Count;
+ UInt32* retPtr = stackalloc UInt32[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (UInt32)value[i];
+ }
+
+ return new Vector<UInt32>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Converts a Vector{Double} to a Vector{Int64}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<Int64> ConvertToInt64(Vector<Double> value)
+ {
+ unchecked
+ {
+ int elements = Vector<Int64>.Count;
+ Int64* retPtr = stackalloc Int64[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (Int64)value[i];
+ }
+
+ return new Vector<Int64>(retPtr);
+ }
+ }
+
+ /// <summary>
+ /// Converts a Vector{Double} to a Vector{UInt64}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+ [CLSCompliant(false)]
+ [Intrinsic]
+ public static unsafe Vector<UInt64> ConvertToUInt64(Vector<Double> value)
+ {
+ unchecked
+ {
+ int elements = Vector<UInt64>.Count;
+ UInt64* retPtr = stackalloc UInt64[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (UInt64)value[i];
+ }
+
+ return new Vector<UInt64>(retPtr);
+ }
+ }
+
+ #endregion Same-Size Conversion
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Numerics/Vector.tt b/src/System.Private.CoreLib/shared/System/Numerics/Vector.tt
new file mode 100644
index 0000000000..d7622466b7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Numerics/Vector.tt
@@ -0,0 +1,1834 @@
+<#@ template debug="true" hostSpecific="true" #>
+<#@ output extension=".cs" #>
+<#@ Assembly Name="System.Core.dll" #>
+<#@ Assembly Name="System.Xml.dll" #>
+<#@ import namespace="System" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Runtime.InteropServices" #>
+<#@ include file="GenerationConfig.ttinclude" #><# GenerateCopyrightHeader(); #>
+
+#if netcoreapp
+using Internal.Runtime.CompilerServices;
+#endif
+using System.Globalization;
+using System.Numerics.Hashing;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Numerics
+{
+ /* Note: The following patterns are used throughout the code here and are described here
+ *
+ * PATTERN:
+ * if (typeof(T) == typeof(Int32)) { ... }
+ * else if (typeof(T) == typeof(Single)) { ... }
+ * EXPLANATION:
+ * At runtime, each instantiation of Vector<T> will be type-specific, and each of these typeof blocks will be eliminated,
+ * as typeof(T) is a (JIT) compile-time constant for each instantiation. This design was chosen to eliminate any overhead from
+ * delegates and other patterns.
+ *
+ * PATTERN:
+ * if (Vector.IsHardwareAccelerated) { ... }
+ * else { ... }
+ * EXPLANATION
+ * This pattern solves two problems:
+ * 1. Allows us to unroll loops when we know the size (when no hardware acceleration is present)
+ * 2. Allows reflection to work:
+ * - If a method is called via reflection, it will not be "intrinsified", which would cause issues if we did
+ * not provide an implementation for that case (i.e. if it only included a case which assumed 16-byte registers)
+ * (NOTE: It is assumed that Vector.IsHardwareAccelerated will be a compile-time constant, eliminating these checks
+ * from the JIT'd code.)
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /// <summary>
+ /// A structure that represents a single Vector. The count of this Vector is fixed but CPU register dependent.
+ /// This struct only supports numerical types. This type is intended to be used as a building block for vectorizing
+ /// large algorithms. This type is immutable, individual elements cannot be modified.
+ /// </summary>
+ [Intrinsic]
+ public struct Vector<T> : IEquatable<Vector<T>>, IFormattable where T : struct
+ {
+ #region Fields
+ private Register register;
+ #endregion Fields
+
+ #region Static Members
+ /// <summary>
+ /// Returns the number of elements stored in the vector. This value is hardware dependent.
+ /// </summary>
+ public static int Count
+ {
+ [Intrinsic]
+ get
+ {
+ return s_count;
+ }
+ }
+ private static readonly int s_count = InitializeCount();
+
+ /// <summary>
+ /// Returns a vector containing all zeroes.
+ /// </summary>
+ public static Vector<T> Zero
+ {
+ [Intrinsic]
+ get
+ {
+ return s_zero;
+ }
+ }
+ private static readonly Vector<T> s_zero = new Vector<T>();
+
+ /// <summary>
+ /// Returns a vector containing all ones.
+ /// </summary>
+ public static Vector<T> One
+ {
+ [Intrinsic]
+ get
+ {
+ return s_one;
+ }
+ }
+ private static readonly Vector<T> s_one = new Vector<T>(GetOneValue());
+
+ internal static Vector<T> AllOnes { get { return s_allOnes; } }
+ private static readonly Vector<T> s_allOnes = new Vector<T>(GetAllBitsSetValue());
+ #endregion Static Members
+
+ #region Static Initialization
+ private struct VectorSizeHelper
+ {
+ internal Vector<T> _placeholder;
+ internal byte _byte;
+ }
+
+ // Calculates the size of this struct in bytes, by computing the offset of a field in a structure
+ private static unsafe int InitializeCount()
+ {
+ VectorSizeHelper vsh;
+ byte* vectorBase = &vsh._placeholder.register.byte_0;
+ byte* byteBase = &vsh._byte;
+ int vectorSizeInBytes = (int)(byteBase - vectorBase);
+
+ int typeSizeInBytes = -1;
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ typeSizeInBytes = sizeof(<#=type.Name#>);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+
+ return vectorSizeInBytes / typeSizeInBytes;
+ }
+ #endregion Static Initialization
+
+ #region Constructors
+ /// <summary>
+ /// Constructs a vector whose components are all <code>value</code>
+ /// </summary>
+ [Intrinsic]
+ public unsafe Vector(T value)
+ : this()
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ fixed (<#=type.Name#>* basePtr = &this.<#=GetRegisterFieldName(type, 0)#>)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (<#=type.Name#>)(object)value;
+ }
+ }
+ }
+<#
+ }
+#>
+ }
+ else
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < totalSize / Marshal.SizeOf(type); g++)
+ {
+#>
+ <#=GetRegisterFieldName(type, g)#> = (<#=type.Name#>)(object)value;
+<#
+ }
+#>
+ }
+<#
+ }
+#>
+ }
+ }
+
+ /// <summary>
+ /// Constructs a vector from the given array. The size of the given array must be at least Vector'T.Count.
+ /// </summary>
+ [Intrinsic]
+ public unsafe Vector(T[] values) : this(values, 0) { }
+
+ /// <summary>
+ /// Constructs a vector from the given array, starting from the given index.
+ /// The array must contain at least Vector'T.Count from the given index.
+ /// </summary>
+ public unsafe Vector(T[] values, int index)
+ : this()
+ {
+ if (values == null)
+ {
+ // Match the JIT's exception type here. For perf, a NullReference is thrown instead of an ArgumentNull.
+ throw new NullReferenceException(SR.Arg_NullArgumentNullRef);
+ }
+ if (index < 0 || (values.Length - index) < Count)
+ {
+ throw new IndexOutOfRangeException(SR.Format(SR.Arg_InsufficientNumberOfElements, Vector<T>.Count, nameof(values)));
+ }
+
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ fixed (<#=type.Name#>* basePtr = &this.<#=GetRegisterFieldName(type, 0)#>)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ *(basePtr + g) = (<#=type.Name#>)(object)values[g + index];
+ }
+ }
+ }
+<#
+ }
+#>
+ }
+ else
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ fixed (<#=type.Name#>* basePtr = &this.<#=GetRegisterFieldName(type, 0)#>)
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ *(basePtr + <#=g#>) = (<#=type.Name#>)(object)values[<#=g#> + index];
+<#
+ }
+#>
+ }
+ }
+<#
+ }
+#>
+ }
+ }
+
+#pragma warning disable 3001 // void* is not a CLS-Compliant argument type
+ internal unsafe Vector(void* dataPointer) : this(dataPointer, 0) { }
+#pragma warning restore 3001 // void* is not a CLS-Compliant argument type
+
+#pragma warning disable 3001 // void* is not a CLS-Compliant argument type
+ // Implemented with offset if this API ever becomes public; an offset of 0 is used internally.
+ internal unsafe Vector(void* dataPointer, int offset)
+ : this()
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* castedPtr = (<#=type.Name#>*)dataPointer;
+ castedPtr += offset;
+ fixed (<#=type.Name#>* registerBase = &this.<#=GetRegisterFieldName(type, 0)#>)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ registerBase[g] = castedPtr[g];
+ }
+ }
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+#pragma warning restore 3001 // void* is not a CLS-Compliant argument type
+
+ private Vector(ref Register existingRegister)
+ {
+ this.register = existingRegister;
+ }
+
+#if netcoreapp
+ /// <summary>
+ /// Constructs a vector from the given span. The span must contain at least Vector'T.Count elements.
+ /// </summary>
+ public Vector(Span<T> values)
+ : this()
+ {
+ <#=GenerateIfConditionAllTypes(supportedTypes)#>
+ {
+ if (values.Length < Count)
+ {
+ throw new IndexOutOfRangeException(SR.Format(SR.Arg_InsufficientNumberOfElements, Vector<T>.Count, nameof(values)));
+ }
+ this = Unsafe.ReadUnaligned<Vector<T>>(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)));
+ }
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+#endif
+ #endregion Constructors
+
+ #region Public Instance Methods
+ /// <summary>
+ /// Copies the vector to the given destination array. The destination array must be at least size Vector'T.Count.
+ /// </summary>
+ /// <param name="destination">The destination array which the values are copied into</param>
+ /// <exception cref="ArgumentNullException">If the destination array is null</exception>
+ /// <exception cref="ArgumentException">If number of elements in source vector is greater than those available in destination array</exception>
+ [Intrinsic]
+ public unsafe void CopyTo(T[] destination)
+ {
+ CopyTo(destination, 0);
+ }
+
+ /// <summary>
+ /// Copies the vector to the given destination array. The destination array must be at least size Vector'T.Count.
+ /// </summary>
+ /// <param name="destination">The destination array which the values are copied into</param>
+ /// <param name="startIndex">The index to start copying to</param>
+ /// <exception cref="ArgumentNullException">If the destination array is null</exception>
+ /// <exception cref="ArgumentOutOfRangeException">If index is greater than end of the array or index is less than zero</exception>
+ /// <exception cref="ArgumentException">If number of elements in source vector is greater than those available in destination array</exception>
+ [Intrinsic]
+ public unsafe void CopyTo(T[] destination, int startIndex)
+ {
+ if (destination == null)
+ {
+ // Match the JIT's exception type here. For perf, a NullReference is thrown instead of an ArgumentNull.
+ throw new NullReferenceException(SR.Arg_NullArgumentNullRef);
+ }
+ if (startIndex < 0 || startIndex >= destination.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.Format(SR.Arg_ArgumentOutOfRangeException, startIndex));
+ }
+ if ((destination.Length - startIndex) < Count)
+ {
+ throw new ArgumentException(SR.Format(SR.Arg_ElementsInSourceIsGreaterThanDestination, startIndex));
+ }
+
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>[] <#=type.Name.ToLowerInvariant()#>Array = (<#=type.Name#>[])(object)destination;
+ fixed (<#=type.Name#>* destinationBase = <#=type.Name.ToLowerInvariant()#>Array)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ destinationBase[startIndex + g] = (<#=type.Name#>)(object)this[g];
+ }
+ }
+ }
+<#
+ }
+#>
+ }
+ else
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>[] <#=type.Name.ToLowerInvariant()#>Array = (<#=type.Name#>[])(object)destination;
+ fixed (<#=type.Name#>* destinationBase = <#=type.Name.ToLowerInvariant()#>Array)
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ destinationBase[startIndex + <#=g#>] = this.<#=GetRegisterFieldName(type, g)#>;
+<#
+ }
+#>
+ }
+ }
+<#
+ }
+#>
+ }
+ }
+
+ /// <summary>
+ /// Returns the element at the given index.
+ /// </summary>
+ public unsafe T this[int index]
+ {
+ [Intrinsic]
+ get
+ {
+ if (index >= Count || index < 0)
+ {
+ throw new IndexOutOfRangeException(SR.Format(SR.Arg_ArgumentOutOfRangeException, index));
+ }
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ fixed (<#=type.Name#>* basePtr = &this.<#=GetRegisterFieldName(type, 0)#>)
+ {
+ return (T)(object)*(basePtr + index);
+ }
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether the given Object is equal to this vector instance.
+ /// </summary>
+ /// <param name="obj">The Object to compare against.</param>
+ /// <returns>True if the Object is equal to this vector; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (!(obj is Vector<T>))
+ {
+ return false;
+ }
+ return Equals((Vector<T>)obj);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether the given vector is equal to this vector instance.
+ /// </summary>
+ /// <param name="other">The vector to compare this instance to.</param>
+ /// <returns>True if the other vector is equal to this instance; False otherwise.</returns>
+ [Intrinsic]
+ public bool Equals(Vector<T> other)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ if (!ScalarEquals(this[g], other[g]))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+<#
+ if (g == 0)
+ {
+#>
+ this.<#=GetRegisterFieldName(type, g)#> == other.<#=GetRegisterFieldName(type, g)#>
+<#
+ }
+ else
+ {
+#>
+ && this.<#=GetRegisterFieldName(type, g)#> == other.<#=GetRegisterFieldName(type, g)#><#=(g == (GetNumFields(type, totalSize) -1)) ? ";" : ""#>
+<#
+ }
+#>
+<#
+ }
+#>
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns the hash code for this instance.
+ /// </summary>
+ /// <returns>The hash code.</returns>
+ public override int GetHashCode()
+ {
+ int hash = 0;
+
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ for (int g = 0; g < Count; g++)
+ {
+ hash = HashHelpers.Combine(hash, ((<#=type.Name#>)(object)this[g]).GetHashCode());
+ }
+ return hash;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ hash = HashHelpers.Combine(hash, this.<#=GetRegisterFieldName(type, g)#>.GetHashCode());
+<#
+ }
+#>
+ return hash;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a String representing this vector.
+ /// </summary>
+ /// <returns>The string representation.</returns>
+ public override string ToString()
+ {
+ return ToString("G", CultureInfo.CurrentCulture);
+ }
+
+ /// <summary>
+ /// Returns a String representing this vector, using the specified format string to format individual elements.
+ /// </summary>
+ /// <param name="format">The format of individual elements.</param>
+ /// <returns>The string representation.</returns>
+ public string ToString(string format)
+ {
+ return ToString(format, CultureInfo.CurrentCulture);
+ }
+
+ /// <summary>
+ /// Returns a String representing this vector, using the specified format string to format individual elements
+ /// and the given IFormatProvider.
+ /// </summary>
+ /// <param name="format">The format of individual elements.</param>
+ /// <param name="formatProvider">The format provider to use when formatting elements.</param>
+ /// <returns>The string representation.</returns>
+ public string ToString(string format, IFormatProvider formatProvider)
+ {
+ StringBuilder sb = new StringBuilder();
+ string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator;
+ sb.Append('<');
+ for (int g = 0; g < Count - 1; g++)
+ {
+ sb.Append(((IFormattable)this[g]).ToString(format, formatProvider));
+ sb.Append(separator);
+ sb.Append(' ');
+ }
+ // Append last element w/out separator
+ sb.Append(((IFormattable)this[Count - 1]).ToString(format, formatProvider));
+ sb.Append('>');
+ return sb.ToString();
+ }
+ #endregion Public Instance Methods
+
+ #region Arithmetic Operators
+ /// <summary>
+ /// Adds two vectors together.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The summed vector.</returns>
+ public static unsafe Vector<T> operator +(Vector<T> left, Vector<T> right)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (<#=type.Name#>)(object)ScalarAdd(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> sum = new Vector<T>();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ sum.<#= GetRegisterFieldName(type, g) #> = (<#=type.Name#>)(left.<#= GetRegisterFieldName(type, g) #> + right.<#= GetRegisterFieldName(type, g) #>);
+<#
+ }
+#>
+ }
+<#
+ }
+#>
+ return sum;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Subtracts the second vector from the first.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The difference vector.</returns>
+ public static unsafe Vector<T> operator -(Vector<T> left, Vector<T> right)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (<#=type.Name#>)(object)ScalarSubtract(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> difference = new Vector<T>();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ difference.<#= GetRegisterFieldName(type, g) #> = (<#=type.Name#>)(left.<#= GetRegisterFieldName(type, g) #> - right.<#= GetRegisterFieldName(type, g) #>);
+<#
+ }
+#>
+ }
+<#
+ }
+#>
+ return difference;
+ }
+ }
+ }
+
+ // This method is intrinsic only for certain types. It cannot access fields directly unless we are sure the context is unaccelerated.
+ /// <summary>
+ /// Multiplies two vectors together.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The product vector.</returns>
+ public static unsafe Vector<T> operator *(Vector<T> left, Vector<T> right)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (<#=type.Name#>)(object)ScalarMultiply(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> product = new Vector<T>();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ product.<#= GetRegisterFieldName(type, g) #> = (<#=type.Name#>)(left.<#= GetRegisterFieldName(type, g) #> * right.<#= GetRegisterFieldName(type, g) #>);
+<#
+ }
+#>
+ }
+<#
+ }
+#>
+ return product;
+ }
+ }
+ }
+
+ // This method is intrinsic only for certain types. It cannot access fields directly unless we are sure the context is unaccelerated.
+ /// <summary>
+ /// Multiplies a vector by the given scalar.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <param name="factor">The scalar value.</param>
+ /// <returns>The scaled vector.</returns>
+ public static Vector<T> operator *(Vector<T> value, T factor)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ return new Vector<T>(factor) * value;
+ }
+ else
+ {
+ Vector<T> product = new Vector<T>();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ product.<#= GetRegisterFieldName(type, g) #> = (<#=type.Name#>)(value.<#= GetRegisterFieldName(type, g) #> * (<#=type.Name#>)(object)factor);
+<#
+ }
+#>
+ }
+<#
+ }
+#>
+ return product;
+ }
+ }
+ }
+
+ // This method is intrinsic only for certain types. It cannot access fields directly unless we are sure the context is unaccelerated.
+ /// <summary>
+ /// Multiplies a vector by the given scalar.
+ /// </summary>
+ /// <param name="factor">The scalar value.</param>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The scaled vector.</returns>
+ public static Vector<T> operator *(T factor, Vector<T> value)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ return new Vector<T>(factor) * value;
+ }
+ else
+ {
+ Vector<T> product = new Vector<T>();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ product.<#= GetRegisterFieldName(type, g) #> = (<#=type.Name#>)(value.<#= GetRegisterFieldName(type, g) #> * (<#=type.Name#>)(object)factor);
+<#
+ }
+#>
+ }
+<#
+ }
+#>
+ return product;
+ }
+ }
+ }
+
+ // This method is intrinsic only for certain types. It cannot access fields directly unless we are sure the context is unaccelerated.
+ /// <summary>
+ /// Divides the first vector by the second.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The vector resulting from the division.</returns>
+ public static unsafe Vector<T> operator /(Vector<T> left, Vector<T> right)
+ {
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (<#=type.Name#>)(object)ScalarDivide(left[g], right[g]);
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> quotient = new Vector<T>();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ quotient.<#= GetRegisterFieldName(type, g) #> = (<#=type.Name#>)(left.<#= GetRegisterFieldName(type, g) #> / right.<#= GetRegisterFieldName(type, g) #>);
+<#
+ }
+#>
+ }
+<#
+ }
+#>
+ return quotient;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Negates a given vector.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The negated vector.</returns>
+ public static Vector<T> operator -(Vector<T> value)
+ {
+ return Zero - value;
+ }
+ #endregion Arithmetic Operators
+
+ #region Bitwise Operators
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-and operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<T> operator &(Vector<T> left, Vector<T> right)
+ {
+ Vector<T> result = new Vector<T>();
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ Int64* resultBase = &result.register.int64_0;
+ Int64* leftBase = &left.register.int64_0;
+ Int64* rightBase = &right.register.int64_0;
+ for (int g = 0; g < Vector<Int64>.Count; g++)
+ {
+ resultBase[g] = leftBase[g] & rightBase[g];
+ }
+ }
+ else
+ {
+ result.<#=GetRegisterFieldName(typeof(Int64), 0)#> = left.<#=GetRegisterFieldName(typeof(Int64), 0)#> & right.<#=GetRegisterFieldName(typeof(Int64), 0)#>;
+ result.<#=GetRegisterFieldName(typeof(Int64), 1)#> = left.<#=GetRegisterFieldName(typeof(Int64), 1)#> & right.<#=GetRegisterFieldName(typeof(Int64), 1)#>;
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-or operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<T> operator |(Vector<T> left, Vector<T> right)
+ {
+ Vector<T> result = new Vector<T>();
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ Int64* resultBase = &result.register.int64_0;
+ Int64* leftBase = &left.register.int64_0;
+ Int64* rightBase = &right.register.int64_0;
+ for (int g = 0; g < Vector<Int64>.Count; g++)
+ {
+ resultBase[g] = leftBase[g] | rightBase[g];
+ }
+ }
+ else
+ {
+ result.<#=GetRegisterFieldName(typeof(Int64), 0)#> = left.<#=GetRegisterFieldName(typeof(Int64), 0)#> | right.<#=GetRegisterFieldName(typeof(Int64), 0)#>;
+ result.<#=GetRegisterFieldName(typeof(Int64), 1)#> = left.<#=GetRegisterFieldName(typeof(Int64), 1)#> | right.<#=GetRegisterFieldName(typeof(Int64), 1)#>;
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-exclusive-or operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [Intrinsic]
+ public static unsafe Vector<T> operator ^(Vector<T> left, Vector<T> right)
+ {
+ Vector<T> result = new Vector<T>();
+ unchecked
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ Int64* resultBase = &result.register.int64_0;
+ Int64* leftBase = &left.register.int64_0;
+ Int64* rightBase = &right.register.int64_0;
+ for (int g = 0; g < Vector<Int64>.Count; g++)
+ {
+ resultBase[g] = leftBase[g] ^ rightBase[g];
+ }
+ }
+ else
+ {
+ result.<#=GetRegisterFieldName(typeof(Int64), 0)#> = left.<#=GetRegisterFieldName(typeof(Int64), 0)#> ^ right.<#=GetRegisterFieldName(typeof(Int64), 0)#>;
+ result.<#=GetRegisterFieldName(typeof(Int64), 1)#> = left.<#=GetRegisterFieldName(typeof(Int64), 1)#> ^ right.<#=GetRegisterFieldName(typeof(Int64), 1)#>;
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements are obtained by taking the one's complement of the given vector's elements.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The one's complement vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> operator ~(Vector<T> value)
+ {
+ return s_allOnes ^ value;
+ }
+ #endregion Bitwise Operators
+
+ #region Logical Operators
+ /// <summary>
+ /// Returns a boolean indicating whether each pair of elements in the given vectors are equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The first vector to compare.</param>
+ /// <returns>True if all elements are equal; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(Vector<T> left, Vector<T> right)
+ {
+ return left.Equals(right);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether any single pair of elements in the given vectors are not equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if left and right are not equal; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(Vector<T> left, Vector<T> right)
+ {
+ return !(left == right);
+ }
+ #endregion Logical Operators
+
+ #region Conversions
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of another type.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+<#
+ if (nonClsCompliantTypes.Contains(type))
+ {
+#>
+ [CLSCompliant(false)]
+<#
+ }
+#>
+ [Intrinsic]
+ public static explicit operator Vector<<#=type.Name#>>(Vector<T> value)
+ {
+ return new Vector<<#=type.Name#>>(ref value.register);
+ }
+
+<#
+ }
+#>
+ #endregion Conversions
+
+ #region Internal Comparison Methods
+ [Intrinsic]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe Vector<T> Equals(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarEquals(left[g], right[g]) ? ConstantHelper.Get<#=type.Name#>WithAllBitsSet() : (<#=type.Name#>)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Register register = new Register();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ <#= GetRegisterFieldName(type, g) #> = left.<#= GetRegisterFieldName(type, g) #> == right.<#= GetRegisterFieldName(type, g) #> ? ConstantHelper.Get<#=type.Name#>WithAllBitsSet() : (<#=type.Name#>)0;
+<#
+ }
+#>
+ return new Vector<T>(ref register);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe Vector<T> LessThan(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? ConstantHelper.Get<#=type.Name#>WithAllBitsSet() : (<#=type.Name#>)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Register register = new Register();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ <#= GetRegisterFieldName(type, g) #> = left.<#= GetRegisterFieldName(type, g) #> < right.<#= GetRegisterFieldName(type, g) #> ? ConstantHelper.Get<#=type.Name#>WithAllBitsSet() : (<#=type.Name#>)0;
+<#
+ }
+#>
+ return new Vector<T>(ref register);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe Vector<T> GreaterThan(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? ConstantHelper.Get<#=type.Name#>WithAllBitsSet() : (<#=type.Name#>)0;
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Register register = new Register();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ <#= GetRegisterFieldName(type, g) #> = left.<#= GetRegisterFieldName(type, g) #> > right.<#= GetRegisterFieldName(type, g) #> ? ConstantHelper.Get<#=type.Name#>WithAllBitsSet() : (<#=type.Name#>)0;
+<#
+ }
+#>
+ return new Vector<T>(ref register);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static Vector<T> GreaterThanOrEqual(Vector<T> left, Vector<T> right)
+ {
+ return Equals(left, right) | GreaterThan(left, right);
+ }
+
+ [Intrinsic]
+ internal static Vector<T> LessThanOrEqual(Vector<T> left, Vector<T> right)
+ {
+ return Equals(left, right) | LessThan(left, right);
+ }
+
+ [Intrinsic]
+ internal static Vector<T> ConditionalSelect(Vector<T> condition, Vector<T> left, Vector<T> right)
+ {
+ return (left & condition) | (Vector.AndNot(right, condition));
+ }
+ #endregion Comparison Methods
+
+ #region Internal Math Methods
+ [Intrinsic]
+ internal static unsafe Vector<T> Abs(Vector<T> value)
+ {
+<#
+ foreach (Type type in supportedTypes)
+ {
+ if (unsignedTypes.Contains(type))
+ {
+#>
+ <#=GenerateIfStatementHeader(type, unsignedTypes)#>
+ {
+ return value;
+ }
+<#
+ }
+ }
+#>
+ if (Vector.IsHardwareAccelerated)
+ {
+<#
+ foreach (Type type in supportedTypes.Except(unsignedTypes))
+ {
+#>
+ <#=GenerateIfStatementHeader(type, supportedTypes.Except(unsignedTypes))#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = (<#=type.Name#>)(object)(Math.Abs((<#=type.Name#>)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+<#
+ foreach (Type type in supportedTypes.Except(unsignedTypes))
+ {
+#>
+ <#=GenerateIfStatementHeader(type, supportedTypes.Except(unsignedTypes))#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ value.<#=GetRegisterFieldName(type, g)#> = (<#=type.Name#>)(Math.Abs(value.<#=GetRegisterFieldName(type, g)#>));
+<#
+ }
+#>
+ return value;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static unsafe Vector<T> Min(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarLessThan(left[g], right[g]) ? (<#=type.Name#>)(object)left[g] : (<#=type.Name#>)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> vec = new Vector<T>();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ vec.<#=GetRegisterFieldName(type, g)#> = left.<#=GetRegisterFieldName(type, g)#> < right.<#=GetRegisterFieldName(type, g)#> ? left.<#=GetRegisterFieldName(type, g)#> : right.<#=GetRegisterFieldName(type, g)#>;
+<#
+ }
+#>
+ return vec;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static unsafe Vector<T> Max(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = ScalarGreaterThan(left[g], right[g]) ? (<#=type.Name#>)(object)left[g] : (<#=type.Name#>)(object)right[g];
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+ Vector<T> vec = new Vector<T>();
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ vec.<#=GetRegisterFieldName(type, g)#> = left.<#=GetRegisterFieldName(type, g)#> > right.<#=GetRegisterFieldName(type, g)#> ? left.<#=GetRegisterFieldName(type, g)#> : right.<#=GetRegisterFieldName(type, g)#>;
+<#
+ }
+#>
+ return vec;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static T DotProduct(Vector<T> left, Vector<T> right)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ T product = default;
+ for (int g = 0; g < Count; g++)
+ {
+ product = ScalarAdd(product, ScalarMultiply(left[g], right[g]));
+ }
+ return product;
+ }
+ else
+ {
+<# foreach (Type type in supportedTypes)
+ {
+ #>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#> product = 0;
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ product += (<#=type.Name#>)(left.<#=GetRegisterFieldName(type, g)#> * right.<#=GetRegisterFieldName(type, g)#>);
+<#
+ }
+#>
+ return (T)(object)product;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+
+ [Intrinsic]
+ internal static unsafe Vector<T> SquareRoot(Vector<T> value)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#>* dataPtr = stackalloc <#=type.Name#>[Count];
+ for (int g = 0; g < Count; g++)
+ {
+ dataPtr[g] = unchecked((<#=type.Name#>)Math.Sqrt((<#=type.Name#>)(object)value[g]));
+ }
+ return new Vector<T>(dataPtr);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ else
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+<#
+ for (int g = 0; g < GetNumFields(type, totalSize); g++)
+ {
+#>
+ value.<#=GetRegisterFieldName(type, g)#> = (<#=type.Name#>)Math.Sqrt(value.<#=GetRegisterFieldName(type, g)#>);
+<#
+ }
+#>
+ return value;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ }
+ #endregion Internal Math Methods
+
+ #region Helper Methods
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static bool ScalarEquals(T left, T right)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return (<#=type.Name#>)(object)left == (<#=type.Name#>)(object)right;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static bool ScalarLessThan(T left, T right)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return (<#=type.Name#>)(object)left < (<#=type.Name#>)(object)right;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static bool ScalarGreaterThan(T left, T right)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return (<#=type.Name#>)(object)left > (<#=type.Name#>)(object)right;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T ScalarAdd(T left, T right)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return (T)(object)unchecked((<#=type.Name#>)((<#=type.Name#>)(object)left + (<#=type.Name#>)(object)right));
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T ScalarSubtract(T left, T right)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return (T)(object)(<#=type.Name#>)((<#=type.Name#>)(object)left - (<#=type.Name#>)(object)right);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T ScalarMultiply(T left, T right)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return (T)(object)unchecked((<#=type.Name#>)((<#=type.Name#>)(object)left * (<#=type.Name#>)(object)right));
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T ScalarDivide(T left, T right)
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return (T)(object)(<#=type.Name#>)((<#=type.Name#>)(object)left / (<#=type.Name#>)(object)right);
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T GetOneValue()
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ <#=type.Name#> value = 1;
+ return (T)(object)value;
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static T GetAllBitsSetValue()
+ {
+<# foreach (Type type in supportedTypes)
+ {
+#>
+ <#=GenerateIfStatementHeader(type)#>
+ {
+ return (T)(object)ConstantHelper.Get<#=type.Name#>WithAllBitsSet();
+ }
+<#
+ }
+#>
+ else
+ {
+ throw new NotSupportedException(SR.Arg_TypeNotSupported);
+ }
+ }
+ #endregion
+ }
+
+ [Intrinsic]
+ public static partial class Vector
+ {
+ #region Widen/Narrow
+<# foreach (Type type in WidenableTypes)
+ {
+ Type widenTarget = GetWidenTarget(type);
+#>
+ /// <summary>
+ /// Widens a Vector{<#=type.Name#>} into two Vector{<#=widenTarget.Name#>}'s.
+ /// <param name="source">The source vector whose elements are widened into the outputs.</param>
+ /// <param name="low">The first output vector, whose elements will contain the widened elements from lower indices in the source vector.</param>
+ /// <param name="high">The second output vector, whose elements will contain the widened elements from higher indices in the source vector.</param>
+ /// </summary>
+<#
+ if (nonClsCompliantTypes.Contains(type) || nonClsCompliantTypes.Contains(widenTarget))
+ {
+#>
+ [CLSCompliant(false)]
+<#
+ }
+#>
+ [Intrinsic]
+ public static unsafe void Widen(Vector<<#=type.Name#>> source, out Vector<<#=widenTarget.Name#>> low, out Vector<<#=widenTarget.Name#>> high)
+ {
+ int elements = Vector<<#=type.Name#>>.Count;
+ <#=widenTarget.Name#>* lowPtr = stackalloc <#=widenTarget.Name#>[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ lowPtr[i] = (<#=widenTarget.Name#>)source[i];
+ }
+ <#=widenTarget.Name#>* highPtr = stackalloc <#=widenTarget.Name#>[elements / 2];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ highPtr[i] = (<#=widenTarget.Name#>)source[i + (elements / 2)];
+ }
+
+ low = new Vector<<#=widenTarget.Name#>>(lowPtr);
+ high = new Vector<<#=widenTarget.Name#>>(highPtr);
+ }
+
+<#
+ }
+#>
+<# foreach (Type narrowSource in NarrowableTypes)
+ {
+ Type narrowTarget = GetNarrowTarget(narrowSource);
+#>
+ /// <summary>
+ /// Narrows two Vector{<#=narrowSource.Name#>}'s into one Vector{<#=narrowTarget.Name#>}.
+ /// <param name="low">The first source vector, whose elements become the lower-index elements of the return value.</param>
+ /// <param name="high">The second source vector, whose elements become the higher-index elements of the return value.</param>
+ /// <returns>A Vector{<#=narrowTarget.Name#>} containing elements narrowed from the source vectors.</returns>
+ /// </summary>
+<#
+ if (nonClsCompliantTypes.Contains(narrowSource) || nonClsCompliantTypes.Contains(narrowTarget))
+ {
+#>
+ [CLSCompliant(false)]
+<#
+ }
+#>
+ [Intrinsic]
+ public static unsafe Vector<<#=narrowTarget.Name#>> Narrow(Vector<<#=narrowSource.Name#>> low, Vector<<#=narrowSource.Name#>> high)
+ {
+ unchecked
+ {
+ int elements = Vector<<#=narrowTarget.Name#>>.Count;
+ <#=narrowTarget.Name#>* retPtr = stackalloc <#=narrowTarget.Name#>[elements];
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i] = (<#=narrowTarget.Name#>)low[i];
+ }
+ for (int i = 0; i < elements / 2; i++)
+ {
+ retPtr[i + (elements / 2)] = (<#=narrowTarget.Name#>)high[i];
+ }
+
+ return new Vector<<#=narrowTarget.Name#>>(retPtr);
+ }
+ }
+
+<#
+ }
+#>
+ #endregion Widen/Narrow
+
+ #region Same-Size Conversion
+<# foreach (var pair in SameSizeConversionPairs)
+ {
+#>
+ /// <summary>
+ /// Converts a Vector{<#=pair.Key.Name#>} to a Vector{<#=pair.Value.Name#>}.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The converted vector.</returns>
+<#
+ if (nonClsCompliantTypes.Contains(pair.Key) || nonClsCompliantTypes.Contains(pair.Value))
+ {
+#>
+ [CLSCompliant(false)]
+<#
+ }
+#>
+ [Intrinsic]
+ public static unsafe Vector<<#=pair.Value.Name#>> ConvertTo<#=pair.Value.Name#>(Vector<<#=pair.Key.Name#>> value)
+ {
+ unchecked
+ {
+ int elements = Vector<<#=pair.Value.Name#>>.Count;
+ <#=pair.Value.Name#>* retPtr = stackalloc <#=pair.Value.Name#>[elements];
+ for (int i = 0; i < elements; i++)
+ {
+ retPtr[i] = (<#=pair.Value.Name#>)value[i];
+ }
+
+ return new Vector<<#=pair.Value.Name#>>(retPtr);
+ }
+ }
+
+<# } #>
+ #endregion Same-Size Conversion
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Numerics/Vector_Operations.cs b/src/System.Private.CoreLib/shared/System/Numerics/Vector_Operations.cs
new file mode 100644
index 0000000000..b69b058be9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Numerics/Vector_Operations.cs
@@ -0,0 +1,865 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Numerics
+{
+ /// <summary>
+ /// Contains various methods useful for creating, manipulating, combining, and converting generic vectors with one another.
+ /// </summary>
+ public static partial class Vector
+ {
+ // JIT is not looking at the Vector class methods
+ // all methods here should be inlined and they must be implemented in terms of Vector<T> intrinsics
+ #region Select Methods
+ /// <summary>
+ /// Creates a new vector with elements selected between the two given source vectors, and based on a mask vector.
+ /// </summary>
+ /// <param name="condition">The integral mask vector used to drive selection.</param>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The new vector with elements selected based on the mask.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<Single> ConditionalSelect(Vector<int> condition, Vector<Single> left, Vector<Single> right)
+ {
+ return (Vector<Single>)Vector<Single>.ConditionalSelect((Vector<Single>)condition, left, right);
+ }
+
+ /// <summary>
+ /// Creates a new vector with elements selected between the two given source vectors, and based on a mask vector.
+ /// </summary>
+ /// <param name="condition">The integral mask vector used to drive selection.</param>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The new vector with elements selected based on the mask.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<double> ConditionalSelect(Vector<long> condition, Vector<double> left, Vector<double> right)
+ {
+ return (Vector<double>)Vector<double>.ConditionalSelect((Vector<double>)condition, left, right);
+ }
+
+ /// <summary>
+ /// Creates a new vector with elements selected between the two given source vectors, and based on a mask vector.
+ /// </summary>
+ /// <param name="condition">The mask vector used to drive selection.</param>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The new vector with elements selected based on the mask.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> ConditionalSelect<T>(Vector<T> condition, Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.ConditionalSelect(condition, left, right);
+ }
+ #endregion Select Methods
+
+ #region Comparison methods
+ #region Equals methods
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left and right were equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Equals<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.Equals(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether elements in the left and right floating point vectors were equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> Equals(Vector<Single> left, Vector<Single> right)
+ {
+ return (Vector<int>)Vector<Single>.Equals(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left and right were equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> Equals(Vector<int> left, Vector<int> right)
+ {
+ return Vector<int>.Equals(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether elements in the left and right floating point vectors were equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> Equals(Vector<double> left, Vector<double> right)
+ {
+ return (Vector<long>)Vector<double>.Equals(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left and right were equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> Equals(Vector<long> left, Vector<long> right)
+ {
+ return Vector<long>.Equals(left, right);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether each pair of elements in the given vectors are equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The first vector to compare.</param>
+ /// <returns>True if all elements are equal; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool EqualsAll<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left == right;
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether any single pair of elements in the given vectors are equal.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if any element pairs are equal; False if no element pairs are equal.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool EqualsAny<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return !Vector<T>.Equals(left, right).Equals(Vector<T>.Zero);
+ }
+ #endregion Equals methods
+
+ #region Lessthan Methods
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were less than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> LessThan<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.LessThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether the elements in left were less than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant integral vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> LessThan(Vector<Single> left, Vector<Single> right)
+ {
+ return (Vector<int>)Vector<Single>.LessThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were less than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> LessThan(Vector<int> left, Vector<int> right)
+ {
+ return Vector<int>.LessThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether the elements in left were less than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant integral vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> LessThan(Vector<double> left, Vector<double> right)
+ {
+ return (Vector<long>)Vector<double>.LessThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were less than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> LessThan(Vector<long> left, Vector<long> right)
+ {
+ return Vector<long>.LessThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether all of the elements in left are less than their corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if all elements in left are less than their corresponding elements in right; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool LessThanAll<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ Vector<int> cond = (Vector<int>)Vector<T>.LessThan(left, right);
+ return cond.Equals(Vector<int>.AllOnes);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether any element in left is less than its corresponding element in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if any elements in left are less than their corresponding elements in right; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool LessThanAny<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ Vector<int> cond = (Vector<int>)Vector<T>.LessThan(left, right);
+ return !cond.Equals(Vector<int>.Zero);
+ }
+ #endregion LessthanMethods
+
+ #region Lessthanorequal methods
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were less than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> LessThanOrEqual<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.LessThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether the elements in left were less than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant integral vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> LessThanOrEqual(Vector<Single> left, Vector<Single> right)
+ {
+ return (Vector<int>)Vector<Single>.LessThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were less than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> LessThanOrEqual(Vector<int> left, Vector<int> right)
+ {
+ return Vector<int>.LessThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were less than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> LessThanOrEqual(Vector<long> left, Vector<long> right)
+ {
+ return Vector<long>.LessThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether the elements in left were less than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant integral vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> LessThanOrEqual(Vector<double> left, Vector<double> right)
+ {
+ return (Vector<long>)Vector<double>.LessThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether all elements in left are less than or equal to their corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if all elements in left are less than or equal to their corresponding elements in right; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool LessThanOrEqualAll<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ Vector<int> cond = (Vector<int>)Vector<T>.LessThanOrEqual(left, right);
+ return cond.Equals(Vector<int>.AllOnes);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether any element in left is less than or equal to its corresponding element in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if any elements in left are less than their corresponding elements in right; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool LessThanOrEqualAny<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ Vector<int> cond = (Vector<int>)Vector<T>.LessThanOrEqual(left, right);
+ return !cond.Equals(Vector<int>.Zero);
+ }
+ #endregion Lessthanorequal methods
+
+ #region Greaterthan methods
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were greater than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> GreaterThan<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.GreaterThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether the elements in left were greater than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant integral vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> GreaterThan(Vector<Single> left, Vector<Single> right)
+ {
+ return (Vector<int>)Vector<Single>.GreaterThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were greater than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> GreaterThan(Vector<int> left, Vector<int> right)
+ {
+ return Vector<int>.GreaterThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether the elements in left were greater than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant integral vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> GreaterThan(Vector<double> left, Vector<double> right)
+ {
+ return (Vector<long>)Vector<double>.GreaterThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were greater than their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> GreaterThan(Vector<long> left, Vector<long> right)
+ {
+ return Vector<long>.GreaterThan(left, right);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether all elements in left are greater than the corresponding elements in right.
+ /// elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if all elements in left are greater than their corresponding elements in right; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool GreaterThanAll<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ Vector<int> cond = (Vector<int>)Vector<T>.GreaterThan(left, right);
+ return cond.Equals(Vector<int>.AllOnes);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether any element in left is greater than its corresponding element in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if any elements in left are greater than their corresponding elements in right; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool GreaterThanAny<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ Vector<int> cond = (Vector<int>)Vector<T>.GreaterThan(left, right);
+ return !cond.Equals(Vector<int>.Zero);
+ }
+ #endregion Greaterthan methods
+
+ #region Greaterthanorequal methods
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were greater than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> GreaterThanOrEqual<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.GreaterThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether the elements in left were greater than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant integral vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> GreaterThanOrEqual(Vector<Single> left, Vector<Single> right)
+ {
+ return (Vector<int>)Vector<Single>.GreaterThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were greater than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<int> GreaterThanOrEqual(Vector<int> left, Vector<int> right)
+ {
+ return Vector<int>.GreaterThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements signal whether the elements in left were greater than or equal to their
+ /// corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> GreaterThanOrEqual(Vector<long> left, Vector<long> right)
+ {
+ return Vector<long>.GreaterThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns an integral vector whose elements signal whether the elements in left were greater than or equal to
+ /// their corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>The resultant integral vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<long> GreaterThanOrEqual(Vector<double> left, Vector<double> right)
+ {
+ return (Vector<long>)Vector<double>.GreaterThanOrEqual(left, right);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether all of the elements in left are greater than or equal to
+ /// their corresponding elements in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if all elements in left are greater than or equal to their corresponding elements in right; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool GreaterThanOrEqualAll<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ Vector<int> cond = (Vector<int>)Vector<T>.GreaterThanOrEqual(left, right);
+ return cond.Equals(Vector<int>.AllOnes);
+ }
+
+ /// <summary>
+ /// Returns a boolean indicating whether any element in left is greater than or equal to its corresponding element in right.
+ /// </summary>
+ /// <param name="left">The first vector to compare.</param>
+ /// <param name="right">The second vector to compare.</param>
+ /// <returns>True if any elements in left are greater than or equal to their corresponding elements in right; False otherwise.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool GreaterThanOrEqualAny<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ Vector<int> cond = (Vector<int>)Vector<T>.GreaterThanOrEqual(left, right);
+ return !cond.Equals(Vector<int>.Zero);
+ }
+ #endregion Greaterthanorequal methods
+ #endregion Comparison methods
+
+ #region Vector Math Methods
+ // Every operation must either be a JIT intrinsic or implemented over a JIT intrinsic
+ // as a thin wrapper
+ // Operations implemented over a JIT intrinsic should be inlined
+ // Methods that do not have a <T> type parameter are recognized as intrinsics
+ /// <summary>
+ /// Returns whether or not vector operations are subject to hardware acceleration through JIT intrinsic support.
+ /// </summary>
+ public static bool IsHardwareAccelerated
+ {
+ [Intrinsic]
+ get
+ {
+ return false;
+ }
+ }
+
+ // Vector<T>
+ // Basic Math
+ // All Math operations for Vector<T> are aggressively inlined here
+
+ /// <summary>
+ /// Returns a new vector whose elements are the absolute values of the given vector's elements.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The absolute value vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Abs<T>(Vector<T> value) where T : struct
+ {
+ return Vector<T>.Abs(value);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements are the minimum of each pair of elements in the two given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The minimum vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Min<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.Min(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements are the maximum of each pair of elements in the two given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The maximum vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Max<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.Max(left, right);
+ }
+
+ // Specialized vector operations
+
+ /// <summary>
+ /// Returns the dot product of two vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The dot product.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static T Dot<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return Vector<T>.DotProduct(left, right);
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements are the square roots of the given vector's elements.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The square root vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> SquareRoot<T>(Vector<T> value) where T : struct
+ {
+ return Vector<T>.SquareRoot(value);
+ }
+ #endregion Vector Math Methods
+
+ #region Named Arithmetic Operators
+ /// <summary>
+ /// Creates a new vector whose values are the sum of each pair of elements from the two given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The summed vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Add<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left + right;
+ }
+
+ /// <summary>
+ /// Creates a new vector whose values are the difference between each pairs of elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The difference vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Subtract<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left - right;
+ }
+
+ /// <summary>
+ /// Creates a new vector whose values are the product of each pair of elements from the two given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The summed vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Multiply<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left * right;
+ }
+
+ /// <summary>
+ /// Returns a new vector whose values are the values of the given vector each multiplied by a scalar value.
+ /// </summary>
+ /// <param name="left">The source vector.</param>
+ /// <param name="right">The scalar factor.</param>
+ /// <returns>The scaled vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Multiply<T>(Vector<T> left, T right) where T : struct
+ {
+ return left * right;
+ }
+
+ /// <summary>
+ /// Returns a new vector whose values are the values of the given vector each multiplied by a scalar value.
+ /// </summary>
+ /// <param name="left">The scalar factor.</param>
+ /// <param name="right">The source vector.</param>
+ /// <returns>The scaled vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Multiply<T>(T left, Vector<T> right) where T : struct
+ {
+ return left * right;
+ }
+
+ /// <summary>
+ /// Returns a new vector whose values are the result of dividing the first vector's elements
+ /// by the corresponding elements in the second vector.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The divided vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Divide<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left / right;
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements are the given vector's elements negated.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The negated vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Negate<T>(Vector<T> value) where T : struct
+ {
+ return -value;
+ }
+ #endregion Named Arithmetic Operators
+
+ #region Named Bitwise Operators
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-and operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> BitwiseAnd<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left & right;
+ }
+
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-or operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> BitwiseOr<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left | right;
+ }
+
+ /// <summary>
+ /// Returns a new vector whose elements are obtained by taking the one's complement of the given vector's elements.
+ /// </summary>
+ /// <param name="value">The source vector.</param>
+ /// <returns>The one's complement vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> OnesComplement<T>(Vector<T> value) where T : struct
+ {
+ return ~value;
+ }
+
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-exclusive-or operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> Xor<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left ^ right;
+ }
+
+ /// <summary>
+ /// Returns a new vector by performing a bitwise-and-not operation on each of the elements in the given vectors.
+ /// </summary>
+ /// <param name="left">The first source vector.</param>
+ /// <param name="right">The second source vector.</param>
+ /// <returns>The resultant vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<T> AndNot<T>(Vector<T> left, Vector<T> right) where T : struct
+ {
+ return left & ~right;
+ }
+ #endregion Named Bitwise Operators
+
+ #region Conversion Methods
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of unsigned bytes.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<Byte> AsVectorByte<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<Byte>)value;
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of signed bytes.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [CLSCompliant(false)]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<SByte> AsVectorSByte<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<SByte>)value;
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of 16-bit integers.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [CLSCompliant(false)]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<UInt16> AsVectorUInt16<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<UInt16>)value;
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of signed 16-bit integers.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<Int16> AsVectorInt16<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<Int16>)value;
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of unsigned 32-bit integers.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [CLSCompliant(false)]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<UInt32> AsVectorUInt32<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<UInt32>)value;
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of signed 32-bit integers.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<Int32> AsVectorInt32<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<Int32>)value;
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of unsigned 64-bit integers.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [CLSCompliant(false)]
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<UInt64> AsVectorUInt64<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<UInt64>)value;
+ }
+
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of signed 64-bit integers.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<Int64> AsVectorInt64<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<Int64>)value;
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of 32-bit floating point numbers.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<Single> AsVectorSingle<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<Single>)value;
+ }
+
+ /// <summary>
+ /// Reinterprets the bits of the given vector into those of a vector of 64-bit floating point numbers.
+ /// </summary>
+ /// <param name="value">The source vector</param>
+ /// <returns>The reinterpreted vector.</returns>
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector<Double> AsVectorDouble<T>(Vector<T> value) where T : struct
+ {
+ return (Vector<Double>)value;
+ }
+ #endregion Conversion Methods
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ObjectDisposedException.cs b/src/System.Private.CoreLib/shared/System/ObjectDisposedException.cs
new file mode 100644
index 0000000000..3daed13bb6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ObjectDisposedException.cs
@@ -0,0 +1,83 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ /// <devdoc>
+ /// <para> The exception that is thrown when accessing an object that was
+ /// disposed.</para>
+ /// </devdoc>
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ObjectDisposedException : InvalidOperationException
+ {
+ private String _objectName;
+
+ // This constructor should only be called by the EE (COMPlusThrow)
+ private ObjectDisposedException() :
+ this(null, SR.ObjectDisposed_Generic)
+ {
+ }
+
+ public ObjectDisposedException(String objectName) :
+ this(objectName, SR.ObjectDisposed_Generic)
+ {
+ }
+
+ public ObjectDisposedException(String objectName, String message) : base(message)
+ {
+ HResult = HResults.COR_E_OBJECTDISPOSED;
+ _objectName = objectName;
+ }
+
+ public ObjectDisposedException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_OBJECTDISPOSED;
+ }
+
+ protected ObjectDisposedException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ _objectName = info.GetString("ObjectName");
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("ObjectName", ObjectName, typeof(string));
+ }
+
+ /// <devdoc>
+ /// <para>Gets the text for the message for this exception.</para>
+ /// </devdoc>
+ public override String Message
+ {
+ get
+ {
+ String name = ObjectName;
+ if (name == null || name.Length == 0)
+ return base.Message;
+
+ String objectDisposed = SR.Format(SR.ObjectDisposed_ObjectName_Name, name);
+ return base.Message + Environment.NewLine + objectDisposed;
+ }
+ }
+
+ public String ObjectName
+ {
+ get
+ {
+ if (_objectName == null)
+ {
+ return String.Empty;
+ }
+ return _objectName;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ObsoleteAttribute.cs b/src/System.Private.CoreLib/shared/System/ObsoleteAttribute.cs
new file mode 100644
index 0000000000..a63db137f8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ObsoleteAttribute.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Attribute for functions, etc that will be removed.
+**
+**
+===========================================================*/
+
+using System;
+
+namespace System
+{
+ // This attribute is attached to members that are not to be used any longer.
+ // Message is some human readable explanation of what to use
+ // Error indicates if the compiler should treat usage of such a method as an
+ // error. (this would be used if the actual implementation of the obsolete
+ // method's implementation had changed).
+ //
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum |
+ AttributeTargets.Interface | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Delegate
+ , Inherited = false)]
+ public sealed class ObsoleteAttribute : Attribute
+ {
+ private String _message;
+ private bool _error;
+
+ public ObsoleteAttribute()
+ {
+ _message = null;
+ _error = false;
+ }
+
+ public ObsoleteAttribute(String message)
+ {
+ _message = message;
+ _error = false;
+ }
+
+ public ObsoleteAttribute(String message, bool error)
+ {
+ _message = message;
+ _error = error;
+ }
+
+ public String Message
+ {
+ get { return _message; }
+ }
+
+ public bool IsError
+ {
+ get { return _error; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/OperationCanceledException.cs b/src/System.Private.CoreLib/shared/System/OperationCanceledException.cs
new file mode 100644
index 0000000000..8a472c9ff0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/OperationCanceledException.cs
@@ -0,0 +1,74 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Exception for cancelled IO requests.
+**
+**
+===========================================================*/
+
+using System;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class OperationCanceledException : SystemException
+ {
+ [NonSerialized]
+ private CancellationToken _cancellationToken;
+
+ public CancellationToken CancellationToken
+ {
+ get { return _cancellationToken; }
+ private set { _cancellationToken = value; }
+ }
+
+ public OperationCanceledException()
+ : base(SR.OperationCanceled)
+ {
+ HResult = HResults.COR_E_OPERATIONCANCELED;
+ }
+
+ public OperationCanceledException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_OPERATIONCANCELED;
+ }
+
+ public OperationCanceledException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_OPERATIONCANCELED;
+ }
+
+
+ public OperationCanceledException(CancellationToken token)
+ : this()
+ {
+ CancellationToken = token;
+ }
+
+ public OperationCanceledException(String message, CancellationToken token)
+ : this(message)
+ {
+ CancellationToken = token;
+ }
+
+ public OperationCanceledException(String message, Exception innerException, CancellationToken token)
+ : this(message, innerException)
+ {
+ CancellationToken = token;
+ }
+
+ protected OperationCanceledException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/OverflowException.cs b/src/System.Private.CoreLib/shared/System/OverflowException.cs
new file mode 100644
index 0000000000..e28dcb87ed
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/OverflowException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for Arithmetic Overflows.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class OverflowException : ArithmeticException
+ {
+ public OverflowException()
+ : base(SR.Arg_OverflowException)
+ {
+ HResult = HResults.COR_E_OVERFLOW;
+ }
+
+ public OverflowException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_OVERFLOW;
+ }
+
+ public OverflowException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_OVERFLOW;
+ }
+
+ protected OverflowException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ParamArrayAttribute.cs b/src/System.Private.CoreLib/shared/System/ParamArrayAttribute.cs
new file mode 100644
index 0000000000..d3c3d46d56
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ParamArrayAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Attribute for multiple parameters.
+**
+**
+=============================================================================*/
+
+namespace System
+{
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
+ public sealed class ParamArrayAttribute : Attribute
+ {
+ public ParamArrayAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ParamsArray.cs b/src/System.Private.CoreLib/shared/System/ParamsArray.cs
new file mode 100644
index 0000000000..043ee679ee
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ParamsArray.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ internal readonly struct ParamsArray
+ {
+ // Sentinel fixed-length arrays eliminate the need for a "count" field keeping this
+ // struct down to just 4 fields. These are only used for their "Length" property,
+ // that is, their elements are never set or referenced.
+ private static readonly object[] s_oneArgArray = new object[1];
+ private static readonly object[] s_twoArgArray = new object[2];
+ private static readonly object[] s_threeArgArray = new object[3];
+
+ private readonly object _arg0;
+ private readonly object _arg1;
+ private readonly object _arg2;
+
+ // After construction, the first three elements of this array will never be accessed
+ // because the indexer will retrieve those values from arg0, arg1, and arg2.
+ private readonly object[] _args;
+
+ public ParamsArray(object arg0)
+ {
+ _arg0 = arg0;
+ _arg1 = null;
+ _arg2 = null;
+
+ // Always assign this.args to make use of its "Length" property
+ _args = s_oneArgArray;
+ }
+
+ public ParamsArray(object arg0, object arg1)
+ {
+ _arg0 = arg0;
+ _arg1 = arg1;
+ _arg2 = null;
+
+ // Always assign this.args to make use of its "Length" property
+ _args = s_twoArgArray;
+ }
+
+ public ParamsArray(object arg0, object arg1, object arg2)
+ {
+ _arg0 = arg0;
+ _arg1 = arg1;
+ _arg2 = arg2;
+
+ // Always assign this.args to make use of its "Length" property
+ _args = s_threeArgArray;
+ }
+
+ public ParamsArray(object[] args)
+ {
+ int len = args.Length;
+ _arg0 = len > 0 ? args[0] : null;
+ _arg1 = len > 1 ? args[1] : null;
+ _arg2 = len > 2 ? args[2] : null;
+ _args = args;
+ }
+
+ public int Length
+ {
+ get { return _args.Length; }
+ }
+
+ public object this[int index]
+ {
+ get { return index == 0 ? _arg0 : GetAtSlow(index); }
+ }
+
+ private object GetAtSlow(int index)
+ {
+ if (index == 1)
+ return _arg1;
+ if (index == 2)
+ return _arg2;
+ return _args[index];
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ParseNumbers.cs b/src/System.Private.CoreLib/shared/System/ParseNumbers.cs
new file mode 100644
index 0000000000..dab4cb2190
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ParseNumbers.cs
@@ -0,0 +1,664 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ /// <summary>Methods for parsing numbers and strings.</summary>
+ internal static class ParseNumbers
+ {
+ internal const int LeftAlign = 0x0001;
+ internal const int RightAlign = 0x0004;
+ internal const int PrefixSpace = 0x0008;
+ internal const int PrintSign = 0x0010;
+ internal const int PrintBase = 0x0020;
+ internal const int PrintAsI1 = 0x0040;
+ internal const int PrintAsI2 = 0x0080;
+ internal const int PrintAsI4 = 0x0100;
+ internal const int TreatAsUnsigned = 0x0200;
+ internal const int TreatAsI1 = 0x0400;
+ internal const int TreatAsI2 = 0x0800;
+ internal const int IsTight = 0x1000;
+ internal const int NoSpace = 0x2000;
+ internal const int PrintRadixBase = 0x4000;
+
+ private const int MinRadix = 2;
+ private const int MaxRadix = 36;
+
+ public static unsafe long StringToLong(ReadOnlySpan<char> s, int radix, int flags)
+ {
+ int pos = 0;
+ return StringToLong(s, radix, flags, ref pos);
+ }
+
+ public static long StringToLong(ReadOnlySpan<char> s, int radix, int flags, ref int currPos)
+ {
+ int i = currPos;
+
+ // Do some radix checking.
+ // A radix of -1 says to use whatever base is spec'd on the number.
+ // Parse in Base10 until we figure out what the base actually is.
+ int r = (-1 == radix) ? 10 : radix;
+
+ if (r != 2 && r != 10 && r != 8 && r != 16)
+ throw new ArgumentException(SR.Arg_InvalidBase, nameof(radix));
+
+ int length = s.Length;
+
+ if (i < 0 || i >= length)
+ throw new ArgumentOutOfRangeException(SR.ArgumentOutOfRange_Index);
+
+ // Get rid of the whitespace and then check that we've still got some digits to parse.
+ if (((flags & IsTight) == 0) && ((flags & NoSpace) == 0))
+ {
+ EatWhiteSpace(s, ref i);
+ if (i == length)
+ throw new FormatException(SR.Format_EmptyInputString);
+ }
+
+ // Check for a sign
+ int sign = 1;
+ if (s[i] == '-')
+ {
+ if (r != 10)
+ throw new ArgumentException(SR.Arg_CannotHaveNegativeValue);
+
+ if ((flags & TreatAsUnsigned) != 0)
+ throw new OverflowException(SR.Overflow_NegativeUnsigned);
+
+ sign = -1;
+ i++;
+ }
+ else if (s[i] == '+')
+ {
+ i++;
+ }
+
+ if ((radix == -1 || radix == 16) && (i + 1 < length) && s[i] == '0')
+ {
+ if (s[i + 1] == 'x' || s[i + 1] == 'X')
+ {
+ r = 16;
+ i += 2;
+ }
+ }
+
+ int grabNumbersStart = i;
+ long result = GrabLongs(r, s, ref i, (flags & TreatAsUnsigned) != 0);
+
+ // Check if they passed us a string with no parsable digits.
+ if (i == grabNumbersStart)
+ throw new FormatException(SR.Format_NoParsibleDigits);
+
+ if ((flags & IsTight) != 0)
+ {
+ //If we've got effluvia left at the end of the string, complain.
+ if (i < length)
+ throw new FormatException(SR.Format_ExtraJunkAtEnd);
+ }
+
+ // Put the current index back into the correct place.
+ currPos = i;
+
+ // Return the value properly signed.
+ if ((ulong)result == 0x8000000000000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0))
+ throw new OverflowException(SR.Overflow_Int64);
+
+ if (r == 10)
+ {
+ result *= sign;
+ }
+
+ return result;
+ }
+
+ public static int StringToInt(ReadOnlySpan<char> s, int radix, int flags)
+ {
+ int pos = 0;
+ return StringToInt(s, radix, flags, ref pos);
+ }
+
+ public static int StringToInt(ReadOnlySpan<char> s, int radix, int flags, ref int currPos)
+ {
+ // They're requied to tell me where to start parsing.
+ int i = currPos;
+
+ // Do some radix checking.
+ // A radix of -1 says to use whatever base is spec'd on the number.
+ // Parse in Base10 until we figure out what the base actually is.
+ int r = (-1 == radix) ? 10 : radix;
+
+ if (r != 2 && r != 10 && r != 8 && r != 16)
+ throw new ArgumentException(SR.Arg_InvalidBase, nameof(radix));
+
+ int length = s.Length;
+
+ if (i < 0 || i >= length)
+ throw new ArgumentOutOfRangeException(SR.ArgumentOutOfRange_Index);
+
+ // Get rid of the whitespace and then check that we've still got some digits to parse.
+ if (((flags & IsTight) == 0) && ((flags & NoSpace) == 0))
+ {
+ EatWhiteSpace(s, ref i);
+ if (i == length)
+ throw new FormatException(SR.Format_EmptyInputString);
+ }
+
+ // Check for a sign
+ int sign = 1;
+ if (s[i] == '-')
+ {
+ if (r != 10)
+ throw new ArgumentException(SR.Arg_CannotHaveNegativeValue);
+
+ if ((flags & TreatAsUnsigned) != 0)
+ throw new OverflowException(SR.Overflow_NegativeUnsigned);
+
+ sign = -1;
+ i++;
+ }
+ else if (s[i] == '+')
+ {
+ i++;
+ }
+
+ // Consume the 0x if we're in an unknown base or in base-16.
+ if ((radix == -1 || radix == 16) && (i + 1 < length) && s[i] == '0')
+ {
+ if (s[i + 1] == 'x' || s[i + 1] == 'X')
+ {
+ r = 16;
+ i += 2;
+ }
+ }
+
+ int grabNumbersStart = i;
+ int result = GrabInts(r, s, ref i, ((flags & TreatAsUnsigned) != 0));
+
+ // Check if they passed us a string with no parsable digits.
+ if (i == grabNumbersStart)
+ throw new FormatException(SR.Format_NoParsibleDigits);
+
+ if ((flags & IsTight) != 0)
+ {
+ // If we've got effluvia left at the end of the string, complain.
+ if (i < length)
+ throw new FormatException(SR.Format_ExtraJunkAtEnd);
+ }
+
+ // Put the current index back into the correct place.
+ currPos = i;
+
+ // Return the value properly signed.
+ if ((flags & TreatAsI1) != 0)
+ {
+ if ((uint)result > 0xFF)
+ throw new OverflowException(SR.Overflow_SByte);
+ }
+ else if ((flags & TreatAsI2) != 0)
+ {
+ if ((uint)result > 0xFFFF)
+ throw new OverflowException(SR.Overflow_Int16);
+ }
+ else if ((uint)result == 0x80000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0))
+ {
+ throw new OverflowException(SR.Overflow_Int32);
+ }
+
+ if (r == 10)
+ {
+ result *= sign;
+ }
+
+ return result;
+ }
+
+ public static string IntToString(int n, int radix, int width, char paddingChar, int flags)
+ {
+ Span<char> buffer = stackalloc char[66]; // Longest possible string length for an integer in binary notation with prefix
+
+ if (radix < MinRadix || radix > MaxRadix)
+ throw new ArgumentException(SR.Arg_InvalidBase, nameof(radix));
+
+ // If the number is negative, make it positive and remember the sign.
+ // If the number is MIN_VALUE, this will still be negative, so we'll have to
+ // special case this later.
+ bool isNegative = false;
+ uint l;
+ if (n < 0)
+ {
+ isNegative = true;
+
+ // For base 10, write out -num, but other bases write out the
+ // 2's complement bit pattern
+ l = (10 == radix) ? (uint)-n : (uint)n;
+ }
+ else
+ {
+ l = (uint)n;
+ }
+
+ // The conversion to a uint will sign extend the number. In order to ensure
+ // that we only get as many bits as we expect, we chop the number.
+ if ((flags & PrintAsI1) != 0)
+ {
+ l &= 0xFF;
+ }
+ else if ((flags & PrintAsI2) != 0)
+ {
+ l &= 0xFFFF;
+ }
+
+ // Special case the 0.
+ int index;
+ if (0 == l)
+ {
+ buffer[0] = '0';
+ index = 1;
+ }
+ else
+ {
+ index = 0;
+ for (int i = 0; i < buffer.Length; i++) // for(...;i<buffer.Length;...) loop instead of do{...}while(l!=0) to help JIT eliminate span bounds checks
+ {
+ uint div = l / (uint)radix; // TODO https://github.com/dotnet/coreclr/issues/3439
+ uint charVal = l - (div * (uint)radix);
+ l = div;
+
+ buffer[i] = (charVal < 10) ?
+ (char)(charVal + '0') :
+ (char)(charVal + 'a' - 10);
+
+ if (l == 0)
+ {
+ index = i + 1;
+ break;
+ }
+ }
+
+ Debug.Assert(l == 0, $"Expected {l} == 0");
+ }
+
+ // If they want the base, append that to the string (in reverse order)
+ if (radix != 10 && ((flags & PrintBase) != 0))
+ {
+ if (16 == radix)
+ {
+ buffer[index++] = 'x';
+ buffer[index++] = '0';
+ }
+ else if (8 == radix)
+ {
+ buffer[index++] = '0';
+ }
+ }
+
+ if (10 == radix)
+ {
+ // If it was negative, append the sign, else if they requested, add the '+'.
+ // If they requested a leading space, put it on.
+ if (isNegative)
+ {
+ buffer[index++] = '-';
+ }
+ else if ((flags & PrintSign) != 0)
+ {
+ buffer[index++] = '+';
+ }
+ else if ((flags & PrefixSpace) != 0)
+ {
+ buffer[index++] = ' ';
+ }
+ }
+
+ // Figure out the size of and allocate the resulting string
+ string result = string.FastAllocateString(Math.Max(width, index));
+ unsafe
+ {
+ // Put the characters into the string in reverse order.
+ // Fill the remaining space, if there is any, with the correct padding character.
+ fixed (char* resultPtr = result)
+ {
+ char* p = resultPtr;
+ int padding = result.Length - index;
+
+ if ((flags & LeftAlign) != 0)
+ {
+ for (int i = 0; i < padding; i++)
+ {
+ *p++ = paddingChar;
+ }
+
+ for (int i = 0; i < index; i++)
+ {
+ *p++ = buffer[index - i - 1];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < index; i++)
+ {
+ *p++ = buffer[index - i - 1];
+ }
+
+ for (int i = 0; i < padding; i++)
+ {
+ *p++ = paddingChar;
+ }
+ }
+
+ Debug.Assert((p - resultPtr) == result.Length, $"Expected {p - resultPtr} == {result.Length}");
+ }
+ }
+ return result;
+ }
+
+ public static string LongToString(long n, int radix, int width, char paddingChar, int flags)
+ {
+ Span<char> buffer = stackalloc char[67]; // Longest possible string length for an integer in binary notation with prefix
+
+ if (radix < MinRadix || radix > MaxRadix)
+ throw new ArgumentException(SR.Arg_InvalidBase, nameof(radix));
+
+ //If the number is negative, make it positive and remember the sign.
+ ulong ul;
+ bool isNegative = false;
+ if (n < 0)
+ {
+ isNegative = true;
+
+ // For base 10, write out -num, but other bases write out the
+ // 2's complement bit pattern
+ ul = (10 == radix) ? (ulong)(-n) : (ulong)n;
+ }
+ else
+ {
+ ul = (ulong)n;
+ }
+
+ if ((flags & PrintAsI1) != 0)
+ {
+ ul = ul & 0xFF;
+ }
+ else if ((flags & PrintAsI2) != 0)
+ {
+ ul = ul & 0xFFFF;
+ }
+ else if ((flags & PrintAsI4) != 0)
+ {
+ ul = ul & 0xFFFFFFFF;
+ }
+
+ //Special case the 0.
+ int index;
+ if (0 == ul)
+ {
+ buffer[0] = '0';
+ index = 1;
+ }
+ else
+ {
+ index = 0;
+ for (int i = 0; i < buffer.Length; i++) // for loop instead of do{...}while(l!=0) to help JIT eliminate span bounds checks
+ {
+ ulong div = ul / (ulong)radix; // TODO https://github.com/dotnet/coreclr/issues/3439
+ int charVal = (int)(ul - (div * (ulong)radix));
+ ul = div;
+
+ buffer[i] = (charVal < 10) ?
+ (char)(charVal + '0') :
+ (char)(charVal + 'a' - 10);
+
+ if (ul == 0)
+ {
+ index = i + 1;
+ break;
+ }
+ }
+ Debug.Assert(ul == 0, $"Expected {ul} == 0");
+ }
+
+ //If they want the base, append that to the string (in reverse order)
+ if (radix != 10 && ((flags & PrintBase) != 0))
+ {
+ if (16 == radix)
+ {
+ buffer[index++] = 'x';
+ buffer[index++] = '0';
+ }
+ else if (8 == radix)
+ {
+ buffer[index++] = '0';
+ }
+ else if ((flags & PrintRadixBase) != 0)
+ {
+ buffer[index++] = '#';
+ buffer[index++] = (char)((radix % 10) + '0');
+ buffer[index++] = (char)((radix / 10) + '0');
+ }
+ }
+
+ if (10 == radix)
+ {
+ //If it was negative, append the sign.
+ if (isNegative)
+ {
+ buffer[index++] = '-';
+ }
+
+ //else if they requested, add the '+';
+ else if ((flags & PrintSign) != 0)
+ {
+ buffer[index++] = '+';
+ }
+
+ //If they requested a leading space, put it on.
+ else if ((flags & PrefixSpace) != 0)
+ {
+ buffer[index++] = ' ';
+ }
+ }
+
+ // Figure out the size of and allocate the resulting string
+ string result = string.FastAllocateString(Math.Max(width, index));
+ unsafe
+ {
+ // Put the characters into the string in reverse order.
+ // Fill the remaining space, if there is any, with the correct padding character.
+ fixed (char* resultPtr = result)
+ {
+ char* p = resultPtr;
+ int padding = result.Length - index;
+
+ if ((flags & LeftAlign) != 0)
+ {
+ for (int i = 0; i < padding; i++)
+ {
+ *p++ = paddingChar;
+ }
+
+ for (int i = 0; i < index; i++)
+ {
+ *p++ = buffer[index - i - 1];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < index; i++)
+ {
+ *p++ = buffer[index - i - 1];
+ }
+
+ for (int i = 0; i < padding; i++)
+ {
+ *p++ = paddingChar;
+ }
+ }
+
+ Debug.Assert((p - resultPtr) == result.Length, $"Expected {p - resultPtr} == {result.Length}");
+ }
+ }
+ return result;
+ }
+
+ private static void EatWhiteSpace(ReadOnlySpan<char> s, ref int i)
+ {
+ int localIndex = i;
+ for (; localIndex < s.Length && char.IsWhiteSpace(s[localIndex]); localIndex++);
+ i = localIndex;
+ }
+
+ private static long GrabLongs(int radix, ReadOnlySpan<char> s, ref int i, bool isUnsigned)
+ {
+ ulong result = 0;
+ ulong maxVal;
+
+ // Allow all non-decimal numbers to set the sign bit.
+ if (radix == 10 && !isUnsigned)
+ {
+ maxVal = 0x7FFFFFFFFFFFFFFF / 10;
+
+ // Read all of the digits and convert to a number
+ while (i < s.Length && IsDigit(s[i], radix, out int value))
+ {
+ // Check for overflows - this is sufficient & correct.
+ if (result > maxVal || ((long)result) < 0)
+ {
+ ThrowOverflowInt64Exception();
+ }
+
+ result = result * (ulong)radix + (ulong)value;
+ i++;
+ }
+
+ if ((long)result < 0 && result != 0x8000000000000000)
+ {
+ ThrowOverflowInt64Exception();
+ }
+ }
+ else
+ {
+ Debug.Assert(radix == 2 || radix == 8 || radix == 10 || radix == 16);
+ maxVal =
+ radix == 10 ? 0xffffffffffffffff / 10 :
+ radix == 16 ? 0xffffffffffffffff / 16 :
+ radix == 8 ? 0xffffffffffffffff / 8 :
+ 0xffffffffffffffff / 2;
+
+ // Read all of the digits and convert to a number
+ while (i < s.Length && IsDigit(s[i], radix, out int value))
+ {
+ // Check for overflows - this is sufficient & correct.
+ if (result > maxVal)
+ {
+ ThrowOverflowUInt64Exception();
+ }
+
+ ulong temp = result * (ulong)radix + (ulong)value;
+
+ if (temp < result) // this means overflow as well
+ {
+ ThrowOverflowUInt64Exception();
+ }
+
+ result = temp;
+ i++;
+ }
+ }
+
+ return (long)result;
+ }
+
+ private static int GrabInts(int radix, ReadOnlySpan<char> s, ref int i, bool isUnsigned)
+ {
+ uint result = 0;
+ uint maxVal;
+
+ // Allow all non-decimal numbers to set the sign bit.
+ if (radix == 10 && !isUnsigned)
+ {
+ maxVal = (0x7FFFFFFF / 10);
+
+ // Read all of the digits and convert to a number
+ while (i < s.Length && IsDigit(s[i], radix, out int value))
+ {
+ // Check for overflows - this is sufficient & correct.
+ if (result > maxVal || (int)result < 0)
+ {
+ ThrowOverflowInt32Exception();
+ }
+ result = result * (uint)radix + (uint)value;
+ i++;
+ }
+ if ((int)result < 0 && result != 0x80000000)
+ {
+ ThrowOverflowInt32Exception();
+ }
+ }
+ else
+ {
+ Debug.Assert(radix == 2 || radix == 8 || radix == 10 || radix == 16);
+ maxVal =
+ radix == 10 ? 0xffffffff / 10 :
+ radix == 16 ? 0xffffffff / 16 :
+ radix == 8 ? 0xffffffff / 8 :
+ 0xffffffff / 2;
+
+ // Read all of the digits and convert to a number
+ while (i < s.Length && IsDigit(s[i], radix, out int value))
+ {
+ // Check for overflows - this is sufficient & correct.
+ if (result > maxVal)
+ {
+ throw new OverflowException(SR.Overflow_UInt32);
+ }
+
+ uint temp = result * (uint)radix + (uint)value;
+
+ if (temp < result) // this means overflow as well
+ {
+ ThrowOverflowUInt32Exception();
+ }
+
+ result = temp;
+ i++;
+ }
+ }
+
+ return (int)result;
+ }
+
+ private static void ThrowOverflowInt32Exception() => throw new OverflowException(SR.Overflow_Int32);
+ private static void ThrowOverflowInt64Exception() => throw new OverflowException(SR.Overflow_Int64);
+ private static void ThrowOverflowUInt32Exception() => throw new OverflowException(SR.Overflow_UInt32);
+ private static void ThrowOverflowUInt64Exception() => throw new OverflowException(SR.Overflow_UInt64);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsDigit(char c, int radix, out int result)
+ {
+ int tmp;
+ if ((uint)(c - '0') <= 9)
+ {
+ result = tmp = c - '0';
+ }
+ else if ((uint)(c - 'A') <= 'Z' - 'A')
+ {
+ result = tmp = c - 'A' + 10;
+ }
+ else if ((uint)(c - 'a') <= 'z' - 'a')
+ {
+ result = tmp = c - 'a' + 10;
+ }
+ else
+ {
+ result = -1;
+ return false;
+ }
+
+ return tmp < radix;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/PlatformNotSupportedException.cs b/src/System.Private.CoreLib/shared/System/PlatformNotSupportedException.cs
new file mode 100644
index 0000000000..5039f3f441
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/PlatformNotSupportedException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: To handle features that don't run on particular platforms
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class PlatformNotSupportedException : NotSupportedException
+ {
+ public PlatformNotSupportedException()
+ : base(SR.Arg_PlatformNotSupported)
+ {
+ HResult = HResults.COR_E_PLATFORMNOTSUPPORTED;
+ }
+
+ public PlatformNotSupportedException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_PLATFORMNOTSUPPORTED;
+ }
+
+ public PlatformNotSupportedException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_PLATFORMNOTSUPPORTED;
+ }
+
+ protected PlatformNotSupportedException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Progress.cs b/src/System.Private.CoreLib/shared/System/Progress.cs
new file mode 100644
index 0000000000..755e7719fe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Progress.cs
@@ -0,0 +1,105 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Diagnostics;
+
+namespace System
+{
+ /// <summary>
+ /// Provides an IProgress{T} that invokes callbacks for each reported progress value.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of the progress report value.</typeparam>
+ /// <remarks>
+ /// Any handler provided to the constructor or event handlers registered with
+ /// the <see cref="ProgressChanged"/> event are invoked through a
+ /// <see cref="System.Threading.SynchronizationContext"/> instance captured
+ /// when the instance is constructed. If there is no current SynchronizationContext
+ /// at the time of construction, the callbacks will be invoked on the ThreadPool.
+ /// </remarks>
+ public class Progress<T> : IProgress<T>
+ {
+ /// <summary>The synchronization context captured upon construction. This will never be null.</summary>
+ private readonly SynchronizationContext _synchronizationContext;
+ /// <summary>The handler specified to the constructor. This may be null.</summary>
+ private readonly Action<T> _handler;
+ /// <summary>A cached delegate used to post invocation to the synchronization context.</summary>
+ private readonly SendOrPostCallback _invokeHandlers;
+
+ /// <summary>Initializes the <see cref="Progress{T}"/>.</summary>
+ public Progress()
+ {
+ // Capture the current synchronization context.
+ // If there is no current context, we use a default instance targeting the ThreadPool.
+ _synchronizationContext = SynchronizationContext.Current ?? ProgressStatics.DefaultContext;
+ Debug.Assert(_synchronizationContext != null);
+ _invokeHandlers = new SendOrPostCallback(InvokeHandlers);
+ }
+
+ /// <summary>Initializes the <see cref="Progress{T}"/> with the specified callback.</summary>
+ /// <param name="handler">
+ /// A handler to invoke for each reported progress value. This handler will be invoked
+ /// in addition to any delegates registered with the <see cref="ProgressChanged"/> event.
+ /// Depending on the <see cref="System.Threading.SynchronizationContext"/> instance captured by
+ /// the <see cref="Progress"/> at construction, it's possible that this handler instance
+ /// could be invoked concurrently with itself.
+ /// </param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="handler"/> is null (Nothing in Visual Basic).</exception>
+ public Progress(Action<T> handler) : this()
+ {
+ if (handler == null) throw new ArgumentNullException(nameof(handler));
+ _handler = handler;
+ }
+
+ /// <summary>Raised for each reported progress value.</summary>
+ /// <remarks>
+ /// Handlers registered with this event will be invoked on the
+ /// <see cref="System.Threading.SynchronizationContext"/> captured when the instance was constructed.
+ /// </remarks>
+ public event EventHandler<T> ProgressChanged;
+
+ /// <summary>Reports a progress change.</summary>
+ /// <param name="value">The value of the updated progress.</param>
+ protected virtual void OnReport(T value)
+ {
+ // If there's no handler, don't bother going through the sync context.
+ // Inside the callback, we'll need to check again, in case
+ // an event handler is removed between now and then.
+ Action<T> handler = _handler;
+ EventHandler<T> changedEvent = ProgressChanged;
+ if (handler != null || changedEvent != null)
+ {
+ // Post the processing to the sync context.
+ // (If T is a value type, it will get boxed here.)
+ _synchronizationContext.Post(_invokeHandlers, value);
+ }
+ }
+
+ /// <summary>Reports a progress change.</summary>
+ /// <param name="value">The value of the updated progress.</param>
+ void IProgress<T>.Report(T value) { OnReport(value); }
+
+ /// <summary>Invokes the action and event callbacks.</summary>
+ /// <param name="state">The progress value.</param>
+ private void InvokeHandlers(object state)
+ {
+ T value = (T)state;
+
+ Action<T> handler = _handler;
+ EventHandler<T> changedEvent = ProgressChanged;
+
+ if (handler != null) handler(value);
+ if (changedEvent != null) changedEvent(this, value);
+ }
+ }
+
+ /// <summary>Holds static values for <see cref="Progress{T}"/>.</summary>
+ /// <remarks>This avoids one static instance per type T.</remarks>
+ internal static class ProgressStatics
+ {
+ /// <summary>A default synchronization context that targets the ThreadPool.</summary>
+ internal static readonly SynchronizationContext DefaultContext = new SynchronizationContext();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Random.cs b/src/System.Private.CoreLib/shared/System/Random.cs
new file mode 100644
index 0000000000..20f035e2e8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Random.cs
@@ -0,0 +1,263 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public class Random
+ {
+ //
+ // Private Constants
+ //
+ private const int MBIG = int.MaxValue;
+ private const int MSEED = 161803398;
+ private const int MZ = 0;
+
+ //
+ // Member Variables
+ //
+ private int _inext;
+ private int _inextp;
+ private int[] _seedArray = new int[56];
+
+ //
+ // Public Constants
+ //
+
+ //
+ // Native Declarations
+ //
+
+ //
+ // Constructors
+ //
+
+ /*=========================================================================================
+ **Action: Initializes a new instance of the Random class, using a default seed value
+ ===========================================================================================*/
+ public Random()
+ : this(GenerateSeed())
+ {
+ }
+
+ /*=========================================================================================
+ **Action: Initializes a new instance of the Random class, using a specified seed value
+ ===========================================================================================*/
+ public Random(int Seed)
+ {
+ int ii = 0;
+ int mj, mk;
+
+ //Initialize our Seed array.
+ int subtraction = (Seed == int.MinValue) ? int.MaxValue : Math.Abs(Seed);
+ mj = MSEED - subtraction;
+ _seedArray[55] = mj;
+ mk = 1;
+ for (int i = 1; i < 55; i++)
+ { //Apparently the range [1..55] is special (Knuth) and so we're wasting the 0'th position.
+ if ((ii += 21) >= 55) ii -= 55;
+ _seedArray[ii] = mk;
+ mk = mj - mk;
+ if (mk < 0) mk += MBIG;
+ mj = _seedArray[ii];
+ }
+ for (int k = 1; k < 5; k++)
+ {
+ for (int i = 1; i < 56; i++)
+ {
+ int n = i + 30;
+ if (n >= 55) n -= 55;
+ _seedArray[i] -= _seedArray[1 + n];
+ if (_seedArray[i] < 0) _seedArray[i] += MBIG;
+ }
+ }
+ _inext = 0;
+ _inextp = 21;
+ Seed = 1;
+ }
+
+ //
+ // Package Private Methods
+ //
+
+ /*====================================Sample====================================
+ **Action: Return a new random number [0..1) and reSeed the Seed array.
+ **Returns: A double [0..1)
+ **Arguments: None
+ **Exceptions: None
+ ==============================================================================*/
+ protected virtual double Sample()
+ {
+ //Including this division at the end gives us significantly improved
+ //random number distribution.
+ return (InternalSample() * (1.0 / MBIG));
+ }
+
+ private int InternalSample()
+ {
+ int retVal;
+ int locINext = _inext;
+ int locINextp = _inextp;
+
+ if (++locINext >= 56) locINext = 1;
+ if (++locINextp >= 56) locINextp = 1;
+
+ retVal = _seedArray[locINext] - _seedArray[locINextp];
+
+ if (retVal == MBIG) retVal--;
+ if (retVal < 0) retVal += MBIG;
+
+ _seedArray[locINext] = retVal;
+
+ _inext = locINext;
+ _inextp = locINextp;
+
+ return retVal;
+ }
+
+ [ThreadStatic]
+ private static Random t_threadRandom;
+ private static readonly Random s_globalRandom = new Random(GenerateGlobalSeed());
+
+ /*=====================================GenerateSeed=====================================
+ **Returns: An integer that can be used as seed values for consecutively
+ creating lots of instances on the same thread within a short period of time.
+ ========================================================================================*/
+ private static int GenerateSeed()
+ {
+ Random rnd = t_threadRandom;
+ if (rnd == null)
+ {
+ int seed;
+ lock (s_globalRandom)
+ {
+ seed = s_globalRandom.Next();
+ }
+ rnd = new Random(seed);
+ t_threadRandom = rnd;
+ }
+ return rnd.Next();
+ }
+
+ /*==================================GenerateGlobalSeed====================================
+ **Action: Creates a number to use as global seed.
+ **Returns: An integer that is safe to use as seed values for thread-local seed generators.
+ ==========================================================================================*/
+ private static unsafe int GenerateGlobalSeed()
+ {
+ int result;
+ Interop.GetRandomBytes((byte*)&result, sizeof(int));
+ return result;
+ }
+
+ //
+ // Public Instance Methods
+ //
+
+
+ /*=====================================Next=====================================
+ **Returns: An int [0..Int32.MaxValue)
+ **Arguments: None
+ **Exceptions: None.
+ ==============================================================================*/
+ public virtual int Next()
+ {
+ return InternalSample();
+ }
+
+ private double GetSampleForLargeRange()
+ {
+ // The distribution of double value returned by Sample
+ // is not distributed well enough for a large range.
+ // If we use Sample for a range [Int32.MinValue..Int32.MaxValue)
+ // We will end up getting even numbers only.
+
+ int result = InternalSample();
+ // Note we can't use addition here. The distribution will be bad if we do that.
+ bool negative = (InternalSample() % 2 == 0) ? true : false; // decide the sign based on second sample
+ if (negative)
+ {
+ result = -result;
+ }
+ double d = result;
+ d += (int.MaxValue - 1); // get a number in range [0 .. 2 * Int32MaxValue - 1)
+ d /= 2 * (uint)int.MaxValue - 1;
+ return d;
+ }
+
+
+ /*=====================================Next=====================================
+ **Returns: An int [minvalue..maxvalue)
+ **Arguments: minValue -- the least legal value for the Random number.
+ ** maxValue -- One greater than the greatest legal return value.
+ **Exceptions: None.
+ ==============================================================================*/
+ public virtual int Next(int minValue, int maxValue)
+ {
+ if (minValue > maxValue)
+ {
+ throw new ArgumentOutOfRangeException(nameof(minValue), SR.Format(SR.Argument_MinMaxValue, nameof(minValue), nameof(maxValue)));
+ }
+
+ long range = (long)maxValue - minValue;
+ if (range <= int.MaxValue)
+ {
+ return ((int)(Sample() * range) + minValue);
+ }
+ else
+ {
+ return (int)((long)(GetSampleForLargeRange() * range) + minValue);
+ }
+ }
+
+
+ /*=====================================Next=====================================
+ **Returns: An int [0..maxValue)
+ **Arguments: maxValue -- One more than the greatest legal return value.
+ **Exceptions: None.
+ ==============================================================================*/
+ public virtual int Next(int maxValue)
+ {
+ if (maxValue < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(maxValue), SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(maxValue)));
+ }
+ return (int)(Sample() * maxValue);
+ }
+
+
+ /*=====================================Next=====================================
+ **Returns: A double [0..1)
+ **Arguments: None
+ **Exceptions: None
+ ==============================================================================*/
+ public virtual double NextDouble()
+ {
+ return Sample();
+ }
+
+
+ /*==================================NextBytes===================================
+ **Action: Fills the byte array with random bytes [0..0x7f]. The entire array is filled.
+ **Returns:Void
+ **Arguments: buffer -- the array to be filled.
+ **Exceptions: None
+ ==============================================================================*/
+ public virtual void NextBytes(byte[] buffer)
+ {
+ if (buffer == null) throw new ArgumentNullException(nameof(buffer));
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ buffer[i] = (byte)InternalSample();
+ }
+ }
+
+ public virtual void NextBytes(Span<byte> buffer)
+ {
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ buffer[i] = (byte)Next();
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/RankException.cs b/src/System.Private.CoreLib/shared/System/RankException.cs
new file mode 100644
index 0000000000..bdd2cd51f1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/RankException.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: For methods that are passed arrays with the wrong number of
+** dimensions.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class RankException : SystemException
+ {
+ public RankException()
+ : base(SR.Arg_RankException)
+ {
+ HResult = HResults.COR_E_RANK;
+ }
+
+ public RankException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_RANK;
+ }
+
+ public RankException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_RANK;
+ }
+
+ protected RankException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs b/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs
new file mode 100644
index 0000000000..78f328a880
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs
@@ -0,0 +1,359 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute;
+using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
+#if !FEATURE_PORTABLE_SPAN
+using Internal.Runtime.CompilerServices;
+#endif // FEATURE_PORTABLE_SPAN
+
+namespace System
+{
+ /// <summary>
+ /// Represents a contiguous region of memory, similar to <see cref="ReadOnlySpan{T}"/>.
+ /// Unlike <see cref="ReadOnlySpan{T}"/>, it is not a byref-like type.
+ /// </summary>
+ [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ public readonly struct ReadOnlyMemory<T>
+ {
+ // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout,
+ // as code uses Unsafe.As to cast between them.
+
+ // The highest order bit of _index is used to discern whether _object is an array/string or an owned memory
+ // if (_index >> 31) == 1, _object is an MemoryManager<T>
+ // else, _object is a T[] or string.
+ // if (_length >> 31) == 1, _object is a pre-pinned array, so Pin() will not allocate a new GCHandle
+ // else, Pin() needs to allocate a new GCHandle to pin the object.
+ private readonly object _object;
+ private readonly int _index;
+ private readonly int _length;
+
+ internal const int RemoveFlagsBitMask = 0x7FFFFFFF;
+
+ /// <summary>
+ /// Creates a new memory over the entirety of the target array.
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyMemory(T[] array)
+ {
+ if (array == null)
+ {
+ this = default;
+ return; // returns default
+ }
+
+ _object = array;
+ _index = 0;
+ _length = array.Length;
+ }
+
+ /// <summary>
+ /// Creates a new memory over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <param name="length">The number of items in the memory.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyMemory(T[] array, int start, int length)
+ {
+ if (array == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ this = default;
+ return; // returns default
+ }
+ if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _object = array;
+ _index = start;
+ _length = length;
+ }
+
+ /// <summary>Creates a new memory over the existing object, start, and length. No validation is performed.</summary>
+ /// <param name="obj">The target object.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <param name="length">The number of items in the memory.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ReadOnlyMemory(object obj, int start, int length)
+ {
+ // No validation performed; caller must provide any necessary validation.
+ _object = obj;
+ _index = start;
+ _length = length;
+ }
+
+ /// <summary>
+ /// Defines an implicit conversion of an array to a <see cref="ReadOnlyMemory{T}"/>
+ /// </summary>
+ public static implicit operator ReadOnlyMemory<T>(T[] array) => new ReadOnlyMemory<T>(array);
+
+ /// <summary>
+ /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="ReadOnlyMemory{T}"/>
+ /// </summary>
+ public static implicit operator ReadOnlyMemory<T>(ArraySegment<T> segment) => new ReadOnlyMemory<T>(segment.Array, segment.Offset, segment.Count);
+
+ /// <summary>
+ /// Returns an empty <see cref="ReadOnlyMemory{T}"/>
+ /// </summary>
+ public static ReadOnlyMemory<T> Empty => default;
+
+ /// <summary>
+ /// The number of items in the memory.
+ /// </summary>
+ public int Length => _length & RemoveFlagsBitMask;
+
+ /// <summary>
+ /// Returns true if Length is 0.
+ /// </summary>
+ public bool IsEmpty => (_length & RemoveFlagsBitMask) == 0;
+
+ /// <summary>
+ /// For <see cref="ReadOnlyMemory{Char}"/>, returns a new instance of string that represents the characters pointed to by the memory.
+ /// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements.
+ /// </summary>
+ public override string ToString()
+ {
+ if (typeof(T) == typeof(char))
+ {
+ return (_object is string str) ? str.Substring(_index, _length & RemoveFlagsBitMask) : Span.ToString();
+ }
+ return string.Format("System.ReadOnlyMemory<{0}>[{1}]", typeof(T).Name, _length & RemoveFlagsBitMask);
+ }
+
+ /// <summary>
+ /// Forms a slice out of the given memory, beginning at 'start'.
+ /// </summary>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyMemory<T> Slice(int start)
+ {
+ // Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not.
+ int capturedLength = _length;
+ int actualLength = capturedLength & RemoveFlagsBitMask;
+ if ((uint)start > (uint)actualLength)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ }
+
+ // It is expected for (capturedLength - start) to be negative if the memory is already pre-pinned.
+ return new ReadOnlyMemory<T>(_object, _index + start, capturedLength - start);
+ }
+
+ /// <summary>
+ /// Forms a slice out of the given memory, beginning at 'start', of given length
+ /// </summary>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <param name="length">The desired length for the slice (exclusive).</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyMemory<T> Slice(int start, int length)
+ {
+ // Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not.
+ int capturedLength = _length;
+ int actualLength = _length & RemoveFlagsBitMask;
+ if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start))
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+ }
+
+ // Set the high-bit to match the this._length high bit (1 for pre-pinned, 0 for unpinned).
+ return new ReadOnlyMemory<T>(_object, _index + start, length | (capturedLength & ~RemoveFlagsBitMask));
+ }
+
+ /// <summary>
+ /// Returns a span from the memory.
+ /// </summary>
+ public ReadOnlySpan<T> Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ if (_index < 0)
+ {
+ Debug.Assert(_length >= 0);
+ Debug.Assert(_object != null);
+ return ((MemoryManager<T>)_object).GetSpan().Slice(_index & RemoveFlagsBitMask, _length);
+ }
+ else if (typeof(T) == typeof(char) && _object is string s)
+ {
+ Debug.Assert(_length >= 0);
+#if FEATURE_PORTABLE_SPAN
+ return new ReadOnlySpan<T>(Unsafe.As<Pinnable<T>>(s), MemoryExtensions.StringAdjustment, s.Length).Slice(_index, _length);
+#else
+ return new ReadOnlySpan<T>(ref Unsafe.As<char, T>(ref s.GetRawStringData()), s.Length).Slice(_index, _length);
+#endif // FEATURE_PORTABLE_SPAN
+ }
+ else if (_object != null)
+ {
+ return new ReadOnlySpan<T>((T[])_object, _index, _length & RemoveFlagsBitMask);
+ }
+ else
+ {
+ return default;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies the contents of the read-only memory into the destination. If the source
+ /// and destination overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.
+ ///
+ /// <param name="destination">The Memory to copy items into.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when the destination is shorter than the source.
+ /// </exception>
+ /// </summary>
+ public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
+
+ /// <summary>
+ /// Copies the contents of the readonly-only memory into the destination. If the source
+ /// and destination overlap, this method behaves as if the original values are in
+ /// a temporary location before the destination is overwritten.
+ ///
+ /// <returns>If the destination is shorter than the source, this method
+ /// return false and no data is written to the destination.</returns>
+ /// </summary>
+ /// <param name="destination">The span to copy items into.</param>
+ public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
+
+ /// <summary>
+ /// Creates a handle for the memory.
+ /// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
+ /// is disposed, enabling taking and using the memory's address.
+ /// <exception cref="System.ArgumentException">
+ /// An instance with nonprimitive (non-blittable) members cannot be pinned.
+ /// </exception>
+ /// </summary>
+ public unsafe MemoryHandle Pin()
+ {
+ if (_index < 0)
+ {
+ Debug.Assert(_object != null);
+ return ((MemoryManager<T>)_object).Pin((_index & RemoveFlagsBitMask));
+ }
+ else if (typeof(T) == typeof(char) && _object is string s)
+ {
+ GCHandle handle = GCHandle.Alloc(s, GCHandleType.Pinned);
+#if FEATURE_PORTABLE_SPAN
+ void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
+#else
+ void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref s.GetRawStringData()), _index);
+#endif // FEATURE_PORTABLE_SPAN
+ return new MemoryHandle(pointer, handle);
+ }
+ else if (_object is T[] array)
+ {
+ // Array is already pre-pinned
+ if (_length < 0)
+ {
+#if FEATURE_PORTABLE_SPAN
+ void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref MemoryMarshal.GetReference<T>(array)), _index);
+#else
+ void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
+#endif // FEATURE_PORTABLE_SPAN
+ return new MemoryHandle(pointer);
+ }
+ else
+ {
+ GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned);
+#if FEATURE_PORTABLE_SPAN
+ void* pointer = Unsafe.Add<T>((void*)handle.AddrOfPinnedObject(), _index);
+#else
+ void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
+#endif // FEATURE_PORTABLE_SPAN
+ return new MemoryHandle(pointer, handle);
+ }
+ }
+ return default;
+ }
+
+ /// <summary>
+ /// Copies the contents from the memory into a new array. This heap
+ /// allocates, so should generally be avoided, however it is sometimes
+ /// necessary to bridge the gap with APIs written in terms of arrays.
+ /// </summary>
+ public T[] ToArray() => Span.ToArray();
+
+ /// <summary>Determines whether the specified object is equal to the current object.</summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj)
+ {
+ if (obj is ReadOnlyMemory<T> readOnlyMemory)
+ {
+ return Equals(readOnlyMemory);
+ }
+ else if (obj is Memory<T> memory)
+ {
+ return Equals(memory);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Returns true if the memory points to the same array and has the same length. Note that
+ /// this does *not* check to see if the *contents* are equal.
+ /// </summary>
+ public bool Equals(ReadOnlyMemory<T> other)
+ {
+ return
+ _object == other._object &&
+ _index == other._index &&
+ _length == other._length;
+ }
+
+ /// <summary>Returns the hash code for this <see cref="ReadOnlyMemory{T}"/></summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode()
+ {
+ return _object != null ? CombineHashCodes(_object.GetHashCode(), _index.GetHashCode(), _length.GetHashCode()) : 0;
+ }
+
+ private static int CombineHashCodes(int left, int right)
+ {
+ return ((left << 5) + left) ^ right;
+ }
+
+ private static int CombineHashCodes(int h1, int h2, int h3)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2), h3);
+ }
+
+ /// <summary>Gets the state of the memory as individual fields.</summary>
+ /// <param name="start">The offset.</param>
+ /// <param name="length">The count.</param>
+ /// <returns>The object.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal object GetObjectStartLength(out int start, out int length)
+ {
+ start = _index;
+ length = _length;
+ return _object;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs
new file mode 100644
index 0000000000..38d62dcfa8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs
@@ -0,0 +1,277 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+using Internal.Runtime.CompilerServices;
+
+#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)'
+
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
+namespace System
+{
+ /// <summary>
+ /// ReadOnlySpan represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed
+ /// or native memory, or to memory allocated on the stack. It is type- and memory-safe.
+ /// </summary>
+ [DebuggerTypeProxy(typeof(SpanDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ [NonVersionable]
+ public readonly ref partial struct ReadOnlySpan<T>
+ {
+ /// <summary>A byref or a native ptr.</summary>
+ internal readonly ByReference<T> _pointer;
+ /// <summary>The number of elements this ReadOnlySpan contains.</summary>
+#if PROJECTN
+ [Bound]
+#endif
+ private readonly int _length;
+
+ /// <summary>
+ /// Creates a new read-only span over the entirety of the target array.
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// reference (Nothing in Visual Basic).</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan(T[] array)
+ {
+ if (array == null)
+ {
+ this = default;
+ return; // returns default
+ }
+
+ _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()));
+ _length = array.Length;
+ }
+
+ /// <summary>
+ /// Creates a new read-only span over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <param name="start">The index at which to begin the read-only span.</param>
+ /// <param name="length">The number of items in the read-only span.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// reference (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan(T[] array, int start, int length)
+ {
+ if (array == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ this = default;
+ return; // returns default
+ }
+ if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _pointer = new ByReference<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start));
+ _length = length;
+ }
+
+ /// <summary>
+ /// Creates a new read-only span over the target unmanaged buffer. Clearly this
+ /// is quite dangerous, because we are creating arbitrarily typed T's
+ /// out of a void*-typed block of memory. And the length is not checked.
+ /// But if this creation is correct, then all subsequent uses are correct.
+ /// </summary>
+ /// <param name="pointer">An unmanaged pointer to memory.</param>
+ /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when <typeparamref name="T"/> is reference type or contains pointers and hence cannot be stored in unmanaged memory.
+ /// </exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="length"/> is negative.
+ /// </exception>
+ [CLSCompliant(false)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe ReadOnlySpan(void* pointer, int length)
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+ if (length < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref *(byte*)pointer));
+ _length = length;
+ }
+
+ // Constructor for internal use only.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ReadOnlySpan(ref T ptr, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ _pointer = new ByReference<T>(ref ptr);
+ _length = length;
+ }
+
+ /// <summary>
+ /// Returns the specified element of the read-only span.
+ /// </summary>
+ /// <param name="index"></param>
+ /// <returns></returns>
+ /// <exception cref="System.IndexOutOfRangeException">
+ /// Thrown when index less than 0 or index greater than or equal to Length
+ /// </exception>
+ public ref readonly T this[int index]
+ {
+#if PROJECTN
+ [BoundsChecking]
+ get
+ {
+ return ref Unsafe.Add(ref _pointer.Value, index);
+ }
+#else
+ [Intrinsic]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [NonVersionable]
+ get
+ {
+ if ((uint)index >= (uint)_length)
+ ThrowHelper.ThrowIndexOutOfRangeException();
+ return ref Unsafe.Add(ref _pointer.Value, index);
+ }
+#endif
+ }
+
+ /// <summary>
+ /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference.
+ /// It can be used for pinning and is required to support the use of span within a fixed statement.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public unsafe ref readonly T GetPinnableReference() => ref (_length != 0) ? ref _pointer.Value : ref Unsafe.AsRef<T>(null);
+
+ /// <summary>
+ /// Copies the contents of this read-only span into destination span. If the source
+ /// and destinations overlap, this method behaves as if the original values in
+ /// a temporary location before the destination is overwritten.
+ ///
+ /// <param name="destination">The span to copy items into.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when the destination Span is shorter than the source Span.
+ /// </exception>
+ /// </summary>
+ public void CopyTo(Span<T> destination)
+ {
+ // Using "if (!TryCopyTo(...))" results in two branches: one for the length
+ // check, and one for the result of TryCopyTo. Since these checks are equivalent,
+ // we can optimize by performing the check once ourselves then calling Memmove directly.
+
+ if ((uint)_length <= (uint)destination.Length)
+ {
+ Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length);
+ }
+ else
+ {
+ ThrowHelper.ThrowArgumentException_DestinationTooShort();
+ }
+ }
+
+ /// Copies the contents of this read-only span into destination span. If the source
+ /// and destinations overlap, this method behaves as if the original values in
+ /// a temporary location before the destination is overwritten.
+ /// </summary>
+ /// <returns>If the destination span is shorter than the source span, this method
+ /// return false and no data is written to the destination.</returns>
+ /// <param name="destination">The span to copy items into.</param>
+ public bool TryCopyTo(Span<T> destination)
+ {
+ bool retVal = false;
+ if ((uint)_length <= (uint)destination.Length)
+ {
+ Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length);
+ retVal = true;
+ }
+ return retVal;
+ }
+
+ /// <summary>
+ /// Returns true if left and right point at the same memory and have the same length. Note that
+ /// this does *not* check to see if the *contents* are equal.
+ /// </summary>
+ public static bool operator ==(ReadOnlySpan<T> left, ReadOnlySpan<T> right)
+ {
+ return left._length == right._length && Unsafe.AreSame<T>(ref left._pointer.Value, ref right._pointer.Value);
+ }
+
+ /// <summary>
+ /// For <see cref="ReadOnlySpan{Char}"/>, returns a new instance of string that represents the characters pointed to by the span.
+ /// Otherwise, returns a <see cref="String"/> with the name of the type and the number of elements.
+ /// </summary>
+ public override string ToString()
+ {
+ if (typeof(T) == typeof(char))
+ {
+ unsafe
+ {
+ fixed (char* src = &Unsafe.As<T, char>(ref _pointer.Value))
+ return new string(src, 0, _length);
+ }
+ }
+ return string.Format("System.ReadOnlySpan<{0}>[{1}]", typeof(T).Name, _length);
+ }
+
+ /// <summary>
+ /// Forms a slice out of the given read-only span, beginning at 'start'.
+ /// </summary>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan<T> Slice(int start)
+ {
+ if ((uint)start > (uint)_length)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), _length - start);
+ }
+
+ /// <summary>
+ /// Forms a slice out of the given read-only span, beginning at 'start', of given length
+ /// </summary>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <param name="length">The desired length for the slice (exclusive).</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan<T> Slice(int start, int length)
+ {
+ if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
+ }
+
+ /// <summary>
+ /// Copies the contents of this read-only span into a new array. This heap
+ /// allocates, so should generally be avoided, however it is sometimes
+ /// necessary to bridge the gap with APIs written in terms of arrays.
+ /// </summary>
+ public T[] ToArray()
+ {
+ if (_length == 0)
+ return Array.Empty<T>();
+
+ var destination = new T[_length];
+ Buffer.Memmove(ref Unsafe.As<byte, T>(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length);
+ return destination;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs
new file mode 100644
index 0000000000..14b33e23f2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.cs
@@ -0,0 +1,141 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+#if !FEATURE_PORTABLE_SPAN
+using System.Runtime.Versioning;
+#endif // !FEATURE_PORTABLE_SPAN
+
+#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)'
+
+namespace System
+{
+ /// <summary>
+ /// ReadOnlySpan represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed
+ /// or native memory, or to memory allocated on the stack. It is type- and memory-safe.
+ /// </summary>
+ [DebuggerTypeProxy(typeof(SpanDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ public readonly ref partial struct ReadOnlySpan<T>
+ {
+ /// <summary>
+ /// The number of items in the read-only span.
+ /// </summary>
+ public int Length
+ {
+#if !FEATURE_PORTABLE_SPAN
+ [NonVersionable]
+#endif // !FEATURE_PORTABLE_SPAN
+ get
+ {
+ return _length;
+ }
+ }
+
+ /// <summary>
+ /// Returns true if Length is 0.
+ /// </summary>
+ public bool IsEmpty
+ {
+#if !FEATURE_PORTABLE_SPAN
+ [NonVersionable]
+#endif // !FEATURE_PORTABLE_SPAN
+ get
+ {
+ return _length == 0;
+ }
+ }
+ /// <summary>
+ /// Returns false if left and right point at the same memory and have the same length. Note that
+ /// this does *not* check to see if the *contents* are equal.
+ /// </summary>
+ public static bool operator !=(ReadOnlySpan<T> left, ReadOnlySpan<T> right) => !(left == right);
+
+ /// <summary>
+ /// This method is not supported as spans cannot be boxed. To compare two spans, use operator==.
+ /// <exception cref="System.NotSupportedException">
+ /// Always thrown by this method.
+ /// </exception>
+ /// </summary>
+ [Obsolete("Equals() on ReadOnlySpan will always throw an exception. Use == instead.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj)
+ {
+ throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan);
+ }
+
+ /// <summary>
+ /// This method is not supported as spans cannot be boxed.
+ /// <exception cref="System.NotSupportedException">
+ /// Always thrown by this method.
+ /// </exception>
+ /// </summary>
+ [Obsolete("GetHashCode() on ReadOnlySpan will always throw an exception.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode()
+ {
+ throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan);
+ }
+
+ /// <summary>
+ /// Defines an implicit conversion of an array to a <see cref="ReadOnlySpan{T}"/>
+ /// </summary>
+ public static implicit operator ReadOnlySpan<T>(T[] array) => new ReadOnlySpan<T>(array);
+
+ /// <summary>
+ /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="ReadOnlySpan{T}"/>
+ /// </summary>
+ public static implicit operator ReadOnlySpan<T>(ArraySegment<T> segment)
+ => new ReadOnlySpan<T>(segment.Array, segment.Offset, segment.Count);
+
+ /// <summary>
+ /// Returns a 0-length read-only span whose base is the null pointer.
+ /// </summary>
+ public static ReadOnlySpan<T> Empty => default(ReadOnlySpan<T>);
+
+ /// <summary>Gets an enumerator for this span.</summary>
+ public Enumerator GetEnumerator() => new Enumerator(this);
+
+ /// <summary>Enumerates the elements of a <see cref="ReadOnlySpan{T}"/>.</summary>
+ public ref struct Enumerator
+ {
+ /// <summary>The span being enumerated.</summary>
+ private readonly ReadOnlySpan<T> _span;
+ /// <summary>The next index to yield.</summary>
+ private int _index;
+
+ /// <summary>Initialize the enumerator.</summary>
+ /// <param name="span">The span to enumerate.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Enumerator(ReadOnlySpan<T> span)
+ {
+ _span = span;
+ _index = -1;
+ }
+
+ /// <summary>Advances the enumerator to the next element of the span.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ int index = _index + 1;
+ if (index < _span.Length)
+ {
+ _index = index;
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>Gets the element at the current position of the enumerator.</summary>
+ public ref readonly T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ref _span[_index];
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AmbiguousMatchException.cs b/src/System.Private.CoreLib/shared/System/Reflection/AmbiguousMatchException.cs
new file mode 100644
index 0000000000..643a127c49
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AmbiguousMatchException.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class AmbiguousMatchException : SystemException
+ {
+ public AmbiguousMatchException()
+ : base(SR.RFLCT_Ambiguous)
+ {
+ HResult = HResults.COR_E_AMBIGUOUSMATCH;
+ }
+
+ public AmbiguousMatchException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_AMBIGUOUSMATCH;
+ }
+
+ public AmbiguousMatchException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_AMBIGUOUSMATCH;
+ }
+
+ internal AmbiguousMatchException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs b/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs
new file mode 100644
index 0000000000..7280869c02
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs
@@ -0,0 +1,201 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Globalization;
+using System.Collections.Generic;
+using System.Configuration.Assemblies;
+using System.Runtime.Serialization;
+using System.Security;
+
+namespace System.Reflection
+{
+ public abstract partial class Assembly : ICustomAttributeProvider, ISerializable
+ {
+ protected Assembly() { }
+
+ public virtual IEnumerable<TypeInfo> DefinedTypes
+ {
+ get
+ {
+ Type[] types = GetTypes();
+ TypeInfo[] typeinfos = new TypeInfo[types.Length];
+ for (int i = 0; i < types.Length; i++)
+ {
+ TypeInfo typeinfo = types[i].GetTypeInfo();
+ if (typeinfo == null)
+ throw new NotSupportedException(SR.Format(SR.NotSupported_NoTypeInfo, types[i].FullName));
+
+ typeinfos[i] = typeinfo;
+ }
+ return typeinfos;
+ }
+ }
+
+ public virtual Type[] GetTypes()
+ {
+ Module[] m = GetModules(false);
+
+ int finalLength = 0;
+ Type[][] moduleTypes = new Type[m.Length][];
+
+ for (int i = 0; i < moduleTypes.Length; i++)
+ {
+ moduleTypes[i] = m[i].GetTypes();
+ finalLength += moduleTypes[i].Length;
+ }
+
+ int current = 0;
+ Type[] ret = new Type[finalLength];
+ for (int i = 0; i < moduleTypes.Length; i++)
+ {
+ int length = moduleTypes[i].Length;
+ Array.Copy(moduleTypes[i], 0, ret, current, length);
+ current += length;
+ }
+
+ return ret;
+ }
+
+ public virtual IEnumerable<Type> ExportedTypes => GetExportedTypes();
+ public virtual Type[] GetExportedTypes() { throw NotImplemented.ByDesign; }
+ public virtual Type[] GetForwardedTypes() { throw NotImplemented.ByDesign; }
+
+ public virtual string CodeBase { get { throw NotImplemented.ByDesign; } }
+ public virtual MethodInfo EntryPoint { get { throw NotImplemented.ByDesign; } }
+ public virtual string FullName { get { throw NotImplemented.ByDesign; } }
+ public virtual string ImageRuntimeVersion { get { throw NotImplemented.ByDesign; } }
+ public virtual bool IsDynamic => false;
+ public virtual string Location { get { throw NotImplemented.ByDesign; } }
+ public virtual bool ReflectionOnly { get { throw NotImplemented.ByDesign; } }
+
+ public virtual ManifestResourceInfo GetManifestResourceInfo(string resourceName) { throw NotImplemented.ByDesign; }
+ public virtual string[] GetManifestResourceNames() { throw NotImplemented.ByDesign; }
+ public virtual Stream GetManifestResourceStream(string name) { throw NotImplemented.ByDesign; }
+ public virtual Stream GetManifestResourceStream(Type type, string name) { throw NotImplemented.ByDesign; }
+
+ public bool IsFullyTrusted => true;
+
+ public virtual AssemblyName GetName() => GetName(copiedName: false);
+ public virtual AssemblyName GetName(bool copiedName) { throw NotImplemented.ByDesign; }
+
+ public virtual Type GetType(string name) => GetType(name, throwOnError: false, ignoreCase: false);
+ public virtual Type GetType(string name, bool throwOnError) => GetType(name, throwOnError: throwOnError, ignoreCase: false);
+ public virtual Type GetType(string name, bool throwOnError, bool ignoreCase) { throw NotImplemented.ByDesign; }
+
+ public virtual bool IsDefined(Type attributeType, bool inherit) { throw NotImplemented.ByDesign; }
+
+ public virtual IEnumerable<CustomAttributeData> CustomAttributes => GetCustomAttributesData();
+ public virtual IList<CustomAttributeData> GetCustomAttributesData() { throw NotImplemented.ByDesign; }
+
+ public virtual object[] GetCustomAttributes(bool inherit) { throw NotImplemented.ByDesign; }
+ public virtual object[] GetCustomAttributes(Type attributeType, bool inherit) { throw NotImplemented.ByDesign; }
+
+ public virtual string EscapedCodeBase => AssemblyName.EscapeCodeBase(CodeBase);
+
+ public object CreateInstance(string typeName) => CreateInstance(typeName, false, BindingFlags.Public | BindingFlags.Instance, binder: null, args: null, culture: null, activationAttributes: null);
+ public object CreateInstance(string typeName, bool ignoreCase) => CreateInstance(typeName, ignoreCase, BindingFlags.Public | BindingFlags.Instance, binder: null, args: null, culture: null, activationAttributes: null);
+ public virtual object CreateInstance(string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes)
+ {
+ Type t = GetType(typeName, throwOnError: false, ignoreCase: ignoreCase);
+ if (t == null)
+ return null;
+
+ return Activator.CreateInstance(t, bindingAttr, binder, args, culture, activationAttributes);
+ }
+
+ public virtual event ModuleResolveEventHandler ModuleResolve { add { throw NotImplemented.ByDesign; } remove { throw NotImplemented.ByDesign; } }
+
+ public virtual Module ManifestModule { get { throw NotImplemented.ByDesign; } }
+ public virtual Module GetModule(string name) { throw NotImplemented.ByDesign; }
+
+ public Module[] GetModules() => GetModules(getResourceModules: false);
+ public virtual Module[] GetModules(bool getResourceModules) { throw NotImplemented.ByDesign; }
+
+ public virtual IEnumerable<Module> Modules => GetLoadedModules(getResourceModules: true);
+ public Module[] GetLoadedModules() => GetLoadedModules(getResourceModules: false);
+ public virtual Module[] GetLoadedModules(bool getResourceModules) { throw NotImplemented.ByDesign; }
+
+ public virtual AssemblyName[] GetReferencedAssemblies() { throw NotImplemented.ByDesign; }
+
+ public virtual Assembly GetSatelliteAssembly(CultureInfo culture) { throw NotImplemented.ByDesign; }
+ public virtual Assembly GetSatelliteAssembly(CultureInfo culture, Version version) { throw NotImplemented.ByDesign; }
+
+ public virtual FileStream GetFile(string name) { throw NotImplemented.ByDesign; }
+ public virtual FileStream[] GetFiles() => GetFiles(getResourceModules: false);
+ public virtual FileStream[] GetFiles(bool getResourceModules) { throw NotImplemented.ByDesign; }
+
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { throw NotImplemented.ByDesign; }
+
+ public override string ToString()
+ {
+ string displayName = FullName;
+ if (displayName == null)
+ return base.ToString();
+ else
+ return displayName;
+ }
+
+ /*
+ Returns true if the assembly was loaded from the global assembly cache.
+ */
+ public virtual bool GlobalAssemblyCache { get { throw NotImplemented.ByDesign; } }
+ public virtual Int64 HostContext { get { throw NotImplemented.ByDesign; } }
+
+ public override bool Equals(object o) => base.Equals(o);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(Assembly left, Assembly right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Assembly left, Assembly right)
+ {
+ return !(left == right);
+ }
+
+ public static string CreateQualifiedName(string assemblyName, string typeName) => typeName + ", " + assemblyName;
+
+ public static Assembly GetAssembly(Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException(nameof(type));
+
+ Module m = type.Module;
+ if (m == null)
+ return null;
+ else
+ return m.Assembly;
+ }
+
+ public static Assembly Load(byte[] rawAssembly) => Load(rawAssembly, rawSymbolStore: null);
+
+ [Obsolete("This method has been deprecated. Please use Assembly.Load() instead. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public static Assembly LoadWithPartialName(string partialName)
+ {
+ if (partialName == null)
+ throw new ArgumentNullException(nameof(partialName));
+
+ return Load(partialName);
+ }
+
+ public static Assembly UnsafeLoadFrom(string assemblyFile) => LoadFrom(assemblyFile);
+
+ public Module LoadModule(string moduleName, byte[] rawModule) => LoadModule(moduleName, rawModule, null);
+ public virtual Module LoadModule(string moduleName, byte[] rawModule, byte[] rawSymbolStore) { throw NotImplemented.ByDesign; }
+
+ public static Assembly ReflectionOnlyLoad(byte[] rawAssembly) { throw new PlatformNotSupportedException(SR.PlatformNotSupported_ReflectionOnly); }
+ public static Assembly ReflectionOnlyLoad(string assemblyString) { throw new PlatformNotSupportedException(SR.PlatformNotSupported_ReflectionOnly); }
+ public static Assembly ReflectionOnlyLoadFrom(string assemblyFile) { throw new PlatformNotSupportedException(SR.PlatformNotSupported_ReflectionOnly); }
+
+ public virtual SecurityRuleSet SecurityRuleSet => SecurityRuleSet.None;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyAlgorithmIdAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyAlgorithmIdAttribute.cs
new file mode 100644
index 0000000000..fe24f353be
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyAlgorithmIdAttribute.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Configuration.Assemblies;
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyAlgorithmIdAttribute : Attribute
+ {
+ public AssemblyAlgorithmIdAttribute(AssemblyHashAlgorithm algorithmId)
+ {
+ AlgorithmId = (uint)algorithmId;
+ }
+
+ [CLSCompliant(false)]
+ public AssemblyAlgorithmIdAttribute(uint algorithmId)
+ {
+ AlgorithmId = algorithmId;
+ }
+
+ [CLSCompliant(false)]
+ public uint AlgorithmId { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCompanyAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCompanyAttribute.cs
new file mode 100644
index 0000000000..d986db60a3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCompanyAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyCompanyAttribute : Attribute
+ {
+ public AssemblyCompanyAttribute(string company)
+ {
+ Company = company;
+ }
+
+ public string Company { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyConfigurationAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyConfigurationAttribute.cs
new file mode 100644
index 0000000000..195c4d0ca6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyConfigurationAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyConfigurationAttribute : Attribute
+ {
+ public AssemblyConfigurationAttribute(string configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public string Configuration { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyContentType.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyContentType.cs
new file mode 100644
index 0000000000..2ee1a00818
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyContentType.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public enum AssemblyContentType
+ {
+ Default = 0,
+ WindowsRuntime = 1,
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCopyrightAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCopyrightAttribute.cs
new file mode 100644
index 0000000000..e50e19932b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCopyrightAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyCopyrightAttribute : Attribute
+ {
+ public AssemblyCopyrightAttribute(string copyright)
+ {
+ Copyright = copyright;
+ }
+
+ public string Copyright { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCultureAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCultureAttribute.cs
new file mode 100644
index 0000000000..e31c6f9c1c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyCultureAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyCultureAttribute : Attribute
+ {
+ public AssemblyCultureAttribute(string culture)
+ {
+ Culture = culture;
+ }
+
+ public string Culture { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDefaultAliasAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDefaultAliasAttribute.cs
new file mode 100644
index 0000000000..ced35ed3fd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDefaultAliasAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyDefaultAliasAttribute : Attribute
+ {
+ public AssemblyDefaultAliasAttribute(string defaultAlias)
+ {
+ DefaultAlias = defaultAlias;
+ }
+
+ public string DefaultAlias { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDelaySignAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDelaySignAttribute.cs
new file mode 100644
index 0000000000..eae2cf613c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDelaySignAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyDelaySignAttribute : Attribute
+ {
+ public AssemblyDelaySignAttribute(bool delaySign)
+ {
+ DelaySign = delaySign;
+ }
+
+ public bool DelaySign { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDescriptionAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDescriptionAttribute.cs
new file mode 100644
index 0000000000..50f57c96a6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyDescriptionAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyDescriptionAttribute : Attribute
+ {
+ public AssemblyDescriptionAttribute(string description)
+ {
+ Description = description;
+ }
+
+ public string Description { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyFileVersionAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyFileVersionAttribute.cs
new file mode 100644
index 0000000000..b5face65bc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyFileVersionAttribute.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyFileVersionAttribute : Attribute
+ {
+ public AssemblyFileVersionAttribute(string version)
+ {
+ if (version == null)
+ throw new ArgumentNullException(nameof(version));
+ Version = version;
+ }
+
+ public string Version { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyFlagsAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyFlagsAttribute.cs
new file mode 100644
index 0000000000..103413340c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyFlagsAttribute.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyFlagsAttribute : Attribute
+ {
+ private AssemblyNameFlags _flags;
+
+ [Obsolete("This constructor has been deprecated. Please use AssemblyFlagsAttribute(AssemblyNameFlags) instead. http://go.microsoft.com/fwlink/?linkid=14202")]
+ [CLSCompliant(false)]
+ public AssemblyFlagsAttribute(uint flags)
+ {
+ _flags = (AssemblyNameFlags)flags;
+ }
+
+ [Obsolete("This property has been deprecated. Please use AssemblyFlags instead. http://go.microsoft.com/fwlink/?linkid=14202")]
+ [CLSCompliant(false)]
+ public uint Flags
+ {
+ get { return (uint)_flags; }
+ }
+
+ public int AssemblyFlags
+ {
+ get { return (int)_flags; }
+ }
+
+ [Obsolete("This constructor has been deprecated. Please use AssemblyFlagsAttribute(AssemblyNameFlags) instead. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public AssemblyFlagsAttribute(int assemblyFlags)
+ {
+ _flags = (AssemblyNameFlags)assemblyFlags;
+ }
+
+ public AssemblyFlagsAttribute(AssemblyNameFlags assemblyFlags)
+ {
+ _flags = assemblyFlags;
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyInformationalVersionAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyInformationalVersionAttribute.cs
new file mode 100644
index 0000000000..915b973ab9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyInformationalVersionAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyInformationalVersionAttribute : Attribute
+ {
+ public AssemblyInformationalVersionAttribute(string informationalVersion)
+ {
+ InformationalVersion = informationalVersion;
+ }
+
+ public string InformationalVersion { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyKeyFileAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyKeyFileAttribute.cs
new file mode 100644
index 0000000000..9f7387d8af
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyKeyFileAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyKeyFileAttribute : Attribute
+ {
+ public AssemblyKeyFileAttribute(string keyFile)
+ {
+ KeyFile = keyFile;
+ }
+
+ public string KeyFile { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyKeyNameAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyKeyNameAttribute.cs
new file mode 100644
index 0000000000..4cf51754ea
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyKeyNameAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyKeyNameAttribute : Attribute
+ {
+ public AssemblyKeyNameAttribute(string keyName)
+ {
+ KeyName = keyName;
+ }
+
+ public string KeyName { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyMetadataAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyMetadataAttribute.cs
new file mode 100644
index 0000000000..de9f6351ec
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyMetadataAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
+ public sealed class AssemblyMetadataAttribute : Attribute
+ {
+ public AssemblyMetadataAttribute(string key, string value)
+ {
+ Key = key;
+ Value = value;
+ }
+
+ public string Key { get; }
+
+ public string Value { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFlags.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFlags.cs
new file mode 100644
index 0000000000..d321032031
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFlags.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum AssemblyNameFlags
+ {
+ None = 0x0000,
+ // Flag used to indicate that an assembly ref contains the full public key, not the compressed token.
+ // Must match afPublicKey in CorHdr.h.
+ PublicKey = 0x0001,
+ //ProcArchMask = 0x00F0, // Bits describing the processor architecture
+ // Accessible via AssemblyName.ProcessorArchitecture
+ EnableJITcompileOptimizer = 0x4000,
+ EnableJITcompileTracking = 0x8000,
+ Retargetable = 0x0100,
+ //ContentType = 0x0E00, // Bits describing the ContentType are accessible via AssemblyName.ContentType
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs
new file mode 100644
index 0000000000..716afb045c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Text;
+using System.Globalization;
+using System.Collections.Generic;
+
+namespace System.Reflection
+{
+ internal static class AssemblyNameFormatter
+ {
+ public static string ComputeDisplayName(string name, Version version, string cultureName, byte[] pkt, AssemblyNameFlags flags, AssemblyContentType contentType)
+ {
+ const int PUBLIC_KEY_TOKEN_LEN = 8;
+
+ if (name == string.Empty)
+ throw new FileLoadException();
+
+ StringBuilder sb = new StringBuilder();
+ if (name != null)
+ {
+ sb.AppendQuoted(name);
+ }
+
+ if (version != null)
+ {
+ Version canonicalizedVersion = version.CanonicalizeVersion();
+ if (canonicalizedVersion.Major != ushort.MaxValue)
+ {
+ sb.Append(", Version=");
+ sb.Append(canonicalizedVersion.Major);
+
+ if (canonicalizedVersion.Minor != ushort.MaxValue)
+ {
+ sb.Append('.');
+ sb.Append(canonicalizedVersion.Minor);
+
+ if (canonicalizedVersion.Build != ushort.MaxValue)
+ {
+ sb.Append('.');
+ sb.Append(canonicalizedVersion.Build);
+
+ if (canonicalizedVersion.Revision != ushort.MaxValue)
+ {
+ sb.Append('.');
+ sb.Append(canonicalizedVersion.Revision);
+ }
+ }
+ }
+ }
+ }
+
+ if (cultureName != null)
+ {
+ if (cultureName == string.Empty)
+ cultureName = "neutral";
+ sb.Append(", Culture=");
+ sb.AppendQuoted(cultureName);
+ }
+
+ if (pkt != null)
+ {
+ if (pkt.Length > PUBLIC_KEY_TOKEN_LEN)
+ throw new ArgumentException();
+
+ sb.Append(", PublicKeyToken=");
+ if (pkt.Length == 0)
+ sb.Append("null");
+ else
+ {
+ foreach (byte b in pkt)
+ {
+ sb.Append(b.ToString("x2", CultureInfo.InvariantCulture));
+ }
+ }
+ }
+
+ if (0 != (flags & AssemblyNameFlags.Retargetable))
+ sb.Append(", Retargetable=Yes");
+
+ if (contentType == AssemblyContentType.WindowsRuntime)
+ sb.Append(", ContentType=WindowsRuntime");
+
+ // NOTE: By design (desktop compat) AssemblyName.FullName and ToString() do not include ProcessorArchitecture.
+
+ return sb.ToString();
+ }
+
+ private static void AppendQuoted(this StringBuilder sb, string s)
+ {
+ bool needsQuoting = false;
+ const char quoteChar = '\"';
+
+ //@todo: App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one
+ // by some algorithm. Rather than guess at it, I'll just use double-quote consistently.
+ if (s != s.Trim() || s.Contains('\"') || s.Contains('\''))
+ needsQuoting = true;
+
+ if (needsQuoting)
+ sb.Append(quoteChar);
+
+ for (int i = 0; i < s.Length; i++)
+ {
+ bool addedEscape = false;
+ foreach (KeyValuePair<char, string> kv in EscapeSequences)
+ {
+ string escapeReplacement = kv.Value;
+ if (!(s[i] == escapeReplacement[0]))
+ continue;
+ if ((s.Length - i) < escapeReplacement.Length)
+ continue;
+ string prefix = s.Substring(i, escapeReplacement.Length);
+ if (prefix == escapeReplacement)
+ {
+ sb.Append('\\');
+ sb.Append(kv.Key);
+ addedEscape = true;
+ }
+ }
+
+ if (!addedEscape)
+ sb.Append(s[i]);
+ }
+
+ if (needsQuoting)
+ sb.Append(quoteChar);
+ }
+
+ private static Version CanonicalizeVersion(this Version version)
+ {
+ ushort major = (ushort)version.Major;
+ ushort minor = (ushort)version.Minor;
+ ushort build = (ushort)version.Build;
+ ushort revision = (ushort)version.Revision;
+
+ if (major == version.Major && minor == version.Minor && build == version.Build && revision == version.Revision)
+ return version;
+
+ return new Version(major, minor, build, revision);
+ }
+
+ public static KeyValuePair<char, string>[] EscapeSequences =
+ {
+ new KeyValuePair<char, string>('\\', "\\"),
+ new KeyValuePair<char, string>(',', ","),
+ new KeyValuePair<char, string>('=', "="),
+ new KeyValuePair<char, string>('\'', "'"),
+ new KeyValuePair<char, string>('\"', "\""),
+ new KeyValuePair<char, string>('n', Environment.NewLine),
+ new KeyValuePair<char, string>('t', "\t"),
+ };
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyProductAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyProductAttribute.cs
new file mode 100644
index 0000000000..43cb62df99
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyProductAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyProductAttribute : Attribute
+ {
+ public AssemblyProductAttribute(string product)
+ {
+ Product = product;
+ }
+
+ public string Product { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblySignatureKeyAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblySignatureKeyAttribute.cs
new file mode 100644
index 0000000000..e6ec8af1b3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblySignatureKeyAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
+ public sealed class AssemblySignatureKeyAttribute : Attribute
+ {
+ public AssemblySignatureKeyAttribute(string publicKey, string countersignature)
+ {
+ PublicKey = publicKey;
+ Countersignature = countersignature;
+ }
+
+ public string PublicKey { get; }
+
+ public string Countersignature { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyTitleAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyTitleAttribute.cs
new file mode 100644
index 0000000000..26d7a2e66c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyTitleAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyTitleAttribute : Attribute
+ {
+ public AssemblyTitleAttribute(string title)
+ {
+ Title = title;
+ }
+
+ public string Title { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyTrademarkAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyTrademarkAttribute.cs
new file mode 100644
index 0000000000..1d3edf51d5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyTrademarkAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyTrademarkAttribute : Attribute
+ {
+ public AssemblyTrademarkAttribute(string trademark)
+ {
+ Trademark = trademark;
+ }
+
+ public string Trademark { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/AssemblyVersionAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyVersionAttribute.cs
new file mode 100644
index 0000000000..b3557bac97
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/AssemblyVersionAttribute.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class AssemblyVersionAttribute : Attribute
+ {
+ public AssemblyVersionAttribute(string version)
+ {
+ Version = version;
+ }
+
+ public string Version { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/Binder.cs b/src/System.Private.CoreLib/shared/System/Reflection/Binder.cs
new file mode 100644
index 0000000000..3dc5665d52
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/Binder.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+
+namespace System.Reflection
+{
+ public abstract class Binder
+ {
+ protected Binder() { }
+ public abstract FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, CultureInfo culture);
+ public abstract MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] names, out object state);
+ public abstract object ChangeType(object value, Type type, CultureInfo culture);
+ public abstract void ReorderArgumentArray(ref object[] args, object state);
+ public abstract MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers);
+ public abstract PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/BindingFlags.cs b/src/System.Private.CoreLib/shared/System/Reflection/BindingFlags.cs
new file mode 100644
index 0000000000..7ba83e20da
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/BindingFlags.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum BindingFlags
+ {
+ // NOTES: We have lookup masks defined in RuntimeType and Activator. If we
+ // change the lookup values then these masks may need to change also.
+
+ // a place holder for no flag specifed
+ Default = 0x00,
+
+ // These flags indicate what to search for when binding
+ IgnoreCase = 0x01, // Ignore the case of Names while searching
+ DeclaredOnly = 0x02, // Only look at the members declared on the Type
+ Instance = 0x04, // Include Instance members in search
+ Static = 0x08, // Include Static members in search
+ Public = 0x10, // Include Public members in search
+ NonPublic = 0x20, // Include Non-Public members in search
+ FlattenHierarchy = 0x40, // Rollup the statics into the class.
+
+ // These flags are used by InvokeMember to determine
+ // what type of member we are trying to Invoke.
+ // BindingAccess = 0xFF00;
+ InvokeMethod = 0x0100,
+ CreateInstance = 0x0200,
+ GetField = 0x0400,
+ SetField = 0x0800,
+ GetProperty = 0x1000,
+ SetProperty = 0x2000,
+
+ // These flags are also used by InvokeMember but they should only
+ // be used when calling InvokeMember on a COM object.
+ PutDispProperty = 0x4000,
+ PutRefDispProperty = 0x8000,
+
+ ExactBinding = 0x010000, // Bind with Exact Type matching, No Change type
+ SuppressChangeType = 0x020000,
+
+ // DefaultValueBinding will return the set of methods having ArgCount or
+ // more parameters. This is used for default values, etc.
+ OptionalParamBinding = 0x040000,
+
+ // These are a couple of misc attributes used
+ IgnoreReturn = 0x01000000, // This is used in COM Interop
+ DoNotWrapExceptions = 0x02000000, // Disables wrapping exceptions in TargetInvocationException
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/CallingConventions.cs b/src/System.Private.CoreLib/shared/System/Reflection/CallingConventions.cs
new file mode 100644
index 0000000000..bb6d6cd809
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/CallingConventions.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// CallingConventions is a set of Bits representing the calling conventions in the system.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum CallingConventions
+ {
+ //NOTE: If you change this please update COMMember.cpp. These
+ // are defined there.
+ Standard = 0x0001,
+ VarArgs = 0x0002,
+ Any = Standard | VarArgs,
+ HasThis = 0x0020,
+ ExplicitThis = 0x0040,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ConstructorInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/ConstructorInfo.cs
new file mode 100644
index 0000000000..3ee1dbf855
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ConstructorInfo.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+
+namespace System.Reflection
+{
+ public abstract partial class ConstructorInfo : MethodBase
+ {
+ protected ConstructorInfo() { }
+
+ public override MemberTypes MemberType => MemberTypes.Constructor;
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public object Invoke(object[] parameters) => Invoke(BindingFlags.Default, binder: null, parameters: parameters, culture: null);
+ public abstract object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture);
+
+ public override bool Equals(object obj) => base.Equals(obj);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(ConstructorInfo left, ConstructorInfo right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(ConstructorInfo left, ConstructorInfo right) => !(left == right);
+
+ public static readonly string ConstructorName = ".ctor";
+ public static readonly string TypeConstructorName = ".cctor";
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/CustomAttributeFormatException.cs b/src/System.Private.CoreLib/shared/System/Reflection/CustomAttributeFormatException.cs
new file mode 100644
index 0000000000..1d7d4a7671
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/CustomAttributeFormatException.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class CustomAttributeFormatException : FormatException
+ {
+ public CustomAttributeFormatException()
+ : this(SR.Arg_CustomAttributeFormatException)
+ {
+ }
+
+ public CustomAttributeFormatException(string message)
+ : this(message, null)
+ {
+ }
+
+ public CustomAttributeFormatException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_CUSTOMATTRIBUTEFORMAT;
+ }
+
+ protected CustomAttributeFormatException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/DefaultMemberAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/DefaultMemberAttribute.cs
new file mode 100644
index 0000000000..585fdb07cd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/DefaultMemberAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface)]
+ public sealed class DefaultMemberAttribute : Attribute
+ {
+ // You must provide the name of the member, this is required
+ public DefaultMemberAttribute(string memberName)
+ {
+ MemberName = memberName;
+ }
+
+ // A get accessor to return the name from the attribute.
+ // NOTE: There is no setter because the name must be provided
+ // to the constructor. The name is not optional.
+ public string MemberName { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/EventAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/EventAttributes.cs
new file mode 100644
index 0000000000..fbc2972f69
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/EventAttributes.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// EventAttributes are an enum defining the attributes associated with and Event.
+// These are defined in CorHdr.h and are a combination of bits and enums.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum EventAttributes
+ {
+ None = 0x0000,
+
+ // This Enum matchs the CorEventAttr defined in CorHdr.h
+ SpecialName = 0x0200, // event is special. Name describes how.
+
+ RTSpecialName = 0x0400, // Runtime(metadata internal APIs) should check name encoding.
+
+ ReservedMask = 0x0400,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/EventInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/EventInfo.cs
new file mode 100644
index 0000000000..ccd9acf648
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/EventInfo.cs
@@ -0,0 +1,115 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+#if FEATURE_COMINTEROP
+using EventRegistrationToken = System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken;
+#endif //#if FEATURE_COMINTEROP
+
+namespace System.Reflection
+{
+ public abstract class EventInfo : MemberInfo
+ {
+ protected EventInfo() { }
+
+ public override MemberTypes MemberType => MemberTypes.Event;
+
+ public abstract EventAttributes Attributes { get; }
+ public bool IsSpecialName => (Attributes & EventAttributes.SpecialName) != 0;
+
+ public MethodInfo[] GetOtherMethods() => GetOtherMethods(nonPublic: false);
+ public virtual MethodInfo[] GetOtherMethods(bool nonPublic) { throw NotImplemented.ByDesign; }
+
+ public virtual MethodInfo AddMethod => GetAddMethod(nonPublic: true);
+ public virtual MethodInfo RemoveMethod => GetRemoveMethod(nonPublic: true);
+ public virtual MethodInfo RaiseMethod => GetRaiseMethod(nonPublic: true);
+
+ public MethodInfo GetAddMethod() => GetAddMethod(nonPublic: false);
+ public MethodInfo GetRemoveMethod() => GetRemoveMethod(nonPublic: false);
+ public MethodInfo GetRaiseMethod() => GetRaiseMethod(nonPublic: false);
+
+ public abstract MethodInfo GetAddMethod(bool nonPublic);
+ public abstract MethodInfo GetRemoveMethod(bool nonPublic);
+ public abstract MethodInfo GetRaiseMethod(bool nonPublic);
+
+ public virtual bool IsMulticast
+ {
+ get
+ {
+ Type cl = EventHandlerType;
+ Type mc = typeof(MulticastDelegate);
+ return mc.IsAssignableFrom(cl);
+ }
+ }
+
+ public virtual Type EventHandlerType
+ {
+ get
+ {
+ MethodInfo m = GetAddMethod(true);
+ ParameterInfo[] p = m.GetParametersNoCopy();
+ Type del = typeof(Delegate);
+ for (int i = 0; i < p.Length; i++)
+ {
+ Type c = p[i].ParameterType;
+ if (c.IsSubclassOf(del))
+ return c;
+ }
+ return null;
+ }
+ }
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public virtual void AddEventHandler(object target, Delegate handler)
+ {
+ MethodInfo addMethod = GetAddMethod(nonPublic: false);
+
+ if (addMethod == null)
+ throw new InvalidOperationException(SR.InvalidOperation_NoPublicAddMethod);
+
+#if FEATURE_COMINTEROP
+ if (addMethod.ReturnType == typeof(EventRegistrationToken))
+ throw new InvalidOperationException(SR.InvalidOperation_NotSupportedOnWinRTEvent);
+#endif //#if FEATURE_COMINTEROP
+
+ addMethod.Invoke(target, new object[] { handler });
+ }
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public virtual void RemoveEventHandler(object target, Delegate handler)
+ {
+ MethodInfo removeMethod = GetRemoveMethod(nonPublic: false);
+
+ if (removeMethod == null)
+ throw new InvalidOperationException(SR.InvalidOperation_NoPublicRemoveMethod);
+
+#if FEATURE_COMINTEROP
+ ParameterInfo[] parameters = removeMethod.GetParametersNoCopy();
+ if (parameters[0].ParameterType == typeof(EventRegistrationToken))
+ throw new InvalidOperationException(SR.InvalidOperation_NotSupportedOnWinRTEvent);
+#endif //#if FEATURE_COMINTEROP
+
+ removeMethod.Invoke(target, new object[] { handler });
+ }
+
+ public override bool Equals(object obj) => base.Equals(obj);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(EventInfo left, EventInfo right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(EventInfo left, EventInfo right) => !(left == right);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ExceptionHandlingClauseOptions.cs b/src/System.Private.CoreLib/shared/System/Reflection/ExceptionHandlingClauseOptions.cs
new file mode 100644
index 0000000000..46285f7c82
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ExceptionHandlingClauseOptions.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum ExceptionHandlingClauseOptions : int
+ {
+ Clause = 0x0,
+ Filter = 0x1,
+ Finally = 0x2,
+ Fault = 0x4,
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/FieldAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/FieldAttributes.cs
new file mode 100644
index 0000000000..048d0e7031
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/FieldAttributes.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ // This Enum matchs the CorFieldAttr defined in CorHdr.h
+ [Flags]
+ public enum FieldAttributes
+ {
+ // member access mask - Use this mask to retrieve accessibility information.
+ FieldAccessMask = 0x0007,
+ PrivateScope = 0x0000, // Member not referenceable.
+ Private = 0x0001, // Accessible only by the parent type.
+ FamANDAssem = 0x0002, // Accessible by sub-types only in this Assembly.
+ Assembly = 0x0003, // Accessibly by anyone in the Assembly.
+ Family = 0x0004, // Accessible only by type and sub-types.
+ FamORAssem = 0x0005, // Accessibly by sub-types anywhere, plus anyone in assembly.
+ Public = 0x0006, // Accessibly by anyone who has visibility to this scope.
+ // end member access mask
+
+ // field contract attributes.
+ Static = 0x0010, // Defined on type, else per instance.
+ InitOnly = 0x0020, // Field may only be initialized, not written to after init.
+ Literal = 0x0040, // Value is compile time constant.
+ NotSerialized = 0x0080, // Field does not have to be serialized when type is remoted.
+
+ SpecialName = 0x0200, // field is special. Name describes how.
+
+ // interop attributes
+ PinvokeImpl = 0x2000, // Implementation is forwarded through pinvoke.
+
+ RTSpecialName = 0x0400, // Runtime(metadata internal APIs) should check name encoding.
+ HasFieldMarshal = 0x1000, // Field has marshalling information.
+ HasDefault = 0x8000, // Field has default.
+ HasFieldRVA = 0x0100, // Field has RVA.
+
+ ReservedMask = 0x9500,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/FieldInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/FieldInfo.cs
new file mode 100644
index 0000000000..0863c2b019
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/FieldInfo.cs
@@ -0,0 +1,72 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+
+namespace System.Reflection
+{
+ public abstract partial class FieldInfo : MemberInfo
+ {
+ protected FieldInfo() { }
+
+ public override MemberTypes MemberType => MemberTypes.Field;
+
+ public abstract FieldAttributes Attributes { get; }
+ public abstract Type FieldType { get; }
+
+ public bool IsInitOnly => (Attributes & FieldAttributes.InitOnly) != 0;
+ public bool IsLiteral => (Attributes & FieldAttributes.Literal) != 0;
+ public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) != 0;
+ public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) != 0;
+ public bool IsSpecialName => (Attributes & FieldAttributes.SpecialName) != 0;
+ public bool IsStatic => (Attributes & FieldAttributes.Static) != 0;
+
+ public bool IsAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Assembly;
+ public bool IsFamily => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Family;
+ public bool IsFamilyAndAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamANDAssem;
+ public bool IsFamilyOrAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamORAssem;
+ public bool IsPrivate => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Private;
+ public bool IsPublic => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Public;
+
+ public virtual bool IsSecurityCritical => true;
+ public virtual bool IsSecuritySafeCritical => false;
+ public virtual bool IsSecurityTransparent => false;
+
+ public abstract RuntimeFieldHandle FieldHandle { get; }
+
+ public override bool Equals(object obj) => base.Equals(obj);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(FieldInfo left, FieldInfo right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(FieldInfo left, FieldInfo right) => !(left == right);
+
+ public abstract object GetValue(object obj);
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public void SetValue(object obj, object value) => SetValue(obj, value, BindingFlags.Default, Type.DefaultBinder, null);
+ public abstract void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture);
+
+ [CLSCompliant(false)]
+ public virtual void SetValueDirect(TypedReference obj, object value) { throw new NotSupportedException(SR.NotSupported_AbstractNonCLS); }
+ [CLSCompliant(false)]
+ public virtual object GetValueDirect(TypedReference obj) { throw new NotSupportedException(SR.NotSupported_AbstractNonCLS); }
+
+ public virtual object GetRawConstantValue() { throw new NotSupportedException(SR.NotSupported_AbstractNonCLS); }
+
+ public virtual Type[] GetOptionalCustomModifiers() { throw NotImplemented.ByDesign; }
+ public virtual Type[] GetRequiredCustomModifiers() { throw NotImplemented.ByDesign; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/GenericParameterAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/GenericParameterAttributes.cs
new file mode 100644
index 0000000000..4b579d273e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/GenericParameterAttributes.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum GenericParameterAttributes
+ {
+ None = 0x0000,
+ VarianceMask = 0x0003,
+ Covariant = 0x0001,
+ Contravariant = 0x0002,
+ SpecialConstraintMask = 0x001C,
+ ReferenceTypeConstraint = 0x0004,
+ NotNullableValueTypeConstraint = 0x0008,
+ DefaultConstructorConstraint = 0x0010,
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ICustomAttributeProvider.cs b/src/System.Private.CoreLib/shared/System/Reflection/ICustomAttributeProvider.cs
new file mode 100644
index 0000000000..3cae295bc4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ICustomAttributeProvider.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public interface ICustomAttributeProvider
+ {
+ object[] GetCustomAttributes(bool inherit);
+ object[] GetCustomAttributes(Type attributeType, bool inherit);
+ bool IsDefined(Type attributeType, bool inherit);
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/IReflect.cs b/src/System.Private.CoreLib/shared/System/Reflection/IReflect.cs
new file mode 100644
index 0000000000..a7e84b6168
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/IReflect.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+
+namespace System.Reflection
+{
+ public interface IReflect
+ {
+ // Return the requested method if it is implemented by the Reflection object. The
+ // match is based upon the name and DescriptorInfo which describes the signature
+ // of the method.
+ MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers);
+
+ // Return the requested method if it is implemented by the Reflection object. The
+ // match is based upon the name of the method. If the object implementes multiple methods
+ // with the same name an AmbiguousMatchException is thrown.
+ MethodInfo GetMethod(string name, BindingFlags bindingAttr);
+
+ MethodInfo[] GetMethods(BindingFlags bindingAttr);
+
+ // Return the requestion field if it is implemented by the Reflection object. The
+ // match is based upon a name. There cannot be more than a single field with
+ // a name.
+ FieldInfo GetField(string name, BindingFlags bindingAttr);
+
+ FieldInfo[] GetFields(BindingFlags bindingAttr);
+
+ // Return the property based upon name. If more than one property has the given
+ // name an AmbiguousMatchException will be thrown. Returns null if no property
+ // is found.
+ PropertyInfo GetProperty(string name, BindingFlags bindingAttr);
+
+ // Return the property based upon the name and Descriptor info describing the property
+ // indexing. Return null if no property is found.
+ PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers);
+
+ // Returns an array of PropertyInfos for all the properties defined on
+ // the Reflection object.
+ PropertyInfo[] GetProperties(BindingFlags bindingAttr);
+
+ // Return an array of members which match the passed in name.
+ MemberInfo[] GetMember(string name, BindingFlags bindingAttr);
+
+ // Return an array of all of the members defined for this object.
+ MemberInfo[] GetMembers(BindingFlags bindingAttr);
+
+ // Description of the Binding Process.
+ // We must invoke a method that is accessible and for which the provided
+ // parameters have the most specific match. A method may be called if
+ // 1. The number of parameters in the method declaration equals the number of
+ // arguments provided to the invocation
+ // 2. The type of each argument can be converted by the binder to the
+ // type of the type of the parameter.
+ //
+ // The binder will find all of the matching methods. These method are found based
+ // upon the type of binding requested (MethodInvoke, Get/Set Properties). The set
+ // of methods is filtered by the name, number of arguments and a set of search modifiers
+ // defined in the Binder.
+ //
+ // After the method is selected, it will be invoked. Accessibility is checked
+ // at that point. The search may be control which set of methods are searched based
+ // upon the accessibility attribute associated with the method.
+ //
+ // The BindToMethod method is responsible for selecting the method to be invoked.
+ // For the default binder, the most specific method will be selected.
+ //
+ // This will invoke a specific member...
+ object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters);
+
+ // Return the underlying Type that represents the IReflect Object. For expando object,
+ // this is the (Object) IReflectInstance.GetType(). For Type object it is this.
+ Type UnderlyingSystemType { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/IReflectableType.cs b/src/System.Private.CoreLib/shared/System/Reflection/IReflectableType.cs
new file mode 100644
index 0000000000..5e2c0edab4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/IReflectableType.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public interface IReflectableType
+ {
+ TypeInfo GetTypeInfo();
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ImageFileMachine.cs b/src/System.Private.CoreLib/shared/System/Reflection/ImageFileMachine.cs
new file mode 100644
index 0000000000..230bc952e5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ImageFileMachine.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public enum ImageFileMachine
+ {
+ I386 = 0x014c,
+ IA64 = 0x0200,
+ AMD64 = 0x8664,
+ ARM = 0x01c4,
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/InterfaceMapping.cs b/src/System.Private.CoreLib/shared/System/Reflection/InterfaceMapping.cs
new file mode 100644
index 0000000000..2e0c0d8a28
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/InterfaceMapping.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public struct InterfaceMapping
+ {
+ public Type TargetType; // The type implementing the interface
+ public Type InterfaceType; // The type representing the interface
+ public MethodInfo[] TargetMethods; // The methods implementing the interface
+ public MethodInfo[] InterfaceMethods; // The methods defined on the interface
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/IntrospectionExtensions.cs b/src/System.Private.CoreLib/shared/System/Reflection/IntrospectionExtensions.cs
new file mode 100644
index 0000000000..acf5987d44
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/IntrospectionExtensions.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Reflection
+{
+ public static class IntrospectionExtensions
+ {
+ public static TypeInfo GetTypeInfo(this Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException(nameof(type));
+
+ if (type is IReflectableType reflectableType)
+ return reflectableType.GetTypeInfo();
+
+ return new TypeDelegator(type);
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/InvalidFilterCriteriaException.cs b/src/System.Private.CoreLib/shared/System/Reflection/InvalidFilterCriteriaException.cs
new file mode 100644
index 0000000000..dedcc54f4c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/InvalidFilterCriteriaException.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class InvalidFilterCriteriaException : ApplicationException
+ {
+ public InvalidFilterCriteriaException()
+ : this(SR.Arg_InvalidFilterCriteriaException)
+ {
+ }
+
+ public InvalidFilterCriteriaException(string message)
+ : this(message, null)
+ {
+ }
+
+ public InvalidFilterCriteriaException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_INVALIDFILTERCRITERIA;
+ }
+
+ protected InvalidFilterCriteriaException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ManifestResourceInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/ManifestResourceInfo.cs
new file mode 100644
index 0000000000..b9c56ab857
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ManifestResourceInfo.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public class ManifestResourceInfo
+ {
+ public ManifestResourceInfo(Assembly containingAssembly,
+ string containingFileName,
+ ResourceLocation resourceLocation)
+ {
+ ReferencedAssembly = containingAssembly;
+ FileName = containingFileName;
+ ResourceLocation = resourceLocation;
+ }
+
+ public virtual Assembly ReferencedAssembly { get; }
+ public virtual string FileName { get; }
+ public virtual ResourceLocation ResourceLocation { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/MemberFilter.cs b/src/System.Private.CoreLib/shared/System/Reflection/MemberFilter.cs
new file mode 100644
index 0000000000..bb1b15796a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/MemberFilter.cs
@@ -0,0 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public delegate bool MemberFilter(MemberInfo m, object filterCriteria);
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/MemberInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/MemberInfo.cs
new file mode 100644
index 0000000000..d8a7458632
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/MemberInfo.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Reflection
+{
+ public abstract partial class MemberInfo : ICustomAttributeProvider
+ {
+ protected MemberInfo() { }
+
+ public abstract MemberTypes MemberType { get; }
+ public abstract string Name { get; }
+ public abstract Type DeclaringType { get; }
+ public abstract Type ReflectedType { get; }
+
+ public virtual Module Module
+ {
+ get
+ {
+ // This check is necessary because for some reason, Type adds a new "Module" property that hides the inherited one instead
+ // of overriding.
+
+ Type type = this as Type;
+ if (type != null)
+ return type.Module;
+
+ throw NotImplemented.ByDesign;
+ }
+ }
+
+ public virtual bool HasSameMetadataDefinitionAs(MemberInfo other) { throw NotImplemented.ByDesign; }
+
+ public abstract bool IsDefined(Type attributeType, bool inherit);
+ public abstract object[] GetCustomAttributes(bool inherit);
+ public abstract object[] GetCustomAttributes(Type attributeType, bool inherit);
+
+ public virtual IEnumerable<CustomAttributeData> CustomAttributes => GetCustomAttributesData();
+ public virtual IList<CustomAttributeData> GetCustomAttributesData() { throw NotImplemented.ByDesign; }
+
+ public virtual int MetadataToken { get { throw new InvalidOperationException(); } }
+
+ public override bool Equals(object obj) => base.Equals(obj);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(MemberInfo left, MemberInfo right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ Type type1, type2;
+ MethodBase method1, method2;
+ FieldInfo field1, field2;
+ EventInfo event1, event2;
+ PropertyInfo property1, property2;
+
+ if ((type1 = left as Type) != null && (type2 = right as Type) != null)
+ return type1 == type2;
+ else if ((method1 = left as MethodBase) != null && (method2 = right as MethodBase) != null)
+ return method1 == method2;
+ else if ((field1 = left as FieldInfo) != null && (field2 = right as FieldInfo) != null)
+ return field1 == field2;
+ else if ((event1 = left as EventInfo) != null && (event2 = right as EventInfo) != null)
+ return event1 == event2;
+ else if ((property1 = left as PropertyInfo) != null && (property2 = right as PropertyInfo) != null)
+ return property1 == property2;
+
+ return false;
+ }
+
+ public static bool operator !=(MemberInfo left, MemberInfo right) => !(left == right);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/MemberTypes.cs b/src/System.Private.CoreLib/shared/System/Reflection/MemberTypes.cs
new file mode 100644
index 0000000000..57072dcfbe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/MemberTypes.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum MemberTypes
+ {
+ Constructor = 0x01,
+ Event = 0x02,
+ Field = 0x04,
+ Method = 0x08,
+ Property = 0x10,
+ TypeInfo = 0x20,
+ Custom = 0x40,
+ NestedType = 0x80,
+ All = Constructor | Event | Field | Method | Property | TypeInfo | NestedType,
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/MethodAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/MethodAttributes.cs
new file mode 100644
index 0000000000..1a7c7bf154
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/MethodAttributes.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum MethodAttributes
+ {
+ // NOTE: This Enum matchs the CorMethodAttr defined in CorHdr.h
+
+ // member access mask - Use this mask to retrieve accessibility information.
+ MemberAccessMask = 0x0007,
+ PrivateScope = 0x0000, // Member not referenceable.
+ Private = 0x0001, // Accessible only by the parent type.
+ FamANDAssem = 0x0002, // Accessible by sub-types only in this Assembly.
+ Assembly = 0x0003, // Accessibly by anyone in the Assembly.
+ Family = 0x0004, // Accessible only by type and sub-types.
+ FamORAssem = 0x0005, // Accessibly by sub-types anywhere, plus anyone in assembly.
+ Public = 0x0006, // Accessibly by anyone who has visibility to this scope.
+ // end member access mask
+
+ // method contract attributes.
+ Static = 0x0010, // Defined on type, else per instance.
+ Final = 0x0020, // Method may not be overridden.
+ Virtual = 0x0040, // Method virtual.
+ HideBySig = 0x0080, // Method hides by name+sig, else just by name.
+ CheckAccessOnOverride = 0x0200,
+
+ // vtable layout mask - Use this mask to retrieve vtable attributes.
+ VtableLayoutMask = 0x0100,
+ ReuseSlot = 0x0000, // The default.
+ NewSlot = 0x0100, // Method always gets a new slot in the vtable.
+ // end vtable layout mask
+
+ // method implementation attributes.
+ Abstract = 0x0400, // Method does not provide an implementation.
+ SpecialName = 0x0800, // Method is special. Name describes how.
+
+ // interop attributes
+ PinvokeImpl = 0x2000, // Implementation is forwarded through pinvoke.
+ UnmanagedExport = 0x0008, // Managed method exported via thunk to unmanaged code.
+ RTSpecialName = 0x1000, // Runtime should check name encoding.
+
+ HasSecurity = 0x4000, // Method has security associate with it.
+ RequireSecObject = 0x8000, // Method calls another method containing security code.
+
+ ReservedMask = 0xd000,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/MethodBase.cs b/src/System.Private.CoreLib/shared/System/Reflection/MethodBase.cs
new file mode 100644
index 0000000000..0037c74f4c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/MethodBase.cs
@@ -0,0 +1,86 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Diagnostics;
+
+namespace System.Reflection
+{
+ public abstract partial class MethodBase : MemberInfo
+ {
+ protected MethodBase() { }
+
+ public abstract ParameterInfo[] GetParameters();
+ public abstract MethodAttributes Attributes { get; }
+ public virtual MethodImplAttributes MethodImplementationFlags => GetMethodImplementationFlags();
+ public abstract MethodImplAttributes GetMethodImplementationFlags();
+ public virtual MethodBody GetMethodBody() { throw new InvalidOperationException(); }
+ public virtual CallingConventions CallingConvention => CallingConventions.Standard;
+
+ public bool IsAbstract => (Attributes & MethodAttributes.Abstract) != 0;
+ public bool IsConstructor
+ {
+ get
+ {
+ // To be backward compatible we only return true for instance RTSpecialName ctors.
+ return (this is ConstructorInfo &&
+ !IsStatic &&
+ ((Attributes & MethodAttributes.RTSpecialName) == MethodAttributes.RTSpecialName));
+ }
+ }
+ public bool IsFinal => (Attributes & MethodAttributes.Final) != 0;
+ public bool IsHideBySig => (Attributes & MethodAttributes.HideBySig) != 0;
+ public bool IsSpecialName => (Attributes & MethodAttributes.SpecialName) != 0;
+ public bool IsStatic => (Attributes & MethodAttributes.Static) != 0;
+ public bool IsVirtual => (Attributes & MethodAttributes.Virtual) != 0;
+
+ public bool IsAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Assembly;
+ public bool IsFamily => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family;
+ public bool IsFamilyAndAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamANDAssem;
+ public bool IsFamilyOrAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamORAssem;
+ public bool IsPrivate => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Private;
+ public bool IsPublic => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
+
+ public virtual bool IsConstructedGenericMethod => IsGenericMethod && !IsGenericMethodDefinition;
+ public virtual bool IsGenericMethod => false;
+ public virtual bool IsGenericMethodDefinition => false;
+ public virtual Type[] GetGenericArguments() { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+ public virtual bool ContainsGenericParameters => false;
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public object Invoke(object obj, object[] parameters) => Invoke(obj, BindingFlags.Default, binder: null, parameters: parameters, culture: null);
+ public abstract object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture);
+
+ public abstract RuntimeMethodHandle MethodHandle { get; }
+
+ public virtual bool IsSecurityCritical { get { throw NotImplemented.ByDesign; } }
+ public virtual bool IsSecuritySafeCritical { get { throw NotImplemented.ByDesign; } }
+ public virtual bool IsSecurityTransparent { get { throw NotImplemented.ByDesign; } }
+
+ public override bool Equals(object obj) => base.Equals(obj);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(MethodBase left, MethodBase right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ MethodInfo method1, method2;
+ ConstructorInfo constructor1, constructor2;
+
+ if ((method1 = left as MethodInfo) != null && (method2 = right as MethodInfo) != null)
+ return method1 == method2;
+ else if ((constructor1 = left as ConstructorInfo) != null && (constructor2 = right as ConstructorInfo) != null)
+ return constructor1 == constructor2;
+
+ return false;
+ }
+
+ public static bool operator !=(MethodBase left, MethodBase right) => !(left == right);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/MethodImplAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/MethodImplAttributes.cs
new file mode 100644
index 0000000000..a1ed326002
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/MethodImplAttributes.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ // This Enum matchs the CorMethodImpl defined in CorHdr.h
+ public enum MethodImplAttributes
+ {
+ // code impl mask
+ CodeTypeMask = 0x0003, // Flags about code type.
+ IL = 0x0000, // Method impl is IL.
+ Native = 0x0001, // Method impl is native.
+ OPTIL = 0x0002, // Method impl is OPTIL
+ Runtime = 0x0003, // Method impl is provided by the runtime.
+ // end code impl mask
+
+ // managed mask
+ ManagedMask = 0x0004, // Flags specifying whether the code is managed or unmanaged.
+ Unmanaged = 0x0004, // Method impl is unmanaged, otherwise managed.
+ Managed = 0x0000, // Method impl is managed.
+ // end managed mask
+
+ // implementation info and interop
+ ForwardRef = 0x0010, // Indicates method is not defined; used primarily in merge scenarios.
+ PreserveSig = 0x0080, // Indicates method sig is exported exactly as declared.
+
+ InternalCall = 0x1000, // Internal Call...
+
+ Synchronized = 0x0020, // Method is single threaded through the body.
+ NoInlining = 0x0008, // Method may not be inlined.
+ AggressiveInlining = 0x0100, // Method should be inlined if possible.
+ NoOptimization = 0x0040, // Method may not be optimized.
+
+ MaxMethodImplVal = 0xffff,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/MethodInfo.Internal.cs b/src/System.Private.CoreLib/shared/System/Reflection/MethodInfo.Internal.cs
new file mode 100644
index 0000000000..2806be639e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/MethodInfo.Internal.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public abstract partial class MethodInfo : MethodBase
+ {
+#if CORERT
+ public // Needs to be public so that Reflection.Core can see it.
+#else
+ internal
+#endif
+ virtual int GenericParameterCount => GetGenericArguments().Length;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/MethodInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/MethodInfo.cs
new file mode 100644
index 0000000000..95c62ba7f0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/MethodInfo.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public abstract partial class MethodInfo : MethodBase
+ {
+ protected MethodInfo() { }
+
+ public override MemberTypes MemberType => MemberTypes.Method;
+
+ public virtual ParameterInfo ReturnParameter { get { throw NotImplemented.ByDesign; } }
+ public virtual Type ReturnType { get { throw NotImplemented.ByDesign; } }
+
+ public override Type[] GetGenericArguments() { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+ public virtual MethodInfo GetGenericMethodDefinition() { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+ public virtual MethodInfo MakeGenericMethod(params Type[] typeArguments) { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+
+ public abstract MethodInfo GetBaseDefinition();
+
+ public abstract ICustomAttributeProvider ReturnTypeCustomAttributes { get; }
+
+ public virtual Delegate CreateDelegate(Type delegateType) { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+ public virtual Delegate CreateDelegate(Type delegateType, object target) { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+
+ public override bool Equals(object obj) => base.Equals(obj);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(MethodInfo left, MethodInfo right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(MethodInfo left, MethodInfo right) => !(left == right);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/Missing.cs b/src/System.Private.CoreLib/shared/System/Reflection/Missing.cs
new file mode 100644
index 0000000000..46ab32fccf
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/Missing.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ public sealed class Missing : ISerializable
+ {
+ public static readonly Missing Value = new Missing();
+
+ private Missing() { }
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/Module.cs b/src/System.Private.CoreLib/shared/System/Reflection/Module.cs
new file mode 100644
index 0000000000..56f83c40d9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/Module.cs
@@ -0,0 +1,182 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ public abstract class Module : ICustomAttributeProvider, ISerializable
+ {
+ protected Module() { }
+
+ public virtual Assembly Assembly { get { throw NotImplemented.ByDesign; } }
+ public virtual string FullyQualifiedName { get { throw NotImplemented.ByDesign; } }
+ public virtual string Name { get { throw NotImplemented.ByDesign; } }
+
+ public virtual int MDStreamVersion { get { throw NotImplemented.ByDesign; } }
+ public virtual Guid ModuleVersionId { get { throw NotImplemented.ByDesign; } }
+ public virtual string ScopeName { get { throw NotImplemented.ByDesign; } }
+ public ModuleHandle ModuleHandle => GetModuleHandleImpl();
+ protected virtual ModuleHandle GetModuleHandleImpl() => ModuleHandle.EmptyHandle; // Not an api but declared protected because of Reflection.Core/Corelib divide (when built by CoreRt)
+ public virtual void GetPEKind(out PortableExecutableKinds peKind, out ImageFileMachine machine) { throw NotImplemented.ByDesign; }
+ public virtual bool IsResource() { throw NotImplemented.ByDesign; }
+
+ public virtual bool IsDefined(Type attributeType, bool inherit) { throw NotImplemented.ByDesign; }
+ public virtual IEnumerable<CustomAttributeData> CustomAttributes => GetCustomAttributesData();
+ public virtual IList<CustomAttributeData> GetCustomAttributesData() { throw NotImplemented.ByDesign; }
+ public virtual object[] GetCustomAttributes(bool inherit) { throw NotImplemented.ByDesign; }
+ public virtual object[] GetCustomAttributes(Type attributeType, bool inherit) { throw NotImplemented.ByDesign; }
+
+ public MethodInfo GetMethod(string name)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+
+ return GetMethodImpl(name, Module.DefaultLookup, null, CallingConventions.Any, null, null);
+ }
+
+ public MethodInfo GetMethod(string name, Type[] types) => GetMethod(name, Module.DefaultLookup, null, CallingConventions.Any, types, null);
+ public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ if (types == null)
+ throw new ArgumentNullException(nameof(types));
+ for (int i = 0; i < types.Length; i++)
+ {
+ if (types[i] == null)
+ throw new ArgumentNullException(nameof(types));
+ }
+ return GetMethodImpl(name, bindingAttr, binder, callConvention, types, modifiers);
+ }
+
+ protected virtual MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) { throw NotImplemented.ByDesign; }
+
+ public MethodInfo[] GetMethods() => GetMethods(Module.DefaultLookup);
+ public virtual MethodInfo[] GetMethods(BindingFlags bindingFlags) { throw NotImplemented.ByDesign; }
+
+ public FieldInfo GetField(string name) => GetField(name, Module.DefaultLookup);
+ public virtual FieldInfo GetField(string name, BindingFlags bindingAttr) { throw NotImplemented.ByDesign; }
+
+ public FieldInfo[] GetFields() => GetFields(Module.DefaultLookup);
+ public virtual FieldInfo[] GetFields(BindingFlags bindingFlags) { throw NotImplemented.ByDesign; }
+
+ public virtual Type[] GetTypes() { throw NotImplemented.ByDesign; }
+
+ public virtual Type GetType(string className) => GetType(className, throwOnError: false, ignoreCase: false);
+ public virtual Type GetType(string className, bool ignoreCase) => GetType(className, throwOnError: false, ignoreCase: ignoreCase);
+ public virtual Type GetType(string className, bool throwOnError, bool ignoreCase) { throw NotImplemented.ByDesign; }
+
+ public virtual Type[] FindTypes(TypeFilter filter, object filterCriteria)
+ {
+ Type[] c = GetTypes();
+ int cnt = 0;
+ for (int i = 0; i < c.Length; i++)
+ {
+ if (filter != null && !filter(c[i], filterCriteria))
+ c[i] = null;
+ else
+ cnt++;
+ }
+ if (cnt == c.Length)
+ return c;
+
+ Type[] ret = new Type[cnt];
+ cnt = 0;
+ for (int i = 0; i < c.Length; i++)
+ {
+ if (c[i] != null)
+ ret[cnt++] = c[i];
+ }
+ return ret;
+ }
+
+ public virtual int MetadataToken { get { throw NotImplemented.ByDesign; } }
+
+ public FieldInfo ResolveField(int metadataToken) => ResolveField(metadataToken, null, null);
+ public virtual FieldInfo ResolveField(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments) { throw NotImplemented.ByDesign; }
+
+ public MemberInfo ResolveMember(int metadataToken) => ResolveMember(metadataToken, null, null);
+ public virtual MemberInfo ResolveMember(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments) { throw NotImplemented.ByDesign; }
+
+ public MethodBase ResolveMethod(int metadataToken) => ResolveMethod(metadataToken, null, null);
+ public virtual MethodBase ResolveMethod(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments) { throw NotImplemented.ByDesign; }
+
+ public virtual byte[] ResolveSignature(int metadataToken) { throw NotImplemented.ByDesign; }
+ public virtual string ResolveString(int metadataToken) { throw NotImplemented.ByDesign; }
+
+ public Type ResolveType(int metadataToken) => ResolveType(metadataToken, null, null);
+ public virtual Type ResolveType(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments) { throw NotImplemented.ByDesign; }
+
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { throw NotImplemented.ByDesign; }
+
+ public override bool Equals(object o) => base.Equals(o);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(Module left, Module right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Module left, Module right) => !(left == right);
+
+ public override string ToString() => ScopeName;
+
+ public static readonly TypeFilter FilterTypeName = FilterTypeNameImpl;
+ public static readonly TypeFilter FilterTypeNameIgnoreCase = FilterTypeNameIgnoreCaseImpl;
+
+ private const BindingFlags DefaultLookup = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
+
+ // FilterTypeName
+ // This method will filter the class based upon the name. It supports
+ // a trailing wild card.
+ private static bool FilterTypeNameImpl(Type cls, object filterCriteria)
+ {
+ // Check that the criteria object is a String object
+ if (filterCriteria == null || !(filterCriteria is string))
+ throw new InvalidFilterCriteriaException(SR.InvalidFilterCriteriaException_CritString);
+
+ string str = (string)filterCriteria;
+
+ // Check to see if this is a prefix or exact match requirement
+ if (str.Length > 0 && str[str.Length - 1] == '*')
+ {
+ str = str.Substring(0, str.Length - 1);
+ return cls.Name.StartsWith(str, StringComparison.Ordinal);
+ }
+
+ return cls.Name.Equals(str);
+ }
+
+ // FilterFieldNameIgnoreCase
+ // This method filter the Type based upon name, it ignores case.
+ private static bool FilterTypeNameIgnoreCaseImpl(Type cls, object filterCriteria)
+ {
+ // Check that the criteria object is a String object
+ if (filterCriteria == null || !(filterCriteria is string))
+ throw new InvalidFilterCriteriaException(SR.InvalidFilterCriteriaException_CritString);
+
+ string str = (string)filterCriteria;
+
+ // Check to see if this is a prefix or exact match requirement
+ if (str.Length > 0 && str[str.Length - 1] == '*')
+ {
+ str = str.Substring(0, str.Length - 1);
+ string name = cls.Name;
+ if (name.Length >= str.Length)
+ return (string.Compare(name, 0, str, 0, str.Length, StringComparison.OrdinalIgnoreCase) == 0);
+ else
+ return false;
+ }
+ return (string.Compare(str, cls.Name, StringComparison.OrdinalIgnoreCase) == 0);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ModuleResolveEventHandler.cs b/src/System.Private.CoreLib/shared/System/Reflection/ModuleResolveEventHandler.cs
new file mode 100644
index 0000000000..eb8926b5db
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ModuleResolveEventHandler.cs
@@ -0,0 +1,9 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public delegate Module ModuleResolveEventHandler(object sender, ResolveEventArgs e);
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ObfuscateAssemblyAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/ObfuscateAssemblyAttribute.cs
new file mode 100644
index 0000000000..f8f765ced2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ObfuscateAssemblyAttribute.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
+ public sealed class ObfuscateAssemblyAttribute : Attribute
+ {
+ public ObfuscateAssemblyAttribute(bool assemblyIsPrivate)
+ {
+ AssemblyIsPrivate = assemblyIsPrivate;
+ }
+
+ public bool AssemblyIsPrivate { get; }
+ public bool StripAfterObfuscation { get; set; } = true;
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ObfuscationAttribute.cs b/src/System.Private.CoreLib/shared/System/Reflection/ObfuscationAttribute.cs
new file mode 100644
index 0000000000..11d93b6313
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ObfuscationAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Delegate,
+ AllowMultiple = true, Inherited = false)]
+ public sealed class ObfuscationAttribute : Attribute
+ {
+ public ObfuscationAttribute()
+ {
+ }
+
+ public bool StripAfterObfuscation { get; set; } = true;
+ public bool Exclude { get; set; } = true;
+ public bool ApplyToMembers { get; set; } = true;
+ public string Feature { get; set; } = "all";
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ParameterAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/ParameterAttributes.cs
new file mode 100644
index 0000000000..ce195897c2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ParameterAttributes.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ParameterAttributes is an enum defining the attributes that may be
+// associated with a Parameter. These are defined in CorHdr.h.
+
+namespace System.Reflection
+{
+ // This Enum matchs the CorParamAttr defined in CorHdr.h
+ [Flags]
+ public enum ParameterAttributes
+ {
+ None = 0x0000, // no flag is specified
+ In = 0x0001, // Param is [In]
+ Out = 0x0002, // Param is [Out]
+ Lcid = 0x0004, // Param is [lcid]
+
+ Retval = 0x0008, // Param is [Retval]
+ Optional = 0x0010, // Param is optional
+
+ HasDefault = 0x1000, // Param has default value.
+ HasFieldMarshal = 0x2000, // Param has FieldMarshal.
+
+ Reserved3 = 0x4000,
+ Reserved4 = 0x8000,
+ ReservedMask = 0xf000,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ParameterInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/ParameterInfo.cs
new file mode 100644
index 0000000000..94bfffaa53
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ParameterInfo.cs
@@ -0,0 +1,110 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ public class ParameterInfo : ICustomAttributeProvider, IObjectReference
+ {
+ protected ParameterInfo() { }
+
+ public virtual ParameterAttributes Attributes => AttrsImpl;
+ public virtual MemberInfo Member => MemberImpl;
+ public virtual string Name => NameImpl;
+ public virtual Type ParameterType => ClassImpl;
+ public virtual int Position => PositionImpl;
+
+ public bool IsIn => (Attributes & ParameterAttributes.In) != 0;
+ public bool IsLcid => (Attributes & ParameterAttributes.Lcid) != 0;
+ public bool IsOptional => (Attributes & ParameterAttributes.Optional) != 0;
+ public bool IsOut => (Attributes & ParameterAttributes.Out) != 0;
+ public bool IsRetval => (Attributes & ParameterAttributes.Retval) != 0;
+
+ public virtual object DefaultValue { get { throw NotImplemented.ByDesign; } }
+ public virtual object RawDefaultValue { get { throw NotImplemented.ByDesign; } }
+ public virtual bool HasDefaultValue { get { throw NotImplemented.ByDesign; } }
+
+ public virtual bool IsDefined(Type attributeType, bool inherit)
+ {
+ if (attributeType == null)
+ throw new ArgumentNullException(nameof(attributeType));
+
+ return false;
+ }
+
+ public virtual IEnumerable<CustomAttributeData> CustomAttributes => GetCustomAttributesData();
+ public virtual IList<CustomAttributeData> GetCustomAttributesData() { throw NotImplemented.ByDesign; }
+
+ public virtual object[] GetCustomAttributes(bool inherit) => Array.Empty<object>();
+ public virtual object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ if (attributeType == null)
+ throw new ArgumentNullException(nameof(attributeType));
+
+ return Array.Empty<object>();
+ }
+
+ public virtual Type[] GetOptionalCustomModifiers() => Array.Empty<Type>();
+ public virtual Type[] GetRequiredCustomModifiers() => Array.Empty<Type>();
+
+ public virtual int MetadataToken => MetadataToken_ParamDef;
+
+ public object GetRealObject(StreamingContext context)
+ {
+ // Once all the serializable fields have come in we can set up the real
+ // instance based on just two of them (MemberImpl and PositionImpl).
+
+ if (MemberImpl == null)
+ throw new SerializationException(SR.Serialization_InsufficientState);
+
+ ParameterInfo[] args = null;
+
+ switch (MemberImpl.MemberType)
+ {
+ case MemberTypes.Constructor:
+ case MemberTypes.Method:
+ if (PositionImpl == -1)
+ {
+ if (MemberImpl.MemberType == MemberTypes.Method)
+ return ((MethodInfo)MemberImpl).ReturnParameter;
+ else
+ throw new SerializationException(SR.Serialization_BadParameterInfo);
+ }
+ else
+ {
+ args = ((MethodBase)MemberImpl).GetParametersNoCopy();
+
+ if (args != null && PositionImpl < args.Length)
+ return args[PositionImpl];
+ else
+ throw new SerializationException(SR.Serialization_BadParameterInfo);
+ }
+
+ case MemberTypes.Property:
+ args = ((PropertyInfo)MemberImpl).GetIndexParameters();
+
+ if (args != null && PositionImpl > -1 && PositionImpl < args.Length)
+ return args[PositionImpl];
+ else
+ throw new SerializationException(SR.Serialization_BadParameterInfo);
+
+ default:
+ throw new SerializationException(SR.Serialization_NoParameterInfo);
+ }
+ }
+
+ public override string ToString() => ParameterType.FormatTypeName() + " " + Name;
+
+ protected ParameterAttributes AttrsImpl;
+ protected Type ClassImpl;
+ protected object DefaultValueImpl;
+ protected MemberInfo MemberImpl;
+ protected string NameImpl;
+ protected int PositionImpl;
+
+ private const int MetadataToken_ParamDef = 0x08000000;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ParameterModifier.cs b/src/System.Private.CoreLib/shared/System/Reflection/ParameterModifier.cs
new file mode 100644
index 0000000000..0fb75ffbd8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ParameterModifier.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public readonly struct ParameterModifier
+ {
+ private readonly bool[] _byRef;
+
+ public ParameterModifier(int parameterCount)
+ {
+ if (parameterCount <= 0)
+ throw new ArgumentException(SR.Arg_ParmArraySize);
+
+ _byRef = new bool[parameterCount];
+ }
+
+ public bool this[int index]
+ {
+ get
+ {
+ return _byRef[index];
+ }
+ set
+ {
+ _byRef[index] = value;
+ }
+ }
+
+#if CORECLR
+ internal bool[] IsByRefArray => _byRef;
+#endif
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/Pointer.cs b/src/System.Private.CoreLib/shared/System/Reflection/Pointer.cs
new file mode 100644
index 0000000000..e1a9990cf0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/Pointer.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ [CLSCompliant(false)]
+ public sealed unsafe class Pointer : ISerializable
+ {
+ // CoreCLR: Do not add or remove fields without updating the ReflectionPointer class in runtimehandles.h
+ private readonly void* _ptr;
+ private readonly Type _ptrType;
+
+ private Pointer(void* ptr, Type ptrType)
+ {
+ Debug.Assert(ptrType.IsRuntimeImplemented()); // CoreCLR: For CoreRT's sake, _ptrType has to be declared as "Type", but in fact, it is always a RuntimeType. Code on CoreCLR expects this.
+ _ptr = ptr;
+ _ptrType = ptrType;
+ }
+
+ public static object Box(void* ptr, Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException(nameof(type));
+ if (!type.IsPointer)
+ throw new ArgumentException(SR.Arg_MustBePointer, nameof(ptr));
+ if (!type.IsRuntimeImplemented())
+ throw new ArgumentException(SR.Arg_MustBeType, nameof(ptr));
+
+ return new Pointer(ptr, type);
+ }
+
+ public static void* Unbox(object ptr)
+ {
+ if (!(ptr is Pointer))
+ throw new ArgumentException(SR.Arg_MustBePointer, nameof(ptr));
+ return ((Pointer)ptr)._ptr;
+ }
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ internal Type GetPointerType() => _ptrType;
+ internal IntPtr GetPointerValue() => (IntPtr)_ptr;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/PortableExecutableKinds.cs b/src/System.Private.CoreLib/shared/System/Reflection/PortableExecutableKinds.cs
new file mode 100644
index 0000000000..79be338685
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/PortableExecutableKinds.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum PortableExecutableKinds
+ {
+ NotAPortableExecutableImage = 0x0,
+ ILOnly = 0x1,
+ Required32Bit = 0x2,
+ PE32Plus = 0x4,
+ Unmanaged32Bit = 0x8,
+ Preferred32Bit = 0x10,
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ProcessorArchitecture.cs b/src/System.Private.CoreLib/shared/System/Reflection/ProcessorArchitecture.cs
new file mode 100644
index 0000000000..becb346c4f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ProcessorArchitecture.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public enum ProcessorArchitecture
+ {
+ None = 0x0000,
+ MSIL = 0x0001,
+ X86 = 0x0002,
+ IA64 = 0x0003,
+ Amd64 = 0x0004,
+ Arm = 0x0005
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/PropertyAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/PropertyAttributes.cs
new file mode 100644
index 0000000000..31e7a653bb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/PropertyAttributes.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// PropertyAttributes is an enum which defines the attributes that may be associated
+// with a property. The values here are defined in Corhdr.h.
+
+namespace System.Reflection
+{
+ // This Enum matchs the CorPropertyAttr defined in CorHdr.h
+ [Flags]
+ public enum PropertyAttributes
+ {
+ None = 0x0000,
+ SpecialName = 0x0200, // property is special. Name describes how.
+
+ RTSpecialName = 0x0400, // Runtime(metadata internal APIs) should check name encoding.
+ HasDefault = 0x1000, // Property has default
+
+ Reserved2 = 0x2000,
+ Reserved3 = 0x4000,
+ Reserved4 = 0x8000,
+ ReservedMask = 0xf400,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/PropertyInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/PropertyInfo.cs
new file mode 100644
index 0000000000..ff8a02e96d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/PropertyInfo.cs
@@ -0,0 +1,74 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+
+namespace System.Reflection
+{
+ public abstract class PropertyInfo : MemberInfo
+ {
+ protected PropertyInfo() { }
+
+ public override MemberTypes MemberType => MemberTypes.Property;
+
+ public abstract Type PropertyType { get; }
+ public abstract ParameterInfo[] GetIndexParameters();
+
+ public abstract PropertyAttributes Attributes { get; }
+ public bool IsSpecialName => (Attributes & PropertyAttributes.SpecialName) != 0;
+
+ public abstract bool CanRead { get; }
+ public abstract bool CanWrite { get; }
+
+ public MethodInfo[] GetAccessors() => GetAccessors(nonPublic: false);
+ public abstract MethodInfo[] GetAccessors(bool nonPublic);
+
+ public virtual MethodInfo GetMethod => GetGetMethod(nonPublic: true);
+ public MethodInfo GetGetMethod() => GetGetMethod(nonPublic: false);
+ public abstract MethodInfo GetGetMethod(bool nonPublic);
+
+ public virtual MethodInfo SetMethod => GetSetMethod(nonPublic: true);
+ public MethodInfo GetSetMethod() => GetSetMethod(nonPublic: false);
+ public abstract MethodInfo GetSetMethod(bool nonPublic);
+
+ public virtual Type[] GetOptionalCustomModifiers() => Array.Empty<Type>();
+ public virtual Type[] GetRequiredCustomModifiers() => Array.Empty<Type>();
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public object GetValue(object obj) => GetValue(obj, index: null);
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public virtual object GetValue(object obj, object[] index) => GetValue(obj, BindingFlags.Default, binder: null, index: index, culture: null);
+ public abstract object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture);
+
+ public virtual object GetConstantValue() { throw NotImplemented.ByDesign; }
+ public virtual object GetRawConstantValue() { throw NotImplemented.ByDesign; }
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public void SetValue(object obj, object value) => SetValue(obj, value, index: null);
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public virtual void SetValue(object obj, object value, object[] index) => SetValue(obj, value, BindingFlags.Default, binder: null, index: index, culture: null);
+ public abstract void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture);
+
+ public override bool Equals(object obj) => base.Equals(obj);
+ public override int GetHashCode() => base.GetHashCode();
+
+ public static bool operator ==(PropertyInfo left, PropertyInfo right)
+ {
+ if (object.ReferenceEquals(left, right))
+ return true;
+
+ if ((object)left == null || (object)right == null)
+ return false;
+
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(PropertyInfo left, PropertyInfo right) => !(left == right);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ReflectionContext.cs b/src/System.Private.CoreLib/shared/System/Reflection/ReflectionContext.cs
new file mode 100644
index 0000000000..e9e93dab81
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ReflectionContext.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public abstract class ReflectionContext
+ {
+ protected ReflectionContext() { }
+
+ public abstract Assembly MapAssembly(Assembly assembly);
+
+ public abstract TypeInfo MapType(TypeInfo type);
+
+ public virtual TypeInfo GetTypeForObject(object value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ return MapType(value.GetType().GetTypeInfo());
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ReflectionTypeLoadException.cs b/src/System.Private.CoreLib/shared/System/Reflection/ReflectionTypeLoadException.cs
new file mode 100644
index 0000000000..5011c50053
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ReflectionTypeLoadException.cs
@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace System.Reflection
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class ReflectionTypeLoadException : SystemException, ISerializable
+ {
+ public ReflectionTypeLoadException(Type[] classes, Exception[] exceptions)
+ : base(null)
+ {
+ Types = classes;
+ LoaderExceptions = exceptions;
+ HResult = HResults.COR_E_REFLECTIONTYPELOAD;
+ }
+
+ public ReflectionTypeLoadException(Type[] classes, Exception[] exceptions, string message)
+ : base(message)
+ {
+ Types = classes;
+ LoaderExceptions = exceptions;
+ HResult = HResults.COR_E_REFLECTIONTYPELOAD;
+ }
+
+ private ReflectionTypeLoadException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ LoaderExceptions = (Exception[])(info.GetValue("Exceptions", typeof(Exception[])));
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("Types", null, typeof(Type[]));
+ info.AddValue("Exceptions", LoaderExceptions, typeof(Exception[]));
+ }
+
+ public Type[] Types { get; }
+
+ public Exception[] LoaderExceptions { get; }
+
+ public override string Message => CreateString(isMessage: true);
+
+ public override string ToString() => CreateString(isMessage: false);
+
+ private string CreateString(bool isMessage)
+ {
+ string baseValue = isMessage ? base.Message : base.ToString();
+
+ Exception[] exceptions = LoaderExceptions;
+ if (exceptions == null || exceptions.Length == 0)
+ {
+ return baseValue;
+ }
+
+ var text = new StringBuilder(baseValue);
+ foreach (Exception e in exceptions)
+ {
+ if (e != null)
+ {
+ text.AppendLine();
+ text.Append(isMessage ? e.Message : e.ToString());
+ }
+ }
+ return text.ToString();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ResourceAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/ResourceAttributes.cs
new file mode 100644
index 0000000000..2d03f42ba0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ResourceAttributes.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum ResourceAttributes
+ {
+ Public = 0x0001,
+ Private = 0x0002,
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/ResourceLocation.cs b/src/System.Private.CoreLib/shared/System/Reflection/ResourceLocation.cs
new file mode 100644
index 0000000000..4902333ac0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/ResourceLocation.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ [Flags]
+ public enum ResourceLocation
+ {
+ ContainedInAnotherAssembly = 2,
+ ContainedInManifestFile = 4,
+ Embedded = 1,
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignatureArrayType.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignatureArrayType.cs
new file mode 100644
index 0000000000..52011b8a9d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignatureArrayType.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Reflection
+{
+ internal sealed class SignatureArrayType : SignatureHasElementType
+ {
+ internal SignatureArrayType(SignatureType elementType, int rank, bool isMultiDim)
+ : base(elementType)
+ {
+ Debug.Assert(rank > 0);
+ Debug.Assert(rank == 1 || isMultiDim);
+
+ _rank = rank;
+ _isMultiDim = isMultiDim;
+ }
+
+ protected sealed override bool IsArrayImpl() => true;
+ protected sealed override bool IsByRefImpl() => false;
+ protected sealed override bool IsPointerImpl() => false;
+
+ public sealed override bool IsSZArray => !_isMultiDim;
+ public sealed override bool IsVariableBoundArray => _isMultiDim;
+
+ public sealed override int GetArrayRank() => _rank;
+
+ protected sealed override string Suffix
+ {
+ get
+ {
+ if (!_isMultiDim)
+ return "[]";
+ else if (_rank == 1)
+ return "[*]";
+ else
+ return "[" + new string(',', _rank - 1) + "]";
+ }
+ }
+
+ private readonly int _rank;
+ private readonly bool _isMultiDim;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignatureByRefType.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignatureByRefType.cs
new file mode 100644
index 0000000000..eb5f6de42e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignatureByRefType.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Reflection
+{
+ internal sealed class SignatureByRefType : SignatureHasElementType
+ {
+ internal SignatureByRefType(SignatureType elementType)
+ : base(elementType)
+ {
+ }
+
+ protected sealed override bool IsArrayImpl() => false;
+ protected sealed override bool IsByRefImpl() => true;
+ protected sealed override bool IsPointerImpl() => false;
+
+ public sealed override bool IsSZArray => false;
+ public sealed override bool IsVariableBoundArray => false;
+
+ public sealed override int GetArrayRank() => throw new ArgumentException(SR.Argument_HasToBeArrayClass);
+
+ protected sealed override string Suffix => "&";
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignatureConstructedGenericType.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignatureConstructedGenericType.cs
new file mode 100644
index 0000000000..cd97ffa21b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignatureConstructedGenericType.cs
@@ -0,0 +1,74 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+using System.Diagnostics;
+
+namespace System.Reflection
+{
+ internal sealed class SignatureConstructedGenericType : SignatureType
+ {
+ internal SignatureConstructedGenericType(Type genericTypeDefinition, Type[] genericTypeArguments)
+ {
+ Debug.Assert(genericTypeDefinition != null && genericTypeArguments != null);
+ _genericTypeDefinition = genericTypeDefinition;
+ _genericTypeArguments = (Type[])(genericTypeArguments.Clone());
+ }
+
+ public sealed override bool IsTypeDefinition => false;
+ public sealed override bool IsGenericTypeDefinition => false;
+ protected sealed override bool HasElementTypeImpl() => false;
+ protected sealed override bool IsArrayImpl() => false;
+ protected sealed override bool IsByRefImpl() => false;
+ public sealed override bool IsByRefLike => _genericTypeDefinition.IsByRefLike;
+ protected sealed override bool IsPointerImpl() => false;
+ public sealed override bool IsSZArray => false;
+ public sealed override bool IsVariableBoundArray => false;
+ public sealed override bool IsConstructedGenericType => true;
+ public sealed override bool IsGenericParameter => false;
+ public sealed override bool IsGenericTypeParameter => false;
+ public sealed override bool IsGenericMethodParameter => false;
+ public sealed override bool ContainsGenericParameters
+ {
+ get
+ {
+ for (int i = 0; i < _genericTypeArguments.Length; i++)
+ {
+ if (_genericTypeArguments[i].ContainsGenericParameters)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ internal sealed override SignatureType ElementType => null;
+ public sealed override int GetArrayRank() => throw new ArgumentException(SR.Argument_HasToBeArrayClass);
+ public sealed override Type GetGenericTypeDefinition() => _genericTypeDefinition;
+ public sealed override Type[] GetGenericArguments() => GenericTypeArguments;
+ public sealed override Type[] GenericTypeArguments => (Type[])(_genericTypeArguments.Clone());
+ public sealed override int GenericParameterPosition => throw new InvalidOperationException(SR.Arg_NotGenericParameter);
+
+ public sealed override string Name => _genericTypeDefinition.Name;
+ public sealed override string Namespace => _genericTypeDefinition.Namespace;
+
+ public sealed override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(_genericTypeDefinition.ToString());
+ sb.Append('[');
+ for (int i = 0; i < _genericTypeArguments.Length; i++)
+ {
+ if (i != 0)
+ sb.Append(',');
+ sb.Append(_genericTypeArguments[i].ToString());
+ }
+ sb.Append(']');
+ return sb.ToString();
+ }
+
+ private readonly Type _genericTypeDefinition;
+ private readonly Type[] _genericTypeArguments;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignatureGenericMethodParameterType.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignatureGenericMethodParameterType.cs
new file mode 100644
index 0000000000..d0790283fb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignatureGenericMethodParameterType.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ internal sealed class SignatureGenericMethodParameterType : SignatureGenericParameterType
+ {
+ internal SignatureGenericMethodParameterType(int position)
+ : base(position)
+ {
+ }
+
+ public sealed override bool IsGenericTypeParameter => false;
+ public sealed override bool IsGenericMethodParameter => true;
+
+ public sealed override string Name => "!!" + GenericParameterPosition;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignatureGenericParameterType.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignatureGenericParameterType.cs
new file mode 100644
index 0000000000..fee7bce353
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignatureGenericParameterType.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+
+namespace System.Reflection
+{
+ internal abstract class SignatureGenericParameterType : SignatureType
+ {
+ protected SignatureGenericParameterType(int position)
+ {
+ Debug.Assert(position >= 0);
+ _position = position;
+ }
+
+ public sealed override bool IsTypeDefinition => false;
+ public sealed override bool IsGenericTypeDefinition => false;
+ protected sealed override bool HasElementTypeImpl() => false;
+ protected sealed override bool IsArrayImpl() => false;
+ protected sealed override bool IsByRefImpl() => false;
+ public sealed override bool IsByRefLike => false;
+ protected sealed override bool IsPointerImpl() => false;
+ public sealed override bool IsSZArray => false;
+ public sealed override bool IsVariableBoundArray => false;
+ public sealed override bool IsConstructedGenericType => false;
+ public sealed override bool IsGenericParameter => true;
+ public abstract override bool IsGenericMethodParameter { get; }
+ public sealed override bool ContainsGenericParameters => true;
+
+ internal sealed override SignatureType ElementType => null;
+ public sealed override int GetArrayRank() => throw new ArgumentException(SR.Argument_HasToBeArrayClass);
+ public sealed override Type GetGenericTypeDefinition() => throw new InvalidOperationException(SR.InvalidOperation_NotGenericType);
+ public sealed override Type[] GetGenericArguments() => Array.Empty<Type>();
+ public sealed override Type[] GenericTypeArguments => Array.Empty<Type>();
+ public sealed override int GenericParameterPosition => _position;
+
+ public abstract override string Name { get; }
+ public sealed override string Namespace => null;
+
+ public sealed override string ToString() => Name;
+
+ private readonly int _position;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignatureHasElementType.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignatureHasElementType.cs
new file mode 100644
index 0000000000..e74e5f5aa2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignatureHasElementType.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+
+namespace System.Reflection
+{
+ internal abstract class SignatureHasElementType : SignatureType
+ {
+ protected SignatureHasElementType(SignatureType elementType)
+ {
+ Debug.Assert(elementType != null);
+ _elementType = elementType;
+ }
+
+ public sealed override bool IsTypeDefinition => false;
+ public sealed override bool IsGenericTypeDefinition => false;
+ protected sealed override bool HasElementTypeImpl() => true;
+ protected abstract override bool IsArrayImpl();
+ protected abstract override bool IsByRefImpl();
+ public sealed override bool IsByRefLike => false;
+ protected abstract override bool IsPointerImpl();
+ public abstract override bool IsSZArray { get; }
+ public abstract override bool IsVariableBoundArray { get; }
+ public sealed override bool IsConstructedGenericType => false;
+ public sealed override bool IsGenericParameter => false;
+ public sealed override bool IsGenericTypeParameter => false;
+ public sealed override bool IsGenericMethodParameter => false;
+ public sealed override bool ContainsGenericParameters => _elementType.ContainsGenericParameters;
+
+ internal sealed override SignatureType ElementType => _elementType;
+ public abstract override int GetArrayRank();
+ public sealed override Type GetGenericTypeDefinition() => throw new InvalidOperationException(SR.InvalidOperation_NotGenericType);
+ public sealed override Type[] GetGenericArguments() => Array.Empty<Type>();
+ public sealed override Type[] GenericTypeArguments => Array.Empty<Type>();
+ public sealed override int GenericParameterPosition => throw new InvalidOperationException(SR.Arg_NotGenericParameter);
+
+ public sealed override string Name => _elementType.Name + Suffix;
+ public sealed override string Namespace => _elementType.Namespace;
+
+ public sealed override string ToString() => _elementType.ToString() + Suffix;
+
+ protected abstract string Suffix { get; }
+
+ private readonly SignatureType _elementType;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignaturePointerType.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignaturePointerType.cs
new file mode 100644
index 0000000000..a75a208165
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignaturePointerType.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Reflection
+{
+ internal sealed class SignaturePointerType : SignatureHasElementType
+ {
+ internal SignaturePointerType(SignatureType elementType)
+ : base(elementType)
+ {
+ }
+
+ protected sealed override bool IsArrayImpl() => false;
+ protected sealed override bool IsByRefImpl() => false;
+ protected sealed override bool IsPointerImpl() => true;
+
+ public sealed override bool IsSZArray => false;
+ public sealed override bool IsVariableBoundArray => false;
+
+ public sealed override int GetArrayRank() => throw new ArgumentException(SR.Argument_HasToBeArrayClass);
+
+ protected sealed override string Suffix => "*";
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignatureType.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignatureType.cs
new file mode 100644
index 0000000000..40a0590448
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignatureType.cs
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace System.Reflection
+{
+ //
+ // Signature Types are highly restricted Type objects that can be passed in the Type[] parameter to Type.GetMethod(). Their primary
+ // use is to pass types representing generic parameters defined by the method, or types composed from them. Passing in the normal
+ // generic parameter Type obtained from MethodInfo.GetGenericArguments() is usually impractical (if you had the method, you wouldn't
+ // be looking for it!)
+ //
+ internal abstract class SignatureType : Type
+ {
+ public sealed override bool IsSignatureType => true;
+
+ // Type flavor predicates
+ public abstract override bool IsTypeDefinition { get; }
+ protected abstract override bool HasElementTypeImpl();
+ protected abstract override bool IsArrayImpl();
+ public abstract override bool IsSZArray { get; }
+ public abstract override bool IsVariableBoundArray { get; }
+ protected abstract override bool IsByRefImpl();
+ public abstract override bool IsByRefLike { get; }
+ protected abstract override bool IsPointerImpl();
+ public sealed override bool IsGenericType => IsGenericTypeDefinition || IsConstructedGenericType;
+ public abstract override bool IsGenericTypeDefinition { get; }
+ public abstract override bool IsConstructedGenericType { get; }
+ public abstract override bool IsGenericParameter { get; }
+ public abstract override bool IsGenericTypeParameter { get; }
+ public abstract override bool IsGenericMethodParameter { get; }
+ public abstract override bool ContainsGenericParameters { get; }
+ public sealed override MemberTypes MemberType => MemberTypes.TypeInfo;
+
+ // Compositors
+ public sealed override Type MakeArrayType() => new SignatureArrayType(this, rank: 1, isMultiDim: false);
+ public sealed override Type MakeArrayType(int rank)
+ {
+ if (rank <= 0)
+ throw new IndexOutOfRangeException();
+ return new SignatureArrayType(this, rank: rank, isMultiDim: true);
+ }
+ public sealed override Type MakeByRefType() => new SignatureByRefType(this);
+ public sealed override Type MakePointerType() => new SignaturePointerType(this);
+ public sealed override Type MakeGenericType(params Type[] typeArguments) => throw new NotSupportedException(SR.NotSupported_SignatureType); // There is no SignatureType for type definition types so it would never be legal to call this.
+
+ // Dissectors
+ public sealed override Type GetElementType() => ElementType;
+ public abstract override int GetArrayRank();
+ public abstract override Type GetGenericTypeDefinition();
+ public abstract override Type[] GenericTypeArguments { get; }
+ public abstract override Type[] GetGenericArguments();
+ public abstract override int GenericParameterPosition { get; }
+ internal abstract SignatureType ElementType { get; }
+
+ // Identity
+#if DEBUG
+ public sealed override bool Equals(object o) => base.Equals(o);
+ public sealed override bool Equals(Type o) => base.Equals(o);
+ public sealed override int GetHashCode() => base.GetHashCode();
+#endif
+ public sealed override Type UnderlyingSystemType => this; // Equals(Type) depends on this.
+
+ // Naming and diagnostics
+ public abstract override string Name { get; }
+ public abstract override string Namespace { get; }
+ public sealed override string FullName => null;
+ public sealed override string AssemblyQualifiedName => null;
+ public abstract override string ToString();
+
+ // Not supported on Signature Types
+ public sealed override Assembly Assembly => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Module Module => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type ReflectedType => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type BaseType => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type[] GetInterfaces() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsAssignableFrom(Type c) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override int MetadataToken => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type DeclaringType => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override MethodBase DeclaringMethod => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type[] GetGenericParameterConstraints() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override GenericParameterAttributes GenericParameterAttributes => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsEnumDefined(object value) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override string GetEnumName(object value) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override string[] GetEnumNames() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type GetEnumUnderlyingType() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Array GetEnumValues() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Guid GUID => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override TypeCode GetTypeCodeImpl() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override TypeAttributes GetAttributeFlagsImpl() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override EventInfo GetEvent(string name, BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override EventInfo[] GetEvents(BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override FieldInfo GetField(string name, BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override FieldInfo[] GetFields(BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override MemberInfo[] GetMembers(BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override MethodInfo[] GetMethods(BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type GetNestedType(string name, BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type[] GetNestedTypes(BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override PropertyInfo[] GetProperties(BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override MethodInfo GetMethodImpl(string name, int genericParameterCount, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override MemberInfo[] FindMembers(MemberTypes memberType, BindingFlags bindingAttr, MemberFilter filter, object filterCriteria) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override MemberInfo[] GetMember(string name, BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override MemberInfo[] GetMember(string name, MemberTypes type, BindingFlags bindingAttr) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override MemberInfo[] GetDefaultMembers() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override EventInfo[] GetEvents() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override object[] GetCustomAttributes(bool inherit) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override object[] GetCustomAttributes(Type attributeType, bool inherit) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsDefined(Type attributeType, bool inherit) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override IList<CustomAttributeData> GetCustomAttributesData() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type GetInterface(string name, bool ignoreCase) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override bool IsCOMObjectImpl() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override bool IsPrimitiveImpl() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override IEnumerable<CustomAttributeData> CustomAttributes => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override Type[] FindInterfaces(TypeFilter filter, object filterCriteria) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override InterfaceMapping GetInterfaceMap(Type interfaceType) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override bool IsContextfulImpl() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsEnum => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsEquivalentTo(Type other) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsInstanceOfType(object o) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override bool IsMarshalByRefImpl() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsSecurityCritical => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsSecuritySafeCritical => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsSecurityTransparent => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsSerializable => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override bool IsSubclassOf(Type c) => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ protected sealed override bool IsValueTypeImpl() => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override StructLayoutAttribute StructLayoutAttribute => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ public sealed override RuntimeTypeHandle TypeHandle => throw new NotSupportedException(SR.NotSupported_SignatureType);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/SignatureTypeExtensions.cs b/src/System.Private.CoreLib/shared/System/Reflection/SignatureTypeExtensions.cs
new file mode 100644
index 0000000000..9247132546
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/SignatureTypeExtensions.cs
@@ -0,0 +1,228 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Reflection;
+using System.Diagnostics;
+
+namespace System.Reflection
+{
+#if CORERT
+ [System.Runtime.CompilerServices.ReflectionBlocked]
+ public // Needs to be public so that Reflection.Core can see it.
+#else
+ internal
+#endif
+ static class SignatureTypeExtensions
+ {
+ /// <summary>
+ /// This is semantically identical to
+ ///
+ /// parameter.ParameterType == pattern.TryResolveAgainstGenericMethod(parameter.Member)
+ ///
+ /// but without the allocation overhead of TryResolve.
+ /// </summary>
+ public static bool MatchesParameterTypeExactly(this Type pattern, ParameterInfo parameter)
+ {
+ if (pattern is SignatureType signatureType)
+ return signatureType.MatchesExactly(parameter.ParameterType);
+ else
+ return pattern == (object)(parameter.ParameterType);
+ }
+
+ /// <summary>
+ /// This is semantically identical to
+ ///
+ /// actual == pattern.TryResolveAgainstGenericMethod(parameterMember)
+ ///
+ /// but without the allocation overhead of TryResolve.
+ /// </summary>
+ internal static bool MatchesExactly(this SignatureType pattern, Type actual)
+ {
+ if (pattern.IsSZArray)
+ {
+ return actual.IsSZArray && pattern.ElementType.MatchesExactly(actual.GetElementType());
+ }
+ else if (pattern.IsVariableBoundArray)
+ {
+ return actual.IsVariableBoundArray && pattern.GetArrayRank() == actual.GetArrayRank() && pattern.ElementType.MatchesExactly(actual.GetElementType());
+ }
+ else if (pattern.IsByRef)
+ {
+ return actual.IsByRef && pattern.ElementType.MatchesExactly(actual.GetElementType());
+ }
+ else if (pattern.IsPointer)
+ {
+ return actual.IsPointer && pattern.ElementType.MatchesExactly(actual.GetElementType());
+ }
+ else if (pattern.IsConstructedGenericType)
+ {
+ if (!actual.IsConstructedGenericType)
+ return false;
+ if (!(pattern.GetGenericTypeDefinition() == actual.GetGenericTypeDefinition()))
+ return false;
+ Type[] patternGenericTypeArguments = pattern.GenericTypeArguments;
+ Type[] actualGenericTypeArguments = actual.GenericTypeArguments;
+ int count = patternGenericTypeArguments.Length;
+ if (count != actualGenericTypeArguments.Length)
+ return false;
+ for (int i = 0; i < count; i++)
+ {
+ Type patternGenericTypeArgument = patternGenericTypeArguments[i];
+ if (patternGenericTypeArgument is SignatureType signatureType)
+ {
+ if (!signatureType.MatchesExactly(actualGenericTypeArguments[i]))
+ return false;
+ }
+ else
+ {
+ if (patternGenericTypeArgument != actualGenericTypeArguments[i])
+ return false;
+ }
+ }
+ return true;
+ }
+ else if (pattern.IsGenericMethodParameter)
+ {
+ if (!actual.IsGenericMethodParameter)
+ return false;
+ if (pattern.GenericParameterPosition != actual.GenericParameterPosition)
+ return false;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Translates a SignatureType into its equivalent resolved Type by recursively substituting all generic parameter references
+ /// with its corresponding generic parameter definition. This is slow so MatchesExactly or MatchesParameterTypeExactly should be
+ /// substituted instead whenever possible. This is only used by the DefaultBinder when its fast-path checks have been exhausted and
+ /// it needs to call non-trivial methods like IsAssignableFrom which SignatureTypes will never support.
+ ///
+ /// Because this method is used to eliminate method candidates in a GetMethod() lookup, it is entirely possible that the Type
+ /// might not be creatable due to conflicting generic constraints. Since this merely implies that this candidate is not
+ /// the method we're looking for, we return null rather than let the TypeLoadException bubble up. The DefaultBinder will catch
+ /// the null and continue its search for a better candidate.
+ /// </summary>
+ internal static Type TryResolveAgainstGenericMethod(this SignatureType signatureType, MethodInfo genericMethod)
+ {
+ return signatureType.TryResolve(genericMethod.GetGenericArguments());
+ }
+
+ private static Type TryResolve(this SignatureType signatureType, Type[] genericMethodParameters)
+ {
+ if (signatureType.IsSZArray)
+ {
+ return signatureType.ElementType.TryResolve(genericMethodParameters)?.TryMakeArrayType();
+ }
+ else if (signatureType.IsVariableBoundArray)
+ {
+ return signatureType.ElementType.TryResolve(genericMethodParameters)?.TryMakeArrayType(signatureType.GetArrayRank());
+ }
+ else if (signatureType.IsByRef)
+ {
+ return signatureType.ElementType.TryResolve(genericMethodParameters)?.TryMakeByRefType();
+ }
+ else if (signatureType.IsPointer)
+ {
+ return signatureType.ElementType.TryResolve(genericMethodParameters)?.TryMakePointerType();
+ }
+ else if (signatureType.IsConstructedGenericType)
+ {
+ Type[] genericTypeArguments = signatureType.GenericTypeArguments;
+ int count = genericTypeArguments.Length;
+ Type[] newGenericTypeArguments = new Type[count];
+ for (int i = 0; i < count; i++)
+ {
+ Type genericTypeArgument = genericTypeArguments[i];
+ if (genericTypeArgument is SignatureType signatureGenericTypeArgument)
+ {
+ newGenericTypeArguments[i] = signatureGenericTypeArgument.TryResolve(genericMethodParameters);
+ if (newGenericTypeArguments[i] == null)
+ return null;
+ }
+ else
+ {
+ newGenericTypeArguments[i] = genericTypeArgument;
+ }
+ }
+ return signatureType.GetGenericTypeDefinition().TryMakeGenericType(newGenericTypeArguments);
+ }
+ else if (signatureType.IsGenericMethodParameter)
+ {
+ int position = signatureType.GenericParameterPosition;
+ if (position >= genericMethodParameters.Length)
+ return null;
+ return genericMethodParameters[position];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private static Type TryMakeArrayType(this Type type)
+ {
+ try
+ {
+ return type.MakeArrayType();
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private static Type TryMakeArrayType(this Type type, int rank)
+ {
+ try
+ {
+ return type.MakeArrayType(rank);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private static Type TryMakeByRefType(this Type type)
+ {
+ try
+ {
+ return type.MakeByRefType();
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private static Type TryMakePointerType(this Type type)
+ {
+ try
+ {
+ return type.MakePointerType();
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private static Type TryMakeGenericType(this Type type, Type[] instantiation)
+ {
+ try
+ {
+ return type.MakeGenericType(instantiation);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/StrongNameKeyPair.cs b/src/System.Private.CoreLib/shared/System/Reflection/StrongNameKeyPair.cs
new file mode 100644
index 0000000000..a0ba97f835
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/StrongNameKeyPair.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ public class StrongNameKeyPair : IDeserializationCallback, ISerializable
+ {
+ // Build key pair from file.
+ public StrongNameKeyPair(FileStream keyPairFile)
+ {
+ if (keyPairFile == null)
+ throw new ArgumentNullException(nameof(keyPairFile));
+
+ int length = (int)keyPairFile.Length;
+ byte[] keyPairArray = new byte[length];
+ keyPairFile.Read(keyPairArray, 0, length);
+ }
+
+ // Build key pair from byte array in memory.
+ public StrongNameKeyPair(byte[] keyPairArray)
+ {
+ if (keyPairArray == null)
+ throw new ArgumentNullException(nameof(keyPairArray));
+ }
+
+ protected StrongNameKeyPair(SerializationInfo info, StreamingContext context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ public StrongNameKeyPair(string keyPairContainer)
+ {
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_StrongNameSigning);
+ }
+
+ public byte[] PublicKey
+ {
+ get
+ {
+ throw new PlatformNotSupportedException(SR.PlatformNotSupported_StrongNameSigning);
+ }
+ }
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ void IDeserializationCallback.OnDeserialization(object sender)
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/TargetException.cs b/src/System.Private.CoreLib/shared/System/Reflection/TargetException.cs
new file mode 100644
index 0000000000..c200000738
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/TargetException.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class TargetException : ApplicationException
+ {
+ public TargetException()
+ : this(null)
+ {
+ }
+
+ public TargetException(string message)
+ : this(message, null)
+ {
+ }
+
+ public TargetException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_TARGET;
+ }
+
+ protected TargetException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/TargetInvocationException.cs b/src/System.Private.CoreLib/shared/System/Reflection/TargetInvocationException.cs
new file mode 100644
index 0000000000..822ddfdfb2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/TargetInvocationException.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class TargetInvocationException : ApplicationException
+ {
+ public TargetInvocationException(Exception inner)
+ : base(SR.Arg_TargetInvocationException, inner)
+ {
+ HResult = HResults.COR_E_TARGETINVOCATION;
+ }
+
+ public TargetInvocationException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_TARGETINVOCATION;
+ }
+
+ internal TargetInvocationException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/TargetParameterCountException.cs b/src/System.Private.CoreLib/shared/System/Reflection/TargetParameterCountException.cs
new file mode 100644
index 0000000000..c68c467290
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/TargetParameterCountException.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Reflection
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class TargetParameterCountException : ApplicationException
+ {
+ public TargetParameterCountException()
+ : base(SR.Arg_TargetParameterCountException)
+ {
+ HResult = HResults.COR_E_TARGETPARAMCOUNT;
+ }
+
+ public TargetParameterCountException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_TARGETPARAMCOUNT;
+ }
+
+ public TargetParameterCountException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_TARGETPARAMCOUNT;
+ }
+
+ internal TargetParameterCountException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/TypeAttributes.cs b/src/System.Private.CoreLib/shared/System/Reflection/TypeAttributes.cs
new file mode 100644
index 0000000000..aa30331856
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/TypeAttributes.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Reflection
+{
+ // This Enum matchs the CorTypeAttr defined in CorHdr.h
+ [Flags]
+ public enum TypeAttributes
+ {
+ VisibilityMask = 0x00000007,
+ NotPublic = 0x00000000, // Class is not public scope.
+ Public = 0x00000001, // Class is public scope.
+ NestedPublic = 0x00000002, // Class is nested with public visibility.
+ NestedPrivate = 0x00000003, // Class is nested with private visibility.
+ NestedFamily = 0x00000004, // Class is nested with family visibility.
+ NestedAssembly = 0x00000005, // Class is nested with assembly visibility.
+ NestedFamANDAssem = 0x00000006, // Class is nested with family and assembly visibility.
+ NestedFamORAssem = 0x00000007, // Class is nested with family or assembly visibility.
+
+ // Use this mask to retrieve class layout informaiton
+ // 0 is AutoLayout, 0x2 is SequentialLayout, 4 is ExplicitLayout
+ LayoutMask = 0x00000018,
+ AutoLayout = 0x00000000, // Class fields are auto-laid out
+ SequentialLayout = 0x00000008, // Class fields are laid out sequentially
+ ExplicitLayout = 0x00000010, // Layout is supplied explicitly
+ // end layout mask
+
+ // Use this mask to distinguish whether a type declaration is an interface. (Class vs. ValueType done based on whether it subclasses S.ValueType)
+ ClassSemanticsMask = 0x00000020,
+ Class = 0x00000000, // Type is a class (or a value type).
+ Interface = 0x00000020, // Type is an interface.
+
+ // Special semantics in addition to class semantics.
+ Abstract = 0x00000080, // Class is abstract
+ Sealed = 0x00000100, // Class is concrete and may not be extended
+ SpecialName = 0x00000400, // Class name is special. Name describes how.
+
+ // Implementation attributes.
+ Import = 0x00001000, // Class / interface is imported
+ Serializable = 0x00002000, // The class is Serializable.
+ WindowsRuntime = 0x00004000, // Type is a Windows Runtime type.
+
+ // Use tdStringFormatMask to retrieve string information for native interop
+ StringFormatMask = 0x00030000,
+ AnsiClass = 0x00000000, // LPTSTR is interpreted as ANSI in this class
+ UnicodeClass = 0x00010000, // LPTSTR is interpreted as UNICODE
+ AutoClass = 0x00020000, // LPTSTR is interpreted automatically
+ CustomFormatClass = 0x00030000, // A non-standard encoding specified by CustomFormatMask
+ CustomFormatMask = 0x00C00000, // Use this mask to retrieve non-standard encoding information for native interop. The meaning of the values of these 2 bits is unspecified.
+
+ // end string format mask
+
+ BeforeFieldInit = 0x00100000, // Initialize the class any time before first static field access.
+
+ RTSpecialName = 0x00000800, // Runtime should check name encoding.
+ HasSecurity = 0x00040000, // Class has security associate with it.
+
+ ReservedMask = 0x00040800,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/TypeDelegator.cs b/src/System.Private.CoreLib/shared/System/Reflection/TypeDelegator.cs
new file mode 100644
index 0000000000..e0be6e87b6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/TypeDelegator.cs
@@ -0,0 +1,130 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// TypeDelegator
+//
+// This class wraps a Type object and delegates all methods to that Type.
+
+using CultureInfo = System.Globalization.CultureInfo;
+
+namespace System.Reflection
+{
+ public class TypeDelegator : TypeInfo
+ {
+ public override bool IsAssignableFrom(TypeInfo typeInfo)
+ {
+ if (typeInfo == null)
+ return false;
+ return IsAssignableFrom(typeInfo.AsType());
+ }
+
+ protected Type typeImpl;
+
+ protected TypeDelegator() { }
+
+ public TypeDelegator(Type delegatingType)
+ {
+ if (delegatingType == null)
+ throw new ArgumentNullException(nameof(delegatingType));
+
+ typeImpl = delegatingType;
+ }
+
+ public override Guid GUID => typeImpl.GUID;
+ public override int MetadataToken => typeImpl.MetadataToken;
+
+ public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target,
+ object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
+ {
+ return typeImpl.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
+ }
+
+ public override Module Module => typeImpl.Module;
+ public override Assembly Assembly => typeImpl.Assembly;
+ public override RuntimeTypeHandle TypeHandle => typeImpl.TypeHandle;
+ public override string Name => typeImpl.Name;
+ public override string FullName => typeImpl.FullName;
+ public override string Namespace => typeImpl.Namespace;
+ public override string AssemblyQualifiedName => typeImpl.AssemblyQualifiedName;
+ public override Type BaseType => typeImpl.BaseType;
+
+ protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder,
+ CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
+ {
+ return typeImpl.GetConstructor(bindingAttr, binder, callConvention, types, modifiers);
+ }
+
+ public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) => typeImpl.GetConstructors(bindingAttr);
+
+ protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder,
+ CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
+ {
+ // This is interesting there are two paths into the impl. One that validates
+ // type as non-null and one where type may be null.
+ if (types == null)
+ return typeImpl.GetMethod(name, bindingAttr);
+ else
+ return typeImpl.GetMethod(name, bindingAttr, binder, callConvention, types, modifiers);
+ }
+
+ public override MethodInfo[] GetMethods(BindingFlags bindingAttr) => typeImpl.GetMethods(bindingAttr);
+
+ public override FieldInfo GetField(string name, BindingFlags bindingAttr) => typeImpl.GetField(name, bindingAttr);
+ public override FieldInfo[] GetFields(BindingFlags bindingAttr) => typeImpl.GetFields(bindingAttr);
+
+ public override Type GetInterface(string name, bool ignoreCase) => typeImpl.GetInterface(name, ignoreCase);
+
+ public override Type[] GetInterfaces() => typeImpl.GetInterfaces();
+
+ public override EventInfo GetEvent(string name, BindingFlags bindingAttr) => typeImpl.GetEvent(name, bindingAttr);
+
+ public override EventInfo[] GetEvents() => typeImpl.GetEvents();
+
+ protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder,
+ Type returnType, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (returnType == null && types == null)
+ return typeImpl.GetProperty(name, bindingAttr);
+ else
+ return typeImpl.GetProperty(name, bindingAttr, binder, returnType, types, modifiers);
+ }
+
+ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) => typeImpl.GetProperties(bindingAttr);
+ public override EventInfo[] GetEvents(BindingFlags bindingAttr) => typeImpl.GetEvents(bindingAttr);
+ public override Type[] GetNestedTypes(BindingFlags bindingAttr) => typeImpl.GetNestedTypes(bindingAttr);
+ public override Type GetNestedType(string name, BindingFlags bindingAttr) => typeImpl.GetNestedType(name, bindingAttr);
+ public override MemberInfo[] GetMember(string name, MemberTypes type, BindingFlags bindingAttr) => typeImpl.GetMember(name, type, bindingAttr);
+ public override MemberInfo[] GetMembers(BindingFlags bindingAttr) => typeImpl.GetMembers(bindingAttr);
+
+ protected override TypeAttributes GetAttributeFlagsImpl() => typeImpl.Attributes;
+
+ public override bool IsTypeDefinition => typeImpl.IsTypeDefinition;
+ public override bool IsSZArray => typeImpl.IsSZArray;
+
+ protected override bool IsArrayImpl() => typeImpl.IsArray;
+ protected override bool IsPrimitiveImpl() => typeImpl.IsPrimitive;
+ protected override bool IsByRefImpl() => typeImpl.IsByRef;
+ public override bool IsGenericTypeParameter => typeImpl.IsGenericTypeParameter;
+ public override bool IsGenericMethodParameter => typeImpl.IsGenericMethodParameter;
+ protected override bool IsPointerImpl() => typeImpl.IsPointer;
+ protected override bool IsValueTypeImpl() => typeImpl.IsValueType;
+ protected override bool IsCOMObjectImpl() => typeImpl.IsCOMObject;
+ public override bool IsByRefLike => typeImpl.IsByRefLike;
+ public override bool IsConstructedGenericType => typeImpl.IsConstructedGenericType;
+
+ public override bool IsCollectible => typeImpl.IsCollectible;
+
+ public override Type GetElementType() => typeImpl.GetElementType();
+ protected override bool HasElementTypeImpl() => typeImpl.HasElementType;
+
+ public override Type UnderlyingSystemType => typeImpl.UnderlyingSystemType;
+
+ // ICustomAttributeProvider
+ public override object[] GetCustomAttributes(bool inherit) => typeImpl.GetCustomAttributes(inherit);
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit) => typeImpl.GetCustomAttributes(attributeType, inherit);
+
+ public override bool IsDefined(Type attributeType, bool inherit) => typeImpl.IsDefined(attributeType, inherit);
+ public override InterfaceMapping GetInterfaceMap(Type interfaceType) => typeImpl.GetInterfaceMap(interfaceType);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/TypeFilter.cs b/src/System.Private.CoreLib/shared/System/Reflection/TypeFilter.cs
new file mode 100644
index 0000000000..eb049f81f9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/TypeFilter.cs
@@ -0,0 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Reflection
+{
+ public delegate bool TypeFilter(Type m, object filterCriteria);
+}
diff --git a/src/System.Private.CoreLib/shared/System/Reflection/TypeInfo.cs b/src/System.Private.CoreLib/shared/System/Reflection/TypeInfo.cs
new file mode 100644
index 0000000000..f4add736f4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Reflection/TypeInfo.cs
@@ -0,0 +1,84 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Reflection
+{
+ public abstract partial class TypeInfo : Type, IReflectableType
+ {
+ protected TypeInfo() { }
+
+ TypeInfo IReflectableType.GetTypeInfo() => this;
+ public virtual Type AsType() => this;
+
+ public virtual Type[] GenericTypeParameters => IsGenericTypeDefinition ? GetGenericArguments() : Type.EmptyTypes;
+
+ public virtual EventInfo GetDeclaredEvent(string name) => GetEvent(name, TypeInfo.DeclaredOnlyLookup);
+ public virtual FieldInfo GetDeclaredField(string name) => GetField(name, TypeInfo.DeclaredOnlyLookup);
+ public virtual MethodInfo GetDeclaredMethod(string name) => GetMethod(name, TypeInfo.DeclaredOnlyLookup);
+ public virtual TypeInfo GetDeclaredNestedType(string name) => GetNestedType(name, TypeInfo.DeclaredOnlyLookup)?.GetTypeInfo();
+ public virtual PropertyInfo GetDeclaredProperty(string name) => GetProperty(name, TypeInfo.DeclaredOnlyLookup);
+
+ public virtual IEnumerable<MethodInfo> GetDeclaredMethods(string name)
+ {
+ foreach (MethodInfo method in GetMethods(TypeInfo.DeclaredOnlyLookup))
+ {
+ if (method.Name == name)
+ yield return method;
+ }
+ }
+
+ public virtual IEnumerable<ConstructorInfo> DeclaredConstructors => GetConstructors(TypeInfo.DeclaredOnlyLookup);
+ public virtual IEnumerable<EventInfo> DeclaredEvents => GetEvents(TypeInfo.DeclaredOnlyLookup);
+ public virtual IEnumerable<FieldInfo> DeclaredFields => GetFields(TypeInfo.DeclaredOnlyLookup);
+ public virtual IEnumerable<MemberInfo> DeclaredMembers => GetMembers(TypeInfo.DeclaredOnlyLookup);
+ public virtual IEnumerable<MethodInfo> DeclaredMethods => GetMethods(TypeInfo.DeclaredOnlyLookup);
+ public virtual IEnumerable<System.Reflection.TypeInfo> DeclaredNestedTypes
+ {
+ get
+ {
+ foreach (Type t in GetNestedTypes(TypeInfo.DeclaredOnlyLookup))
+ {
+ yield return t.GetTypeInfo();
+ }
+ }
+ }
+ public virtual IEnumerable<PropertyInfo> DeclaredProperties => GetProperties(TypeInfo.DeclaredOnlyLookup);
+
+ public virtual IEnumerable<Type> ImplementedInterfaces => GetInterfaces();
+
+ //a re-implementation of ISAF from Type, skipping the use of UnderlyingType
+ public virtual bool IsAssignableFrom(TypeInfo typeInfo)
+ {
+ if (typeInfo == null)
+ return false;
+
+ if (this == typeInfo)
+ return true;
+
+ // If c is a subclass of this class, then c can be cast to this type.
+ if (typeInfo.IsSubclassOf(this))
+ return true;
+
+ if (this.IsInterface)
+ {
+ return typeInfo.ImplementInterface(this);
+ }
+ else if (IsGenericParameter)
+ {
+ Type[] constraints = GetGenericParameterConstraints();
+ for (int i = 0; i < constraints.Length; i++)
+ if (!constraints[i].IsAssignableFrom(typeInfo))
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ResolveEventArgs.cs b/src/System.Private.CoreLib/shared/System/ResolveEventArgs.cs
new file mode 100644
index 0000000000..6196947bb5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ResolveEventArgs.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+
+namespace System
+{
+ public class ResolveEventArgs : EventArgs
+ {
+ public ResolveEventArgs(string name)
+ {
+ Name = name;
+ }
+
+ public ResolveEventArgs(string name, Assembly requestingAssembly)
+ {
+ Name = name;
+ RequestingAssembly = requestingAssembly;
+ }
+
+ public string Name { get; }
+ public Assembly RequestingAssembly { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ResolveEventHandler.cs b/src/System.Private.CoreLib/shared/System/ResolveEventHandler.cs
new file mode 100644
index 0000000000..cb9af5de66
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ResolveEventHandler.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+
+namespace System
+{
+ public delegate Assembly ResolveEventHandler(object sender, ResolveEventArgs args);
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/IResourceReader.cs b/src/System.Private.CoreLib/shared/System/Resources/IResourceReader.cs
new file mode 100644
index 0000000000..543a5a67de
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/IResourceReader.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: Abstraction to read streams of resources.
+**
+**
+===========================================================*/
+
+using System;
+using System.Collections;
+
+namespace System.Resources
+{
+ public interface IResourceReader : IEnumerable, IDisposable
+ {
+ // Interface does not need to be marked with the serializable attribute
+ // Closes the ResourceReader, releasing any resources associated with it.
+ // This could close a network connection, a file, or do nothing.
+ void Close();
+
+ new IDictionaryEnumerator GetEnumerator();
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/MissingManifestResourceException.cs b/src/System.Private.CoreLib/shared/System/Resources/MissingManifestResourceException.cs
new file mode 100644
index 0000000000..20914ac7e1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/MissingManifestResourceException.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.Resources
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class MissingManifestResourceException : SystemException
+ {
+ public MissingManifestResourceException()
+ : base(SR.Arg_MissingManifestResourceException)
+ {
+ HResult = System.HResults.COR_E_MISSINGMANIFESTRESOURCE;
+ }
+
+ public MissingManifestResourceException(string message)
+ : base(message)
+ {
+ HResult = System.HResults.COR_E_MISSINGMANIFESTRESOURCE;
+ }
+
+ public MissingManifestResourceException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = System.HResults.COR_E_MISSINGMANIFESTRESOURCE;
+ }
+
+ protected MissingManifestResourceException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/MissingSatelliteAssemblyException.cs b/src/System.Private.CoreLib/shared/System/Resources/MissingSatelliteAssemblyException.cs
new file mode 100644
index 0000000000..af547b21f1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/MissingSatelliteAssemblyException.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: Exception for a missing satellite assembly needed
+** for ultimate resource fallback. This usually
+** indicates a setup and/or deployment problem.
+**
+**
+===========================================================*/
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.Resources
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class MissingSatelliteAssemblyException : SystemException
+ {
+ private String _cultureName;
+
+ public MissingSatelliteAssemblyException()
+ : base(SR.MissingSatelliteAssembly_Default)
+ {
+ HResult = System.HResults.COR_E_MISSINGSATELLITEASSEMBLY;
+ }
+
+ public MissingSatelliteAssemblyException(string message)
+ : base(message)
+ {
+ HResult = System.HResults.COR_E_MISSINGSATELLITEASSEMBLY;
+ }
+
+ public MissingSatelliteAssemblyException(string message, String cultureName)
+ : base(message)
+ {
+ HResult = System.HResults.COR_E_MISSINGSATELLITEASSEMBLY;
+ _cultureName = cultureName;
+ }
+
+ public MissingSatelliteAssemblyException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = System.HResults.COR_E_MISSINGSATELLITEASSEMBLY;
+ }
+
+ protected MissingSatelliteAssemblyException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+
+ public String CultureName
+ {
+ get { return _cultureName; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/NeutralResourcesLanguageAttribute.cs b/src/System.Private.CoreLib/shared/System/Resources/NeutralResourcesLanguageAttribute.cs
new file mode 100644
index 0000000000..495c5205b2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/NeutralResourcesLanguageAttribute.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Resources
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
+ public sealed class NeutralResourcesLanguageAttribute : Attribute
+ {
+ public NeutralResourcesLanguageAttribute(string cultureName)
+ {
+ if (cultureName == null)
+ throw new ArgumentNullException(nameof(cultureName));
+
+ CultureName = cultureName;
+ Location = UltimateResourceFallbackLocation.MainAssembly;
+ }
+
+ public NeutralResourcesLanguageAttribute(string cultureName, UltimateResourceFallbackLocation location)
+ {
+ if (cultureName == null)
+ throw new ArgumentNullException(nameof(cultureName));
+ if (!Enum.IsDefined(typeof(UltimateResourceFallbackLocation), location))
+ throw new ArgumentException(SR.Format(SR.Arg_InvalidNeutralResourcesLanguage_FallbackLoc, location));
+
+ CultureName = cultureName;
+ Location = location;
+ }
+
+ public string CultureName { get; }
+ public UltimateResourceFallbackLocation Location { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/ResourceFallbackManager.cs b/src/System.Private.CoreLib/shared/System/Resources/ResourceFallbackManager.cs
new file mode 100644
index 0000000000..8268f3208d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/ResourceFallbackManager.cs
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: Encapsulates CultureInfo fallback for resource
+** lookup
+**
+**
+===========================================================*/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System.Resources
+{
+ internal class ResourceFallbackManager : IEnumerable<CultureInfo>
+ {
+ private CultureInfo m_startingCulture;
+ private CultureInfo m_neutralResourcesCulture;
+ private bool m_useParents;
+
+ internal ResourceFallbackManager(CultureInfo startingCulture, CultureInfo neutralResourcesCulture, bool useParents)
+ {
+ if (startingCulture != null)
+ {
+ m_startingCulture = startingCulture;
+ }
+ else
+ {
+ m_startingCulture = CultureInfo.CurrentUICulture;
+ }
+
+ m_neutralResourcesCulture = neutralResourcesCulture;
+ m_useParents = useParents;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ // WARING: This function must be kept in sync with ResourceManager.GetFirstResourceSet()
+ public IEnumerator<CultureInfo> GetEnumerator()
+ {
+ bool reachedNeutralResourcesCulture = false;
+
+ // 1. starting culture chain, up to neutral
+ CultureInfo currentCulture = m_startingCulture;
+ do
+ {
+ if (m_neutralResourcesCulture != null && currentCulture.Name == m_neutralResourcesCulture.Name)
+ {
+ // Return the invariant culture all the time, even if the UltimateResourceFallbackLocation
+ // is a satellite assembly. This is fixed up later in ManifestBasedResourceGroveler::UltimateFallbackFixup.
+ yield return CultureInfo.InvariantCulture;
+ reachedNeutralResourcesCulture = true;
+ break;
+ }
+ yield return currentCulture;
+ currentCulture = currentCulture.Parent;
+ } while (m_useParents && !currentCulture.HasInvariantCultureName);
+
+ if (!m_useParents || m_startingCulture.HasInvariantCultureName)
+ {
+ yield break;
+ }
+
+ // 2. invariant
+ // Don't return invariant twice though.
+ if (reachedNeutralResourcesCulture)
+ yield break;
+
+ yield return CultureInfo.InvariantCulture;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/ResourceTypeCode.cs b/src/System.Private.CoreLib/shared/System/Resources/ResourceTypeCode.cs
new file mode 100644
index 0000000000..b0ceb61df4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/ResourceTypeCode.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: Marker for types in .resources files
+**
+**
+===========================================================*/
+
+namespace System.Resources
+{
+ /* An internal implementation detail for .resources files, describing
+ what type an object is.
+ Ranges:
+ 0 - 0x1F Primitives and reserved values
+ 0x20 - 0x3F Specially recognized types, like byte[] and Streams
+
+ Note this data must be included in any documentation describing the
+ internals of .resources files.
+ */
+ internal enum ResourceTypeCode
+ {
+ // Primitives
+ Null = 0,
+ String = 1,
+ Boolean = 2,
+ Char = 3,
+ Byte = 4,
+ SByte = 5,
+ Int16 = 6,
+ UInt16 = 7,
+ Int32 = 8,
+ UInt32 = 9,
+ Int64 = 0xa,
+ UInt64 = 0xb,
+ Single = 0xc,
+ Double = 0xd,
+ Decimal = 0xe,
+ DateTime = 0xf,
+ TimeSpan = 0x10,
+
+ // A meta-value - change this if you add new primitives
+ LastPrimitive = TimeSpan,
+
+ // Types with a special representation, like byte[] and Stream
+ ByteArray = 0x20,
+ Stream = 0x21,
+
+ // User types - serialized using the binary formatter.
+ StartOfUserTypes = 0x40
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/RuntimeResourceSet.cs b/src/System.Private.CoreLib/shared/System/Resources/RuntimeResourceSet.cs
new file mode 100644
index 0000000000..a63e68c19d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/RuntimeResourceSet.cs
@@ -0,0 +1,434 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: CultureInfo-specific collection of resources.
+**
+**
+===========================================================*/
+
+using System;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using System.Runtime.Versioning;
+using System.Diagnostics;
+
+namespace System.Resources
+{
+ // A RuntimeResourceSet stores all the resources defined in one
+ // particular CultureInfo, with some loading optimizations.
+ //
+ // It is expected that nearly all the runtime's users will be satisfied with the
+ // default resource file format, and it will be more efficient than most simple
+ // implementations. Users who would consider creating their own ResourceSets and/or
+ // ResourceReaders and ResourceWriters are people who have to interop with a
+ // legacy resource file format, are creating their own resource file format
+ // (using XML, for instance), or require doing resource lookups at runtime over
+ // the network. This group will hopefully be small, but all the infrastructure
+ // should be in place to let these users write & plug in their own tools.
+ //
+ // The Default Resource File Format
+ //
+ // The fundamental problems addressed by the resource file format are:
+ //
+ // * Versioning - A ResourceReader could in theory support many different
+ // file format revisions.
+ // * Storing intrinsic datatypes (ie, ints, Strings, DateTimes, etc) in a compact
+ // format
+ // * Support for user-defined classes - Accomplished using Serialization
+ // * Resource lookups should not require loading an entire resource file - If you
+ // look up a resource, we only load the value for that resource, minimizing working set.
+ //
+ //
+ // There are four sections to the default file format. The first
+ // is the Resource Manager header, which consists of a magic number
+ // that identifies this as a Resource file, and a ResourceSet class name.
+ // The class name is written here to allow users to provide their own
+ // implementation of a ResourceSet (and a matching ResourceReader) to
+ // control policy. If objects greater than a certain size or matching a
+ // certain naming scheme shouldn't be stored in memory, users can tweak that
+ // with their own subclass of ResourceSet.
+ //
+ // The second section in the system default file format is the
+ // RuntimeResourceSet specific header. This contains a version number for
+ // the .resources file, the number of resources in this file, the number of
+ // different types contained in the file, followed by a list of fully
+ // qualified type names. After this, we include an array of hash values for
+ // each resource name, then an array of virtual offsets into the name section
+ // of the file. The hashes allow us to do a binary search on an array of
+ // integers to find a resource name very quickly without doing many string
+ // compares (except for once we find the real type, of course). If a hash
+ // matches, the index into the array of hash values is used as the index
+ // into the name position array to find the name of the resource. The type
+ // table allows us to read multiple different classes from the same file,
+ // including user-defined types, in a more efficient way than using
+ // Serialization, at least when your .resources file contains a reasonable
+ // proportion of base data types such as Strings or ints. We use
+ // Serialization for all the non-instrinsic types.
+ //
+ // The third section of the file is the name section. It contains a
+ // series of resource names, written out as byte-length prefixed little
+ // endian Unicode strings (UTF-16). After each name is a four byte virtual
+ // offset into the data section of the file, pointing to the relevant
+ // string or serialized blob for this resource name.
+ //
+ // The fourth section in the file is the data section, which consists
+ // of a type and a blob of bytes for each item in the file. The type is
+ // an integer index into the type table. The data is specific to that type,
+ // but may be a number written in binary format, a String, or a serialized
+ // Object.
+ //
+ // The system default file format (V1) is as follows:
+ //
+ // What Type of Data
+ // ==================================================== ===========
+ //
+ // Resource Manager header
+ // Magic Number (0xBEEFCACE) Int32
+ // Resource Manager header version Int32
+ // Num bytes to skip from here to get past this header Int32
+ // Class name of IResourceReader to parse this file String
+ // Class name of ResourceSet to parse this file String
+ //
+ // RuntimeResourceReader header
+ // ResourceReader version number Int32
+ // [Only in debug V2 builds - "***DEBUG***"] String
+ // Number of resources in the file Int32
+ // Number of types in the type table Int32
+ // Name of each type Set of Strings
+ // Padding bytes for 8-byte alignment (use PAD) Bytes (0-7)
+ // Hash values for each resource name Int32 array, sorted
+ // Virtual offset of each resource name Int32 array, coupled with hash values
+ // Absolute location of Data section Int32
+ //
+ // RuntimeResourceReader Name Section
+ // Name & virtual offset of each resource Set of (UTF-16 String, Int32) pairs
+ //
+ // RuntimeResourceReader Data Section
+ // Type and Value of each resource Set of (Int32, blob of bytes) pairs
+ //
+ // This implementation, when used with the default ResourceReader class,
+ // loads only the strings that you look up for. It can do string comparisons
+ // without having to create a new String instance due to some memory mapped
+ // file optimizations in the ResourceReader and FastResourceComparer
+ // classes. This keeps the memory we touch to a minimum when loading
+ // resources.
+ //
+ // If you use a different IResourceReader class to read a file, or if you
+ // do case-insensitive lookups (and the case-sensitive lookup fails) then
+ // we will load all the names of each resource and each resource value.
+ // This could probably use some optimization.
+ //
+ // In addition, this supports object serialization in a similar fashion.
+ // We build an array of class types contained in this file, and write it
+ // to RuntimeResourceReader header section of the file. Every resource
+ // will contain its type (as an index into the array of classes) with the data
+ // for that resource. We will use the Runtime's serialization support for this.
+ //
+ // All strings in the file format are written with BinaryReader and
+ // BinaryWriter, which writes out the length of the String in bytes as an
+ // Int32 then the contents as Unicode chars encoded in UTF-8. In the name
+ // table though, each resource name is written in UTF-16 so we can do a
+ // string compare byte by byte against the contents of the file, without
+ // allocating objects. Ideally we'd have a way of comparing UTF-8 bytes
+ // directly against a String object, but that may be a lot of work.
+ //
+ // The offsets of each resource string are relative to the beginning
+ // of the Data section of the file. This way, if a tool decided to add
+ // one resource to a file, it would only need to increment the number of
+ // resources, add the hash &amp; location of last byte in the name section
+ // to the array of resource hashes and resource name positions (carefully
+ // keeping these arrays sorted), add the name to the end of the name &amp;
+ // offset list, possibly add the type list of types types (and increase
+ // the number of items in the type table), and add the resource value at
+ // the end of the file. The other offsets wouldn't need to be updated to
+ // reflect the longer header section.
+ //
+ // Resource files are currently limited to 2 gigabytes due to these
+ // design parameters. A future version may raise the limit to 4 gigabytes
+ // by using unsigned integers, or may use negative numbers to load items
+ // out of an assembly manifest. Also, we may try sectioning the resource names
+ // into smaller chunks, each of size sqrt(n), would be substantially better for
+ // resource files containing thousands of resources.
+ //
+#if CORERT
+ public // On CoreRT, this must be public because of need to whitelist past the ReflectionBlock.
+#else
+ internal
+#endif
+ sealed class RuntimeResourceSet : ResourceSet, IEnumerable
+ {
+ internal const int Version = 2; // File format version number
+
+ // Cache for resources. Key is the resource name, which can be cached
+ // for arbitrarily long times, since the object is usually a string
+ // literal that will live for the lifetime of the appdomain. The
+ // value is a ResourceLocator instance, which might cache the object.
+ private Dictionary<String, ResourceLocator> _resCache;
+
+
+ // For our special load-on-demand reader, cache the cast. The
+ // RuntimeResourceSet's implementation knows how to treat this reader specially.
+ private ResourceReader _defaultReader;
+
+ // This is a lookup table for case-insensitive lookups, and may be null.
+ // Consider always using a case-insensitive resource cache, as we don't
+ // want to fill this out if we can avoid it. The problem is resource
+ // fallback will somewhat regularly cause us to look up resources that
+ // don't exist.
+ private Dictionary<String, ResourceLocator> _caseInsensitiveTable;
+
+ // If we're not using our custom reader, then enumerate through all
+ // the resources once, adding them into the table.
+ private bool _haveReadFromReader;
+
+ internal RuntimeResourceSet(String fileName) : base(false)
+ {
+ _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default);
+ Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
+ _defaultReader = new ResourceReader(stream, _resCache);
+ Reader = _defaultReader;
+ }
+
+ internal RuntimeResourceSet(Stream stream) : base(false)
+ {
+ _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default);
+ _defaultReader = new ResourceReader(stream, _resCache);
+ Reader = _defaultReader;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (Reader == null)
+ return;
+
+ if (disposing)
+ {
+ lock (Reader)
+ {
+ _resCache = null;
+ if (_defaultReader != null)
+ {
+ _defaultReader.Close();
+ _defaultReader = null;
+ }
+ _caseInsensitiveTable = null;
+ // Set Reader to null to avoid a race in GetObject.
+ base.Dispose(disposing);
+ }
+ }
+ else
+ {
+ // Just to make sure we always clear these fields in the future...
+ _resCache = null;
+ _caseInsensitiveTable = null;
+ _defaultReader = null;
+ base.Dispose(disposing);
+ }
+ }
+
+ public override IDictionaryEnumerator GetEnumerator()
+ {
+ return GetEnumeratorHelper();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumeratorHelper();
+ }
+
+ private IDictionaryEnumerator GetEnumeratorHelper()
+ {
+ IResourceReader copyOfReader = Reader;
+ if (copyOfReader == null || _resCache == null)
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
+
+ return copyOfReader.GetEnumerator();
+ }
+
+
+ public override String GetString(String key)
+ {
+ Object o = GetObject(key, false, true);
+ return (String)o;
+ }
+
+ public override String GetString(String key, bool ignoreCase)
+ {
+ Object o = GetObject(key, ignoreCase, true);
+ return (String)o;
+ }
+
+ public override Object GetObject(String key)
+ {
+ return GetObject(key, false, false);
+ }
+
+ public override Object GetObject(String key, bool ignoreCase)
+ {
+ return GetObject(key, ignoreCase, false);
+ }
+
+ private Object GetObject(String key, bool ignoreCase, bool isString)
+ {
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+ if (Reader == null || _resCache == null)
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
+
+ Object value = null;
+ ResourceLocator resLocation;
+
+ lock (Reader)
+ {
+ if (Reader == null)
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
+
+ if (_defaultReader != null)
+ {
+ // Find the offset within the data section
+ int dataPos = -1;
+ if (_resCache.TryGetValue(key, out resLocation))
+ {
+ value = resLocation.Value;
+ dataPos = resLocation.DataPosition;
+ }
+
+ if (dataPos == -1 && value == null)
+ {
+ dataPos = _defaultReader.FindPosForResource(key);
+ }
+
+ if (dataPos != -1 && value == null)
+ {
+ Debug.Assert(dataPos >= 0, "data section offset cannot be negative!");
+ // Normally calling LoadString or LoadObject requires
+ // taking a lock. Note that in this case, we took a
+ // lock on the entire RuntimeResourceSet, which is
+ // sufficient since we never pass this ResourceReader
+ // to anyone else.
+ ResourceTypeCode typeCode;
+ if (isString)
+ {
+ value = _defaultReader.LoadString(dataPos);
+ typeCode = ResourceTypeCode.String;
+ }
+ else
+ {
+ value = _defaultReader.LoadObject(dataPos, out typeCode);
+ }
+
+ resLocation = new ResourceLocator(dataPos, (ResourceLocator.CanCache(typeCode)) ? value : null);
+ lock (_resCache)
+ {
+ _resCache[key] = resLocation;
+ }
+ }
+
+ if (value != null || !ignoreCase)
+ {
+ return value; // may be null
+ }
+ } // if (_defaultReader != null)
+
+ // At this point, we either don't have our default resource reader
+ // or we haven't found the particular resource we're looking for
+ // and may have to search for it in a case-insensitive way.
+ if (!_haveReadFromReader)
+ {
+ // If necessary, init our case insensitive hash table.
+ if (ignoreCase && _caseInsensitiveTable == null)
+ {
+ _caseInsensitiveTable = new Dictionary<String, ResourceLocator>(StringComparer.OrdinalIgnoreCase);
+ }
+
+ if (_defaultReader == null)
+ {
+ IDictionaryEnumerator en = Reader.GetEnumerator();
+ while (en.MoveNext())
+ {
+ DictionaryEntry entry = en.Entry;
+ String readKey = (String)entry.Key;
+ ResourceLocator resLoc = new ResourceLocator(-1, entry.Value);
+ _resCache.Add(readKey, resLoc);
+ if (ignoreCase)
+ _caseInsensitiveTable.Add(readKey, resLoc);
+ }
+ // Only close the reader if it is NOT our default one,
+ // since we need it around to resolve ResourceLocators.
+ if (!ignoreCase)
+ Reader.Close();
+ }
+ else
+ {
+ Debug.Assert(ignoreCase, "This should only happen for case-insensitive lookups");
+ ResourceReader.ResourceEnumerator en = _defaultReader.GetEnumeratorInternal();
+ while (en.MoveNext())
+ {
+ // Note: Always ask for the resource key before the data position.
+ String currentKey = (String)en.Key;
+ int dataPos = en.DataPosition;
+ ResourceLocator resLoc = new ResourceLocator(dataPos, null);
+ _caseInsensitiveTable.Add(currentKey, resLoc);
+ }
+ }
+ _haveReadFromReader = true;
+ }
+ Object obj = null;
+ bool found = false;
+ bool keyInWrongCase = false;
+ if (_defaultReader != null)
+ {
+ if (_resCache.TryGetValue(key, out resLocation))
+ {
+ found = true;
+ obj = ResolveResourceLocator(resLocation, key, _resCache, keyInWrongCase);
+ }
+ }
+ if (!found && ignoreCase)
+ {
+ if (_caseInsensitiveTable.TryGetValue(key, out resLocation))
+ {
+ found = true;
+ keyInWrongCase = true;
+ obj = ResolveResourceLocator(resLocation, key, _resCache, keyInWrongCase);
+ }
+ }
+ return obj;
+ } // lock(Reader)
+ }
+
+ // The last parameter indicates whether the lookup required a
+ // case-insensitive lookup to succeed, indicating we shouldn't add
+ // the ResourceLocation to our case-sensitive cache.
+ private Object ResolveResourceLocator(ResourceLocator resLocation, String key, Dictionary<String, ResourceLocator> copyOfCache, bool keyInWrongCase)
+ {
+ // We need to explicitly resolve loosely linked manifest
+ // resources, and we need to resolve ResourceLocators with null objects.
+ Object value = resLocation.Value;
+ if (value == null)
+ {
+ ResourceTypeCode typeCode;
+ lock (Reader)
+ {
+ value = _defaultReader.LoadObject(resLocation.DataPosition, out typeCode);
+ }
+ if (!keyInWrongCase && ResourceLocator.CanCache(typeCode))
+ {
+ resLocation.Value = value;
+ copyOfCache[key] = resLocation;
+ }
+ }
+ return value;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/SatelliteContractVersionAttribute.cs b/src/System.Private.CoreLib/shared/System/Resources/SatelliteContractVersionAttribute.cs
new file mode 100644
index 0000000000..0707447677
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/SatelliteContractVersionAttribute.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: Specifies which version of a satellite assembly
+** the ResourceManager should ask for.
+**
+**
+===========================================================*/
+
+namespace System.Resources
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
+ public sealed class SatelliteContractVersionAttribute : Attribute
+ {
+ public SatelliteContractVersionAttribute(String version)
+ {
+ if (version == null)
+ throw new ArgumentNullException(nameof(version));
+ Version = version;
+ }
+
+ public String Version { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/UltimateResourceFallbackLocation.cs b/src/System.Private.CoreLib/shared/System/Resources/UltimateResourceFallbackLocation.cs
new file mode 100644
index 0000000000..83640ec9fe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/UltimateResourceFallbackLocation.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+** Purpose: Tells the ResourceManager where to find the
+** ultimate fallback resources for your assembly.
+**
+**
+===========================================================*/
+
+using System;
+
+namespace System.Resources
+{
+ public enum UltimateResourceFallbackLocation
+ {
+ MainAssembly,
+ Satellite
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AccessedThroughPropertyAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AccessedThroughPropertyAttribute.cs
new file mode 100644
index 0000000000..25efcafa3f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AccessedThroughPropertyAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Field)]
+ public sealed class AccessedThroughPropertyAttribute : Attribute
+ {
+ public AccessedThroughPropertyAttribute(string propertyName)
+ {
+ PropertyName = propertyName;
+ }
+
+ public string PropertyName { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilderAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilderAttribute.cs
new file mode 100644
index 0000000000..688a3a01ba
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilderAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Indicates the type of the async method builder that should be used by a language compiler to
+ /// build the attributed type when used as the return type of an async method.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Delegate | AttributeTargets.Enum, Inherited = false, AllowMultiple = false)]
+ public sealed class AsyncMethodBuilderAttribute : Attribute
+ {
+ /// <summary>Initializes the <see cref="AsyncMethodBuilderAttribute"/>.</summary>
+ /// <param name="builderType">The <see cref="Type"/> of the associated builder.</param>
+ public AsyncMethodBuilderAttribute(Type builderType) => BuilderType = builderType;
+
+ /// <summary>Gets the <see cref="Type"/> of the associated builder.</summary>
+ public Type BuilderType { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncStateMachineAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncStateMachineAttribute.cs
new file mode 100644
index 0000000000..66c9175ee7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncStateMachineAttribute.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
+ public sealed class AsyncStateMachineAttribute : StateMachineAttribute
+ {
+ public AsyncStateMachineAttribute(Type stateMachineType)
+ : base(stateMachineType)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs
new file mode 100644
index 0000000000..0e1220d119
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs
@@ -0,0 +1,219 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Threading.Tasks;
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>Represents a builder for asynchronous methods that return a <see cref="ValueTask"/>.</summary>
+ [StructLayout(LayoutKind.Auto)]
+ public struct AsyncValueTaskMethodBuilder
+ {
+ /// <summary>The <see cref="AsyncTaskMethodBuilder"/> to which most operations are delegated.</summary>
+ private AsyncTaskMethodBuilder _methodBuilder; // mutable struct; do not make it readonly
+ /// <summary>true if completed synchronously and successfully; otherwise, false.</summary>
+ private bool _haveResult;
+ /// <summary>true if the builder should be used for setting/getting the result; otherwise, false.</summary>
+ private bool _useBuilder;
+
+ /// <summary>Creates an instance of the <see cref="AsyncValueTaskMethodBuilder"/> struct.</summary>
+ /// <returns>The initialized instance.</returns>
+ public static AsyncValueTaskMethodBuilder Create() =>
+#if CORERT
+ // corert's AsyncTaskMethodBuilder.Create() currently does additional debugger-related
+ // work, so we need to delegate to it.
+ new AsyncValueTaskMethodBuilder() { _methodBuilder = AsyncTaskMethodBuilder.Create() };
+#else
+ // _methodBuilder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr
+ // that Create() is a nop, so we can just return the default here.
+ default;
+#endif
+
+ /// <summary>Begins running the builder with the associated state machine.</summary>
+ /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+ /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+ // will provide the right ExecutionContext semantics
+#if netstandard
+ _methodBuilder.Start(ref stateMachine);
+#else
+ AsyncMethodBuilderCore.Start(ref stateMachine);
+#endif
+
+ /// <summary>Associates the builder with the specified state machine.</summary>
+ /// <param name="stateMachine">The state machine instance to associate with the builder.</param>
+ public void SetStateMachine(IAsyncStateMachine stateMachine) => _methodBuilder.SetStateMachine(stateMachine);
+
+ /// <summary>Marks the task as successfully completed.</summary>
+ public void SetResult()
+ {
+ if (_useBuilder)
+ {
+ _methodBuilder.SetResult();
+ }
+ else
+ {
+ _haveResult = true;
+ }
+ }
+
+ /// <summary>Marks the task as failed and binds the specified exception to the task.</summary>
+ /// <param name="exception">The exception to bind to the task.</param>
+ public void SetException(Exception exception) => _methodBuilder.SetException(exception);
+
+ /// <summary>Gets the task for this builder.</summary>
+ public ValueTask Task
+ {
+ get
+ {
+ if (_haveResult)
+ {
+ return default;
+ }
+ else
+ {
+ _useBuilder = true;
+ return new ValueTask(_methodBuilder.Task);
+ }
+ }
+ }
+
+ /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary>
+ /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+ /// <param name="awaiter">The awaiter.</param>
+ /// <param name="stateMachine">The state machine.</param>
+ public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : INotifyCompletion
+ where TStateMachine : IAsyncStateMachine
+ {
+ _useBuilder = true;
+ _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
+ }
+
+ /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary>
+ /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+ /// <param name="awaiter">The awaiter.</param>
+ /// <param name="stateMachine">The state machine.</param>
+ [SecuritySafeCritical]
+ public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : ICriticalNotifyCompletion
+ where TStateMachine : IAsyncStateMachine
+ {
+ _useBuilder = true;
+ _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
+ }
+ }
+
+ /// <summary>Represents a builder for asynchronous methods that returns a <see cref="ValueTask{TResult}"/>.</summary>
+ /// <typeparam name="TResult">The type of the result.</typeparam>
+ [StructLayout(LayoutKind.Auto)]
+ public struct AsyncValueTaskMethodBuilder<TResult>
+ {
+ /// <summary>The <see cref="AsyncTaskMethodBuilder{TResult}"/> to which most operations are delegated.</summary>
+ private AsyncTaskMethodBuilder<TResult> _methodBuilder; // mutable struct; do not make it readonly
+ /// <summary>The result for this builder, if it's completed before any awaits occur.</summary>
+ private TResult _result;
+ /// <summary>true if <see cref="_result"/> contains the synchronous result for the async method; otherwise, false.</summary>
+ private bool _haveResult;
+ /// <summary>true if the builder should be used for setting/getting the result; otherwise, false.</summary>
+ private bool _useBuilder;
+
+ /// <summary>Creates an instance of the <see cref="AsyncValueTaskMethodBuilder{TResult}"/> struct.</summary>
+ /// <returns>The initialized instance.</returns>
+ public static AsyncValueTaskMethodBuilder<TResult> Create() =>
+#if CORERT
+ // corert's AsyncTaskMethodBuilder<TResult>.Create() currently does additional debugger-related
+ // work, so we need to delegate to it.
+ new AsyncValueTaskMethodBuilder<TResult>() { _methodBuilder = AsyncTaskMethodBuilder<TResult>.Create() };
+#else
+ // _methodBuilder should be initialized to AsyncTaskMethodBuilder<TResult>.Create(), but on coreclr
+ // that Create() is a nop, so we can just return the default here.
+ default;
+#endif
+
+ /// <summary>Begins running the builder with the associated state machine.</summary>
+ /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+ /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+ // will provide the right ExecutionContext semantics
+#if netstandard
+ _methodBuilder.Start(ref stateMachine);
+#else
+ AsyncMethodBuilderCore.Start(ref stateMachine);
+#endif
+
+ /// <summary>Associates the builder with the specified state machine.</summary>
+ /// <param name="stateMachine">The state machine instance to associate with the builder.</param>
+ public void SetStateMachine(IAsyncStateMachine stateMachine) => _methodBuilder.SetStateMachine(stateMachine);
+
+ /// <summary>Marks the task as successfully completed.</summary>
+ /// <param name="result">The result to use to complete the task.</param>
+ public void SetResult(TResult result)
+ {
+ if (_useBuilder)
+ {
+ _methodBuilder.SetResult(result);
+ }
+ else
+ {
+ _result = result;
+ _haveResult = true;
+ }
+ }
+
+ /// <summary>Marks the task as failed and binds the specified exception to the task.</summary>
+ /// <param name="exception">The exception to bind to the task.</param>
+ public void SetException(Exception exception) => _methodBuilder.SetException(exception);
+
+ /// <summary>Gets the task for this builder.</summary>
+ public ValueTask<TResult> Task
+ {
+ get
+ {
+ if (_haveResult)
+ {
+ return new ValueTask<TResult>(_result);
+ }
+ else
+ {
+ _useBuilder = true;
+ return new ValueTask<TResult>(_methodBuilder.Task);
+ }
+ }
+ }
+
+ /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary>
+ /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+ /// <param name="awaiter">the awaiter</param>
+ /// <param name="stateMachine">The state machine.</param>
+ public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : INotifyCompletion
+ where TStateMachine : IAsyncStateMachine
+ {
+ _useBuilder = true;
+ _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
+ }
+
+ /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary>
+ /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+ /// <param name="awaiter">the awaiter</param>
+ /// <param name="stateMachine">The state machine.</param>
+ [SecuritySafeCritical]
+ public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : ICriticalNotifyCompletion
+ where TStateMachine : IAsyncStateMachine
+ {
+ _useBuilder = true;
+ _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerFilePathAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerFilePathAttribute.cs
new file mode 100644
index 0000000000..5858634b42
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerFilePathAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ public sealed class CallerFilePathAttribute : Attribute
+ {
+ public CallerFilePathAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerLineNumberAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerLineNumberAttribute.cs
new file mode 100644
index 0000000000..5bd2fcb91b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerLineNumberAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ public sealed class CallerLineNumberAttribute : Attribute
+ {
+ public CallerLineNumberAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerMemberNameAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerMemberNameAttribute.cs
new file mode 100644
index 0000000000..8b046335b5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CallerMemberNameAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ public sealed class CallerMemberNameAttribute : Attribute
+ {
+ public CallerMemberNameAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilationRelaxations.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilationRelaxations.cs
new file mode 100644
index 0000000000..88e2657a6a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilationRelaxations.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ /// IMPORTANT: Keep this in sync with corhdr.h
+ [Flags]
+ public enum CompilationRelaxations : int
+ {
+ NoStringInterning = 0x0008 // Start in 0x0008, we had other non public flags in this enum before,
+ // so we'll start here just in case somebody used them. This flag is only
+ // valid when set for Assemblies.
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilationRelaxationsAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilationRelaxationsAttribute.cs
new file mode 100644
index 0000000000..d6da23fdf2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilationRelaxationsAttribute.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Method)]
+ public class CompilationRelaxationsAttribute : Attribute
+ {
+ public CompilationRelaxationsAttribute(int relaxations)
+ {
+ CompilationRelaxations = relaxations;
+ }
+
+ public CompilationRelaxationsAttribute(CompilationRelaxations relaxations)
+ {
+ CompilationRelaxations = (int)relaxations;
+ }
+
+ public int CompilationRelaxations { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilerGeneratedAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilerGeneratedAttribute.cs
new file mode 100644
index 0000000000..1c05abd1fe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilerGeneratedAttribute.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.All, Inherited = true)]
+ public sealed class CompilerGeneratedAttribute : Attribute
+ {
+ public CompilerGeneratedAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilerGlobalScopeAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilerGlobalScopeAttribute.cs
new file mode 100644
index 0000000000..752295e876
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CompilerGlobalScopeAttribute.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ // Attribute used to communicate to the VS7 debugger that a class should be treated as if it has global scope.
+
+ [AttributeUsage(AttributeTargets.Class)]
+ public class CompilerGlobalScopeAttribute : Attribute
+ {
+ public CompilerGlobalScopeAttribute() { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs
new file mode 100644
index 0000000000..8f7b0c809c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs
@@ -0,0 +1,236 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Sources;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>Provides an awaitable type that enables configured awaits on a <see cref="ValueTask"/>.</summary>
+ [StructLayout(LayoutKind.Auto)]
+ public readonly struct ConfiguredValueTaskAwaitable
+ {
+ /// <summary>The wrapped <see cref="Task"/>.</summary>
+ private readonly ValueTask _value;
+
+ /// <summary>Initializes the awaitable.</summary>
+ /// <param name="value">The wrapped <see cref="ValueTask"/>.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ConfiguredValueTaskAwaitable(ValueTask value) => _value = value;
+
+ /// <summary>Returns an awaiter for this <see cref="ConfiguredValueTaskAwaitable"/> instance.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ConfiguredValueTaskAwaiter GetAwaiter() => new ConfiguredValueTaskAwaiter(_value);
+
+ /// <summary>Provides an awaiter for a <see cref="ConfiguredValueTaskAwaitable"/>.</summary>
+ [StructLayout(LayoutKind.Auto)]
+ public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion
+#if CORECLR
+ , IStateMachineBoxAwareAwaiter
+#endif
+ {
+ /// <summary>The value being awaited.</summary>
+ private readonly ValueTask _value;
+
+ /// <summary>Initializes the awaiter.</summary>
+ /// <param name="value">The value to be awaited.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ConfiguredValueTaskAwaiter(ValueTask value) => _value = value;
+
+ /// <summary>Gets whether the <see cref="ConfiguredValueTaskAwaitable"/> has completed.</summary>
+ public bool IsCompleted
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _value.IsCompleted;
+ }
+
+ /// <summary>Gets the result of the ValueTask.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [StackTraceHidden]
+ public void GetResult() => _value.ThrowIfCompletedUnsuccessfully();
+
+ /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable"/>.</summary>
+ public void OnCompleted(Action continuation)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj is Task t)
+ {
+ t.ConfigureAwait(_value._continueOnCapturedContext).GetAwaiter().OnCompleted(continuation);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token,
+ ValueTaskSourceOnCompletedFlags.FlowExecutionContext |
+ (_value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None));
+ }
+ else
+ {
+ ValueTask.CompletedTask.ConfigureAwait(_value._continueOnCapturedContext).GetAwaiter().OnCompleted(continuation);
+ }
+ }
+
+ /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable"/>.</summary>
+ public void UnsafeOnCompleted(Action continuation)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj is Task t)
+ {
+ t.ConfigureAwait(_value._continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token,
+ _value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None);
+ }
+ else
+ {
+ ValueTask.CompletedTask.ConfigureAwait(_value._continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation);
+ }
+ }
+
+#if CORECLR
+ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj is Task t)
+ {
+ TaskAwaiter.UnsafeOnCompletedInternal(t, box, _value._continueOnCapturedContext);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource>(obj).OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token,
+ _value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None);
+ }
+ else
+ {
+ TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, _value._continueOnCapturedContext);
+ }
+ }
+#endif
+ }
+ }
+
+ /// <summary>Provides an awaitable type that enables configured awaits on a <see cref="ValueTask{TResult}"/>.</summary>
+ /// <typeparam name="TResult">The type of the result produced.</typeparam>
+ [StructLayout(LayoutKind.Auto)]
+ public readonly struct ConfiguredValueTaskAwaitable<TResult>
+ {
+ /// <summary>The wrapped <see cref="ValueTask{TResult}"/>.</summary>
+ private readonly ValueTask<TResult> _value;
+
+ /// <summary>Initializes the awaitable.</summary>
+ /// <param name="value">The wrapped <see cref="ValueTask{TResult}"/>.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ConfiguredValueTaskAwaitable(ValueTask<TResult> value) => _value = value;
+
+ /// <summary>Returns an awaiter for this <see cref="ConfiguredValueTaskAwaitable{TResult}"/> instance.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ConfiguredValueTaskAwaiter GetAwaiter() => new ConfiguredValueTaskAwaiter(_value);
+
+ /// <summary>Provides an awaiter for a <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary>
+ [StructLayout(LayoutKind.Auto)]
+ public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion
+#if CORECLR
+ , IStateMachineBoxAwareAwaiter
+#endif
+ {
+ /// <summary>The value being awaited.</summary>
+ private readonly ValueTask<TResult> _value;
+
+ /// <summary>Initializes the awaiter.</summary>
+ /// <param name="value">The value to be awaited.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ConfiguredValueTaskAwaiter(ValueTask<TResult> value) => _value = value;
+
+ /// <summary>Gets whether the <see cref="ConfiguredValueTaskAwaitable{TResult}"/> has completed.</summary>
+ public bool IsCompleted
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _value.IsCompleted;
+ }
+
+ /// <summary>Gets the result of the ValueTask.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [StackTraceHidden]
+ public TResult GetResult() => _value.Result;
+
+ /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary>
+ public void OnCompleted(Action continuation)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj is Task<TResult> t)
+ {
+ t.ConfigureAwait(_value._continueOnCapturedContext).GetAwaiter().OnCompleted(continuation);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource<TResult>>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token,
+ ValueTaskSourceOnCompletedFlags.FlowExecutionContext |
+ (_value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None));
+ }
+ else
+ {
+ ValueTask.CompletedTask.ConfigureAwait(_value._continueOnCapturedContext).GetAwaiter().OnCompleted(continuation);
+ }
+ }
+
+ /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary>
+ public void UnsafeOnCompleted(Action continuation)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj is Task<TResult> t)
+ {
+ t.ConfigureAwait(_value._continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource<TResult>>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token,
+ _value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None);
+ }
+ else
+ {
+ ValueTask.CompletedTask.ConfigureAwait(_value._continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation);
+ }
+ }
+
+#if CORECLR
+ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj is Task<TResult> t)
+ {
+ TaskAwaiter.UnsafeOnCompletedInternal(t, box, _value._continueOnCapturedContext);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource<TResult>>(obj).OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token,
+ _value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None);
+ }
+ else
+ {
+ TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, _value._continueOnCapturedContext);
+ }
+ }
+#endif
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CustomConstantAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CustomConstantAttribute.cs
new file mode 100644
index 0000000000..f75693eb40
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/CustomConstantAttribute.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)]
+ public abstract class CustomConstantAttribute : Attribute
+ {
+ public abstract Object Value { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DateTimeConstantAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DateTimeConstantAttribute.cs
new file mode 100644
index 0000000000..813e6803bf
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DateTimeConstantAttribute.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)]
+ public sealed class DateTimeConstantAttribute : CustomConstantAttribute
+ {
+ private DateTime _date;
+
+ public DateTimeConstantAttribute(long ticks)
+ {
+ _date = new DateTime(ticks);
+ }
+
+ public override Object Value => _date;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DecimalConstantAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DecimalConstantAttribute.cs
new file mode 100644
index 0000000000..19db84eb43
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DecimalConstantAttribute.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Note: If you add a new ctor overloads you need to update ParameterInfo.RawDefaultValue
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)]
+ public sealed class DecimalConstantAttribute : Attribute
+ {
+ private Decimal _dec;
+
+ [CLSCompliant(false)]
+ public DecimalConstantAttribute(
+ byte scale,
+ byte sign,
+ uint hi,
+ uint mid,
+ uint low
+ )
+ {
+ _dec = new Decimal((int)low, (int)mid, (int)hi, (sign != 0), scale);
+ }
+
+ public DecimalConstantAttribute(
+ byte scale,
+ byte sign,
+ int hi,
+ int mid,
+ int low
+ )
+ {
+ _dec = new Decimal(low, mid, hi, (sign != 0), scale);
+ }
+
+ public Decimal Value => _dec;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DefaultDependencyAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DefaultDependencyAttribute.cs
new file mode 100644
index 0000000000..4c1f489215
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DefaultDependencyAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly)]
+ public sealed class DefaultDependencyAttribute : Attribute
+ {
+ public DefaultDependencyAttribute(LoadHint loadHintArgument)
+ {
+ LoadHint = loadHintArgument;
+ }
+
+ public LoadHint LoadHint { get; }
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DependencyAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DependencyAttribute.cs
new file mode 100644
index 0000000000..0fe07edc9e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DependencyAttribute.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class DependencyAttribute : Attribute
+ {
+ public DependencyAttribute(String dependentAssemblyArgument, LoadHint loadHintArgument)
+ {
+ DependentAssembly = dependentAssemblyArgument;
+ LoadHint = loadHintArgument;
+ }
+
+ public String DependentAssembly { get; }
+ public LoadHint LoadHint { get; }
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DisablePrivateReflectionAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DisablePrivateReflectionAttribute.cs
new file mode 100644
index 0000000000..4fc00e10ed
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DisablePrivateReflectionAttribute.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
+ public sealed class DisablePrivateReflectionAttribute : Attribute
+ {
+ public DisablePrivateReflectionAttribute() { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DiscardableAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DiscardableAttribute.cs
new file mode 100644
index 0000000000..c88b3a7599
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/DiscardableAttribute.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ // Custom attribute to indicating a TypeDef is a discardable attribute.
+
+ public class DiscardableAttribute : Attribute
+ {
+ public DiscardableAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ExtensionAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ExtensionAttribute.cs
new file mode 100644
index 0000000000..92170880f1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ExtensionAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Indicates that a method is an extension method, or that a class or assembly contains extension methods.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
+ public sealed class ExtensionAttribute : Attribute { }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FixedAddressValueTypeAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FixedAddressValueTypeAttribute.cs
new file mode 100644
index 0000000000..8dc6c43126
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FixedAddressValueTypeAttribute.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Field)]
+ public sealed class FixedAddressValueTypeAttribute : Attribute
+ {
+ public FixedAddressValueTypeAttribute() { }
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FixedBufferAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FixedBufferAttribute.cs
new file mode 100644
index 0000000000..bb8f00f686
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FixedBufferAttribute.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+** Purpose: Used by a compiler for generating value types
+** in-place within other value types containing a certain
+** number of elements of the given (primitive) type. Somewhat
+** similar to P/Invoke's ByValTStr attribute.
+** Used by C# with this syntax: "fixed int buffer[10];"
+**
+===========================================================*/
+
+using System;
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class FixedBufferAttribute : Attribute
+ {
+ public FixedBufferAttribute(Type elementType, int length)
+ {
+ ElementType = elementType;
+ Length = length;
+ }
+
+ public Type ElementType { get; }
+ public int Length { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FormattableStringFactory.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FormattableStringFactory.cs
new file mode 100644
index 0000000000..23d03860ed
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/FormattableStringFactory.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: implementation of the FormattableStringFactory
+** class.
+**
+===========================================================*/
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// A factory type used by compilers to create instances of the type <see cref="FormattableString"/>.
+ /// </summary>
+ public static class FormattableStringFactory
+ {
+ /// <summary>
+ /// Create a <see cref="FormattableString"/> from a composite format string and object
+ /// array containing zero or more objects to format.
+ /// </summary>
+ public static FormattableString Create(string format, params object[] arguments)
+ {
+ if (format == null)
+ {
+ throw new ArgumentNullException(nameof(format));
+ }
+
+ if (arguments == null)
+ {
+ throw new ArgumentNullException(nameof(arguments));
+ }
+
+ return new ConcreteFormattableString(format, arguments);
+ }
+
+ private sealed class ConcreteFormattableString : FormattableString
+ {
+ private readonly string _format;
+ private readonly object[] _arguments;
+
+ internal ConcreteFormattableString(string format, object[] arguments)
+ {
+ _format = format;
+ _arguments = arguments;
+ }
+
+ public override string Format { get { return _format; } }
+ public override object[] GetArguments() { return _arguments; }
+ public override int ArgumentCount { get { return _arguments.Length; } }
+ public override object GetArgument(int index) { return _arguments[index]; }
+ public override string ToString(IFormatProvider formatProvider) { return string.Format(formatProvider, _format, _arguments); }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IAsyncStateMachine.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IAsyncStateMachine.cs
new file mode 100644
index 0000000000..7fb7ea5395
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IAsyncStateMachine.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+//
+//
+// Represents state machines generated for asynchronous methods.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Represents state machines generated for asynchronous methods.
+ /// This type is intended for compiler use only.
+ /// </summary>
+ public interface IAsyncStateMachine
+ {
+ /// <summary>Moves the state machine to its next state.</summary>
+ void MoveNext();
+ /// <summary>Configures the state machine with a heap-allocated replica.</summary>
+ /// <param name="stateMachine">The heap-allocated replica.</param>
+ void SetStateMachine(IAsyncStateMachine stateMachine);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/INotifyCompletion.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/INotifyCompletion.cs
new file mode 100644
index 0000000000..aba0a0691f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/INotifyCompletion.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
+//
+//
+//
+// Interfaces used to represent instances that notify listeners of their completion via continuations.
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+using System;
+using System.Security;
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Represents an operation that will schedule continuations when the operation completes.
+ /// </summary>
+ public interface INotifyCompletion
+ {
+ /// <summary>Schedules the continuation action to be invoked when the instance completes.</summary>
+ /// <param name="continuation">The action to invoke when the operation completes.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
+ void OnCompleted(Action continuation);
+ }
+
+ /// <summary>
+ /// Represents an awaiter used to schedule continuations when an await operation completes.
+ /// </summary>
+ public interface ICriticalNotifyCompletion : INotifyCompletion
+ {
+ /// <summary>Schedules the continuation action to be invoked when the instance completes.</summary>
+ /// <param name="continuation">The action to invoke when the operation completes.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
+ /// <remarks>Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information.</remarks>
+ void UnsafeOnCompleted(Action continuation);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ITuple.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ITuple.cs
new file mode 100644
index 0000000000..cafee11f8a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ITuple.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// This interface is required for types that want to be indexed into by dynamic patterns.
+ /// </summary>
+ public interface ITuple
+ {
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int Length { get; }
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object this[int index] { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IndexerNameAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IndexerNameAttribute.cs
new file mode 100644
index 0000000000..ea843b3daa
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IndexerNameAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Property, Inherited = true)]
+ public sealed class IndexerNameAttribute : Attribute
+ {
+ public IndexerNameAttribute(String indexerName)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/InternalsVisibleToAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/InternalsVisibleToAttribute.cs
new file mode 100644
index 0000000000..f754694815
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/InternalsVisibleToAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
+ public sealed class InternalsVisibleToAttribute : Attribute
+ {
+ public InternalsVisibleToAttribute(string assemblyName)
+ {
+ AssemblyName = assemblyName;
+ }
+
+ public string AssemblyName { get; }
+ public bool AllInternalsVisible { get; set; } = true;
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IntrinsicAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IntrinsicAttribute.cs
new file mode 100644
index 0000000000..6bdd91d844
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IntrinsicAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ // Calls to methods or references to fields marked with this attribute may be replaced at
+ // some call sites with jit intrinsic expansions.
+ // Types marked with this attribute may be specially treated by the runtime/compiler.
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Field, Inherited = false)]
+ internal sealed class IntrinsicAttribute : Attribute
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsByRefLikeAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsByRefLikeAttribute.cs
new file mode 100644
index 0000000000..90e49d2a42
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsByRefLikeAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Reserved to be used by the compiler for tracking metadata.
+ /// This attribute should not be used by developers in source code.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [AttributeUsage(AttributeTargets.Struct)]
+ public sealed class IsByRefLikeAttribute : Attribute
+ {
+ public IsByRefLikeAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsConst.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsConst.cs
new file mode 100644
index 0000000000..7f948b608a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsConst.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ public static partial class IsConst
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsReadOnlyAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsReadOnlyAttribute.cs
new file mode 100644
index 0000000000..657df43957
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsReadOnlyAttribute.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Reserved to be used by the compiler for tracking metadata.
+ /// This attribute should not be used by developers in source code.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [AttributeUsage(AttributeTargets.All, Inherited = false)]
+ public sealed class IsReadOnlyAttribute : Attribute
+ {
+ public IsReadOnlyAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsVolatile.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsVolatile.cs
new file mode 100644
index 0000000000..fd1c6a1b12
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IsVolatile.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ public static class IsVolatile
+ {
+ // no instantiation, please!
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IteratorStateMachineAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IteratorStateMachineAttribute.cs
new file mode 100644
index 0000000000..53afc95664
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/IteratorStateMachineAttribute.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
+ public sealed class IteratorStateMachineAttribute : StateMachineAttribute
+ {
+ public IteratorStateMachineAttribute(Type stateMachineType)
+ : base(stateMachineType)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/LoadHint.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/LoadHint.cs
new file mode 100644
index 0000000000..3820f8544b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/LoadHint.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ public enum LoadHint
+ {
+ Default = 0x0000, // No preference specified
+ Always = 0x0001, // Dependency is always loaded
+ Sometimes = 0x0002, // Dependency is sometimes loaded
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodCodeType.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodCodeType.cs
new file mode 100644
index 0000000000..841b666198
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodCodeType.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+
+namespace System.Runtime.CompilerServices
+{
+ public enum MethodCodeType
+ {
+ IL = MethodImplAttributes.IL,
+ Native = MethodImplAttributes.Native,
+ OPTIL = MethodImplAttributes.OPTIL,
+ Runtime = MethodImplAttributes.Runtime
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodImplAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodImplAttribute.cs
new file mode 100644
index 0000000000..8e8f93c268
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodImplAttribute.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ // Custom attribute to specify additional method properties.
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)]
+ sealed public class MethodImplAttribute : Attribute
+ {
+ public MethodCodeType MethodCodeType;
+
+ public MethodImplAttribute(MethodImplOptions methodImplOptions)
+ {
+ Value = methodImplOptions;
+ }
+
+ public MethodImplAttribute(short value)
+ {
+ Value = (MethodImplOptions)value;
+ }
+
+ public MethodImplAttribute()
+ {
+ }
+
+ public MethodImplOptions Value { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodImplOptions.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodImplOptions.cs
new file mode 100644
index 0000000000..2b5affc699
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/MethodImplOptions.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ // This Enum matchs the miImpl flags defined in corhdr.h. It is used to specify
+ // certain method properties.
+ [Flags]
+ public enum MethodImplOptions
+ {
+ Unmanaged = 0x0004,
+ NoInlining = 0x0008,
+ ForwardRef = 0x0010,
+ Synchronized = 0x0020,
+ NoOptimization = 0x0040,
+ PreserveSig = 0x0080,
+ AggressiveInlining = 0x0100,
+ InternalCall = 0x1000
+ }
+} \ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ReferenceAssemblyAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ReferenceAssemblyAttribute.cs
new file mode 100644
index 0000000000..f3842ec562
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ReferenceAssemblyAttribute.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Attribute: ReferenceAssemblyAttribute
+**
+** Purpose: Identifies an assembly as being a "reference
+** assembly", meaning it contains public surface area but
+** no usable implementation. Reference assemblies
+** should be loadable for introspection, but not execution.
+**
+============================================================*/
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
+ public sealed class ReferenceAssemblyAttribute : Attribute
+ {
+ public ReferenceAssemblyAttribute()
+ {
+ }
+
+ public ReferenceAssemblyAttribute(String description)
+ {
+ Description = description;
+ }
+
+ public String Description { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeCompatibilityAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeCompatibilityAttribute.cs
new file mode 100644
index 0000000000..609c560330
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeCompatibilityAttribute.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Mark up the program to indicate various legacy or new opt-in behaviors.
+**
+**
+=============================================================================*/
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
+ public sealed class RuntimeCompatibilityAttribute : Attribute
+ {
+ public RuntimeCompatibilityAttribute()
+ {
+ // legacy behavior is the default, and WrapNonExceptionThrows is implicitly
+ // false thanks to the CLR's guarantee of zeroed memory.
+ }
+
+ // If a non-CLSCompliant exception (i.e. one that doesn't derive from System.Exception) is
+ // thrown, should it be wrapped up in a System.Runtime.CompilerServices.RuntimeWrappedException
+ // instance when presented to catch handlers?
+ public bool WrapNonExceptionThrows { get; set; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeFeature.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeFeature.cs
new file mode 100644
index 0000000000..c6a97eca20
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeFeature.cs
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ public static class RuntimeFeature
+ {
+ /// <summary>
+ /// Name of the Portable PDB feature.
+ /// </summary>
+ public const string PortablePdb = nameof(PortablePdb);
+
+#if FEATURE_DEFAULT_INTERFACES
+ /// <summary>
+ /// Indicates that this version of runtime supports default interface method implementations.
+ /// </summary>
+ public const string DefaultImplementationsOfInterfaces = nameof(DefaultImplementationsOfInterfaces);
+#endif
+
+ /// <summary>
+ /// Checks whether a certain feature is supported by the Runtime.
+ /// </summary>
+ public static bool IsSupported(string feature)
+ {
+ switch (feature)
+ {
+ case PortablePdb:
+#if FEATURE_DEFAULT_INTERFACES
+ case DefaultImplementationsOfInterfaces:
+#endif
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeWrappedException.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeWrappedException.cs
new file mode 100644
index 0000000000..72996c6dfc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/RuntimeWrappedException.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Exception used to wrap all non-CLS compliant exceptions.
+ /// </summary>
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class RuntimeWrappedException : Exception
+ {
+ private object _wrappedException; // EE expects this name
+
+ // Not an api but has to be public as System.Linq.Expression invokes this through Reflection when an expression
+ // throws an object that doesn't derive from Exception.
+ public RuntimeWrappedException(object thrownObject)
+ : base(SR.RuntimeWrappedException)
+ {
+ HResult = HResults.COR_E_RUNTIMEWRAPPED;
+ _wrappedException = thrownObject;
+ }
+
+ private RuntimeWrappedException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ _wrappedException = info.GetValue("WrappedException", typeof(object));
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("WrappedException", _wrappedException, typeof(object));
+ }
+
+ public object WrappedException => _wrappedException;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/SpecialNameAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/SpecialNameAttribute.cs
new file mode 100644
index 0000000000..b18e62895f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/SpecialNameAttribute.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Struct)]
+ public sealed class SpecialNameAttribute : Attribute
+ {
+ public SpecialNameAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StateMachineAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StateMachineAttribute.cs
new file mode 100644
index 0000000000..e081d63e71
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StateMachineAttribute.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
+ public class StateMachineAttribute : Attribute
+ {
+ public StateMachineAttribute(Type stateMachineType)
+ {
+ StateMachineType = stateMachineType;
+ }
+
+ public Type StateMachineType { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StringFreezingAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StringFreezingAttribute.cs
new file mode 100644
index 0000000000..25a8bfbc26
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StringFreezingAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ // Custom attribute to indicate that strings should be frozen.
+
+ [AttributeUsage(AttributeTargets.Assembly, Inherited = false)]
+ public sealed class StringFreezingAttribute : Attribute
+ {
+ public StringFreezingAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StrongBox.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StrongBox.cs
new file mode 100644
index 0000000000..0a1a565f54
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/StrongBox.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Holds a reference to a value.
+ /// </summary>
+ /// <typeparam name="T">The type of the value that the <see cref = "StrongBox{T}"></see> references.</typeparam>
+ public class StrongBox<T> : IStrongBox
+ {
+ /// <summary>
+ /// Gets the strongly typed value associated with the <see cref = "StrongBox{T}"></see>
+ /// <remarks>This is explicitly exposed as a field instead of a property to enable loading the address of the field.</remarks>
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
+ public T Value;
+
+ /// <summary>
+ /// Initializes a new StrongBox which can receive a value when used in a reference call.
+ /// </summary>
+ public StrongBox()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new <see cref = "StrongBox{T}"></see> with the specified value.
+ /// </summary>
+ /// <param name="value">A value that the <see cref = "StrongBox{T}"></see> will reference.</param>
+ public StrongBox(T value)
+ {
+ Value = value;
+ }
+
+ object IStrongBox.Value
+ {
+ get
+ {
+ return Value;
+ }
+ set
+ {
+ Value = (T)value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Defines a property for accessing the value that an object references.
+ /// </summary>
+ public interface IStrongBox
+ {
+ /// <summary>
+ /// Gets or sets the value the object references.
+ /// </summary>
+ object Value { get; set; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/SuppressIldasmAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/SuppressIldasmAttribute.cs
new file mode 100644
index 0000000000..b4224b1c89
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/SuppressIldasmAttribute.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module)]
+ public sealed class SuppressIldasmAttribute : Attribute
+ {
+ public SuppressIldasmAttribute() { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TupleElementNamesAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TupleElementNamesAttribute.cs
new file mode 100644
index 0000000000..ad923dfae5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TupleElementNamesAttribute.cs
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Indicates that the use of <see cref="System.ValueTuple"/> on a member is meant to be treated as a tuple with element names.
+ /// </summary>
+ [CLSCompliant(false)]
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Event)]
+ public sealed class TupleElementNamesAttribute : Attribute
+ {
+ private readonly string[] _transformNames;
+
+ /// <summary>
+ /// Initializes a new instance of the <see
+ /// cref="TupleElementNamesAttribute"/> class.
+ /// </summary>
+ /// <param name="transformNames">
+ /// Specifies, in a pre-order depth-first traversal of a type's
+ /// construction, which <see cref="System.ValueType"/> occurrences are
+ /// meant to carry element names.
+ /// </param>
+ /// <remarks>
+ /// This constructor is meant to be used on types that contain an
+ /// instantiation of <see cref="System.ValueType"/> that contains
+ /// element names. For instance, if <c>C</c> is a generic type with
+ /// two type parameters, then a use of the constructed type <c>C{<see
+ /// cref="System.ValueTuple{T1, T2}"/>, <see
+ /// cref="System.ValueTuple{T1, T2, T3}"/></c> might be intended to
+ /// treat the first type argument as a tuple with element names and the
+ /// second as a tuple without element names. In which case, the
+ /// appropriate attribute specification should use a
+ /// <c>transformNames</c> value of <c>{ "name1", "name2", null, null,
+ /// null }</c>.
+ /// </remarks>
+ public TupleElementNamesAttribute(string[] transformNames)
+ {
+ if (transformNames == null)
+ {
+ throw new ArgumentNullException(nameof(transformNames));
+ }
+
+ _transformNames = transformNames;
+ }
+
+ /// <summary>
+ /// Specifies, in a pre-order depth-first traversal of a type's
+ /// construction, which <see cref="System.ValueTuple"/> elements are
+ /// meant to carry element names.
+ /// </summary>
+ public IList<string> TransformNames => _transformNames;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs
new file mode 100644
index 0000000000..27dd645755
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedFromAttribute.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false, AllowMultiple = false)]
+ public sealed class TypeForwardedFromAttribute : Attribute
+ {
+ public TypeForwardedFromAttribute(string assemblyFullName)
+ {
+ if (string.IsNullOrEmpty(assemblyFullName))
+ throw new ArgumentNullException(nameof(assemblyFullName));
+
+ AssemblyFullName = assemblyFullName;
+ }
+
+ public string AssemblyFullName { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedToAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedToAttribute.cs
new file mode 100644
index 0000000000..85d5c030c1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TypeForwardedToAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
+ public sealed class TypeForwardedToAttribute : Attribute
+ {
+ public TypeForwardedToAttribute(Type destination)
+ {
+ Destination = destination;
+ }
+
+ public Type Destination { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/UnsafeValueTypeAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/UnsafeValueTypeAttribute.cs
new file mode 100644
index 0000000000..f049c89b3f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/UnsafeValueTypeAttribute.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Struct)]
+ sealed public class UnsafeValueTypeAttribute : Attribute
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs
new file mode 100644
index 0000000000..02b5910b77
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs
@@ -0,0 +1,223 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Sources;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>Provides an awaiter for a <see cref="ValueTask"/>.</summary>
+ public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion
+#if CORECLR
+ , IStateMachineBoxAwareAwaiter
+#endif
+ {
+ /// <summary>Shim used to invoke an <see cref="Action"/> passed as the state argument to a <see cref="Action{Object}"/>.</summary>
+ internal static readonly Action<object> s_invokeActionDelegate = state =>
+ {
+ if (!(state is Action action))
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state);
+ return;
+ }
+
+ action();
+ };
+ /// <summary>The value being awaited.</summary>
+ private readonly ValueTask _value;
+
+ /// <summary>Initializes the awaiter.</summary>
+ /// <param name="value">The value to be awaited.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ValueTaskAwaiter(ValueTask value) => _value = value;
+
+ /// <summary>Gets whether the <see cref="ValueTask"/> has completed.</summary>
+ public bool IsCompleted
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _value.IsCompleted;
+ }
+
+ /// <summary>Gets the result of the ValueTask.</summary>
+ [StackTraceHidden]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void GetResult() => _value.ThrowIfCompletedUnsuccessfully();
+
+ /// <summary>Schedules the continuation action for this ValueTask.</summary>
+ public void OnCompleted(Action continuation)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj is Task t)
+ {
+ t.GetAwaiter().OnCompleted(continuation);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource>(obj).OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext);
+ }
+ else
+ {
+ ValueTask.CompletedTask.GetAwaiter().OnCompleted(continuation);
+ }
+ }
+
+ /// <summary>Schedules the continuation action for this ValueTask.</summary>
+ public void UnsafeOnCompleted(Action continuation)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj is Task t)
+ {
+ t.GetAwaiter().UnsafeOnCompleted(continuation);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource>(obj).OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext);
+ }
+ else
+ {
+ ValueTask.CompletedTask.GetAwaiter().UnsafeOnCompleted(continuation);
+ }
+ }
+
+#if CORECLR
+ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj is Task t)
+ {
+ TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext: true);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource>(obj).OnCompleted(s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext);
+ }
+ else
+ {
+ TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, continueOnCapturedContext: true);
+ }
+ }
+
+ /// <summary>Shim used to invoke <see cref="ITaskCompletionAction.Invoke"/> of the supplied <see cref="IAsyncStateMachineBox"/>.</summary>
+ internal static readonly Action<object> s_invokeAsyncStateMachineBox = state =>
+ {
+ if (!(state is IAsyncStateMachineBox box))
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state);
+ return;
+ }
+
+ box.MoveNext();
+ };
+#endif
+ }
+
+ /// <summary>Provides an awaiter for a <see cref="ValueTask{TResult}"/>.</summary>
+ public readonly struct ValueTaskAwaiter<TResult> : ICriticalNotifyCompletion
+#if CORECLR
+ , IStateMachineBoxAwareAwaiter
+#endif
+ {
+ /// <summary>The value being awaited.</summary>
+ private readonly ValueTask<TResult> _value;
+
+ /// <summary>Initializes the awaiter.</summary>
+ /// <param name="value">The value to be awaited.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ValueTaskAwaiter(ValueTask<TResult> value) => _value = value;
+
+ /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> has completed.</summary>
+ public bool IsCompleted
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _value.IsCompleted;
+ }
+
+ /// <summary>Gets the result of the ValueTask.</summary>
+ [StackTraceHidden]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TResult GetResult() => _value.Result;
+
+ /// <summary>Schedules the continuation action for this ValueTask.</summary>
+ public void OnCompleted(Action continuation)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj is Task<TResult> t)
+ {
+ t.GetAwaiter().OnCompleted(continuation);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource<TResult>>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext);
+ }
+ else
+ {
+ ValueTask.CompletedTask.GetAwaiter().OnCompleted(continuation);
+ }
+ }
+
+ /// <summary>Schedules the continuation action for this ValueTask.</summary>
+ public void UnsafeOnCompleted(Action continuation)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj is Task<TResult> t)
+ {
+ t.GetAwaiter().UnsafeOnCompleted(continuation);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource<TResult>>(obj).OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext);
+ }
+ else
+ {
+ ValueTask.CompletedTask.GetAwaiter().UnsafeOnCompleted(continuation);
+ }
+ }
+
+#if CORECLR
+ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
+ {
+ object obj = _value._obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj is Task<TResult> t)
+ {
+ TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext: true);
+ }
+ else if (obj != null)
+ {
+ Unsafe.As<IValueTaskSource<TResult>>(obj).OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext);
+ }
+ else
+ {
+ TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, continueOnCapturedContext: true);
+ }
+ }
+#endif
+ }
+
+#if CORECLR
+ /// <summary>Internal interface used to enable optimizations from <see cref="AsyncTaskMethodBuilder"/>.</summary>>
+ internal interface IStateMachineBoxAwareAwaiter
+ {
+ /// <summary>Invoked to set <see cref="ITaskCompletionAction.Invoke"/> of the <paramref name="box"/> as the awaiter's continuation.</summary>
+ /// <param name="box">The box object.</param>
+ void AwaitUnsafeOnCompleted(IAsyncStateMachineBox box);
+ }
+#endif
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/YieldAwaitable.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/YieldAwaitable.cs
new file mode 100644
index 0000000000..8aed0fcd5a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/YieldAwaitable.cs
@@ -0,0 +1,202 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+//
+//
+// Compiler-targeted type for switching back into the current execution context, e.g.
+//
+// await Task.Yield();
+// =====================
+// var $awaiter = Task.Yield().GetAwaiter();
+// if (!$awaiter.IsCompleted)
+// {
+// $builder.AwaitUnsafeOnCompleted(ref $awaiter, ref this);
+// return;
+// Label:
+// }
+// $awaiter.GetResult();
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System;
+using System.Security;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Runtime.CompilerServices
+{
+ // NOTE: YieldAwaitable currently has no state; while developers are encouraged to use Task.Yield() to produce one,
+ // no validation is performed to ensure that the developer isn't doing "await new YieldAwaitable()". Such validation
+ // would require additional, useless state to be stored, and as this is a type in the CompilerServices namespace, and
+ // as the above example isn't harmful, we take the cheaper approach of not validating anything.
+
+ /// <summary>Provides an awaitable context for switching into a target environment.</summary>
+ /// <remarks>This type is intended for compiler use only.</remarks>
+ public readonly struct YieldAwaitable
+ {
+ /// <summary>Gets an awaiter for this <see cref="YieldAwaitable"/>.</summary>
+ /// <returns>An awaiter for this awaitable.</returns>
+ /// <remarks>This method is intended for compiler user rather than use directly in code.</remarks>
+ public YieldAwaiter GetAwaiter() { return new YieldAwaiter(); }
+
+ /// <summary>Provides an awaiter that switches into a target environment.</summary>
+ /// <remarks>This type is intended for compiler use only.</remarks>
+ public readonly struct YieldAwaiter : ICriticalNotifyCompletion
+#if CORECLR
+ , IStateMachineBoxAwareAwaiter
+#endif
+ {
+ /// <summary>Gets whether a yield is not required.</summary>
+ /// <remarks>This property is intended for compiler user rather than use directly in code.</remarks>
+ public bool IsCompleted { get { return false; } } // yielding is always required for YieldAwaiter, hence false
+
+ /// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
+ /// <param name="continuation">The action to invoke asynchronously.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
+ public void OnCompleted(Action continuation)
+ {
+ QueueContinuation(continuation, flowContext: true);
+ }
+
+ /// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
+ /// <param name="continuation">The action to invoke asynchronously.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
+ public void UnsafeOnCompleted(Action continuation)
+ {
+ QueueContinuation(continuation, flowContext: false);
+ }
+
+ /// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
+ /// <param name="continuation">The action to invoke asynchronously.</param>
+ /// <param name="flowContext">true to flow ExecutionContext; false if flowing is not required.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
+ private static void QueueContinuation(Action continuation, bool flowContext)
+ {
+ // Validate arguments
+ if (continuation == null) throw new ArgumentNullException(nameof(continuation));
+
+ if (TplEtwProvider.Log.IsEnabled())
+ {
+ continuation = OutputCorrelationEtwEvent(continuation);
+ }
+ // Get the current SynchronizationContext, and if there is one,
+ // post the continuation to it. However, treat the base type
+ // as if there wasn't a SynchronizationContext, since that's what it
+ // logically represents.
+ var syncCtx = SynchronizationContext.Current;
+ if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
+ {
+ syncCtx.Post(s_sendOrPostCallbackRunAction, continuation);
+ }
+ else
+ {
+ // If we're targeting the default scheduler, queue to the thread pool, so that we go into the global
+ // queue. As we're going into the global queue, we might as well use QUWI, which for the global queue is
+ // just a tad faster than task, due to a smaller object getting allocated and less work on the execution path.
+ TaskScheduler scheduler = TaskScheduler.Current;
+ if (scheduler == TaskScheduler.Default)
+ {
+ if (flowContext)
+ {
+ ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
+ }
+ else
+ {
+ ThreadPool.UnsafeQueueUserWorkItem(s_waitCallbackRunAction, continuation);
+ }
+ }
+ // We're targeting a custom scheduler, so queue a task.
+ else
+ {
+ Task.Factory.StartNew(continuation, default(CancellationToken), TaskCreationOptions.PreferFairness, scheduler);
+ }
+ }
+ }
+
+#if CORECLR
+ void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
+ {
+ Debug.Assert(box != null);
+
+ // If tracing is enabled, delegate the Action-based implementation.
+ if (TplEtwProvider.Log.IsEnabled())
+ {
+ QueueContinuation(box.MoveNextAction, flowContext: false);
+ return;
+ }
+
+ // Otherwise, this is the same logic as in QueueContinuation, except using
+ // an IAsyncStateMachineBox instead of an Action, and only for flowContext:false.
+
+ SynchronizationContext syncCtx = SynchronizationContext.Current;
+ if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
+ {
+ syncCtx.Post(s => ((IAsyncStateMachineBox)s).MoveNext(), box);
+ }
+ else
+ {
+ TaskScheduler scheduler = TaskScheduler.Current;
+ if (scheduler == TaskScheduler.Default)
+ {
+ ThreadPool.UnsafeQueueUserWorkItem(s => ((IAsyncStateMachineBox)s).MoveNext(), box);
+ }
+ else
+ {
+ Task.Factory.StartNew(s => ((IAsyncStateMachineBox)s).MoveNext(), box, default, TaskCreationOptions.PreferFairness, scheduler);
+ }
+ }
+ }
+#endif
+
+ private static Action OutputCorrelationEtwEvent(Action continuation)
+ {
+#if CORERT
+ // TODO
+ return continuation;
+#else
+ int continuationId = Task.NewId();
+ Task currentTask = Task.InternalCurrent;
+ // fire the correlation ETW event
+ TplEtwProvider.Log.AwaitTaskContinuationScheduled(TaskScheduler.Current.Id, (currentTask != null) ? currentTask.Id : 0, continuationId);
+
+ return AsyncMethodBuilderCore.CreateContinuationWrapper(continuation, (innerContinuation,continuationIdTask) =>
+ {
+ var etwLog = TplEtwProvider.Log;
+ etwLog.TaskWaitContinuationStarted(((Task<int>)continuationIdTask).Result);
+
+ // ETW event for Task Wait End.
+ Guid prevActivityId = new Guid();
+ // Ensure the continuation runs under the correlated activity ID generated above
+ if (etwLog.TasksSetActivityIds)
+ EventSource.SetCurrentThreadActivityId(TplEtwProvider.CreateGuidForTaskID(((Task<int>)continuationIdTask).Result), out prevActivityId);
+
+ // Invoke the original continuation provided to OnCompleted.
+ innerContinuation();
+ // Restore activity ID
+
+ if (etwLog.TasksSetActivityIds)
+ EventSource.SetCurrentThreadActivityId(prevActivityId);
+
+ etwLog.TaskWaitContinuationComplete(((Task<int>)continuationIdTask).Result);
+ }, Task.FromResult(continuationId)); // pass the ID in a task to avoid a closure\
+#endif
+ }
+
+ /// <summary>WaitCallback that invokes the Action supplied as object state.</summary>
+ private static readonly WaitCallback s_waitCallbackRunAction = RunAction;
+ /// <summary>SendOrPostCallback that invokes the Action supplied as object state.</summary>
+ private static readonly SendOrPostCallback s_sendOrPostCallbackRunAction = RunAction;
+ /// <summary>Runs an Action delegate provided as state.</summary>
+ /// <param name="state">The Action delegate to invoke.</param>
+ private static void RunAction(object state) { ((Action)state)(); }
+
+ /// <summary>Ends the await operation.</summary>
+ public void GetResult() { } // Nop. It exists purely because the compiler pattern demands it.
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/Cer.cs b/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/Cer.cs
new file mode 100644
index 0000000000..77ab3ea770
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/Cer.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.ConstrainedExecution
+{
+ public enum Cer : int
+ {
+ None = 0,
+ MayFail = 1, // Might fail, but the method will say it failed
+ Success = 2,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/Consistency.cs b/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/Consistency.cs
new file mode 100644
index 0000000000..e2cc79ec35
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/Consistency.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.ConstrainedExecution
+{
+ public enum Consistency : int
+ {
+ MayCorruptProcess = 0,
+ MayCorruptAppDomain = 1,
+ MayCorruptInstance = 2,
+ WillNotCorruptState = 3,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/CriticalFinalizerObject.cs b/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/CriticalFinalizerObject.cs
new file mode 100644
index 0000000000..3f35f816a3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/CriticalFinalizerObject.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+** Deriving from this class will cause any finalizer you define to be critical
+** (i.e. the finalizer is guaranteed to run, won't be aborted by the host and is
+** run after the finalizers of other objects collected at the same time).
+**
+**
+===========================================================*/
+
+namespace System.Runtime.ConstrainedExecution
+{
+ public abstract class CriticalFinalizerObject
+ {
+ protected CriticalFinalizerObject()
+ {
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1821:RemoveEmptyFinalizers")]
+ ~CriticalFinalizerObject()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/ReliabilityContractAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/ReliabilityContractAttribute.cs
new file mode 100644
index 0000000000..b3cb0143fa
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/ConstrainedExecution/ReliabilityContractAttribute.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+/*============================================================
+**
+**
+**
+** Purpose: Defines a publically documentable contract for
+** reliability between a method and its callers, expressing
+** what state will remain consistent in the presence of
+** failures (ie async exceptions like thread abort) and whether
+** the method needs to be called from within a CER.
+**
+**
+===========================================================*/
+
+namespace System.Runtime.ConstrainedExecution
+{
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Interface /* | AttributeTargets.Delegate*/, Inherited = false)]
+ public sealed class ReliabilityContractAttribute : Attribute
+ {
+ public ReliabilityContractAttribute(Consistency consistencyGuarantee, Cer cer)
+ {
+ ConsistencyGuarantee = consistencyGuarantee;
+ Cer = cer;
+ }
+
+ public Consistency ConsistencyGuarantee { get; }
+ public Cer Cer { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionNotification.cs b/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionNotification.cs
new file mode 100644
index 0000000000..605588d657
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionNotification.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.ExceptionServices
+{
+ // Definition of the argument-type passed to the FirstChanceException event handler
+ public class FirstChanceExceptionEventArgs : EventArgs
+ {
+ public FirstChanceExceptionEventArgs(Exception exception)
+ {
+ Exception = exception;
+ }
+
+ // Returns the exception object pertaining to the first chance exception
+ public Exception Exception { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/HandleProcessCorruptedStateExceptionsAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/HandleProcessCorruptedStateExceptionsAttribute.cs
new file mode 100644
index 0000000000..cc1bc81e5a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/HandleProcessCorruptedStateExceptionsAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.ExceptionServices
+{
+ // This attribute can be applied to methods to indicate that ProcessCorruptedState
+ // Exceptions should be delivered to them.
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+ public sealed class HandleProcessCorruptedStateExceptionsAttribute : Attribute
+ {
+ public HandleProcessCorruptedStateExceptionsAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/BestFitMappingAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/BestFitMappingAttribute.cs
new file mode 100644
index 0000000000..4ebee1538c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/BestFitMappingAttribute.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
+ public sealed class BestFitMappingAttribute : Attribute
+ {
+ public BestFitMappingAttribute(bool BestFitMapping)
+ {
+ this.BestFitMapping = BestFitMapping;
+ }
+
+ public bool BestFitMapping { get; }
+
+ public bool ThrowOnUnmappableChar;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/CallingConvention.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/CallingConvention.cs
new file mode 100644
index 0000000000..3b18fdee3a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/CallingConvention.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ // Used for the CallingConvention named argument to the DllImport and NativeCallable attribute
+ public enum CallingConvention
+ {
+ Winapi = 1,
+ Cdecl = 2,
+ StdCall = 3,
+ ThisCall = 4,
+ FastCall = 5,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/CharSet.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/CharSet.cs
new file mode 100644
index 0000000000..d587ec006b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/CharSet.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ // Use this in P/Invoke function prototypes to specify
+ // which character set to use when marshalling Strings.
+ // Using Ansi will marshal the strings as 1 byte char*'s.
+ // Using Unicode will marshal the strings as 2 byte wchar*'s.
+ // Generally you probably want to use Auto, which does the
+ // right thing 99% of the time.
+
+ public enum CharSet
+ {
+ None = 1, // User didn't specify how to marshal strings.
+ Ansi = 2, // Strings should be marshalled as ANSI 1 byte chars.
+ Unicode = 3, // Strings should be marshalled as Unicode 2 byte chars.
+ Auto = 4, // Marshal Strings in the right way for the target system.
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/ComVisibleAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/ComVisibleAttribute.cs
new file mode 100644
index 0000000000..84b9505a5a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/ComVisibleAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Delegate | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, Inherited = false)]
+ public sealed class ComVisibleAttribute : Attribute
+ {
+ public ComVisibleAttribute(bool visibility)
+ {
+ Value = visibility;
+ }
+
+ public bool Value { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DefaultCharSetAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DefaultCharSetAttribute.cs
new file mode 100644
index 0000000000..7a486f7017
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DefaultCharSetAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Module, Inherited = false)]
+ public sealed class DefaultCharSetAttribute : Attribute
+ {
+ public DefaultCharSetAttribute(CharSet charSet)
+ {
+ CharSet = charSet;
+ }
+
+ public CharSet CharSet { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DefaultDllImportSearchPathsAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DefaultDllImportSearchPathsAttribute.cs
new file mode 100644
index 0000000000..1ff27fbbd5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DefaultDllImportSearchPathsAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Method, AllowMultiple = false)]
+ public sealed class DefaultDllImportSearchPathsAttribute : Attribute
+ {
+ public DefaultDllImportSearchPathsAttribute(DllImportSearchPath paths)
+ {
+ Paths = paths;
+ }
+
+ public DllImportSearchPath Paths { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DllImportAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DllImportAttribute.cs
new file mode 100644
index 0000000000..97f870d49c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DllImportAttribute.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public sealed class DllImportAttribute : Attribute
+ {
+ public DllImportAttribute(string dllName)
+ {
+ Value = dllName;
+ }
+
+ public string Value { get; }
+
+ public string EntryPoint;
+ public CharSet CharSet;
+ public bool SetLastError;
+ public bool ExactSpelling;
+ public CallingConvention CallingConvention;
+ public bool BestFitMapping;
+ public bool PreserveSig;
+ public bool ThrowOnUnmappableChar;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DllImportSearchPath.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DllImportSearchPath.cs
new file mode 100644
index 0000000000..8dbdb40be9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/DllImportSearchPath.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [Flags]
+ public enum DllImportSearchPath
+ {
+ UseDllDirectoryForDependencies = 0x100,
+ ApplicationDirectory = 0x200,
+ UserDirectories = 0x400,
+ System32 = 0x800,
+ SafeDirectories = 0x1000,
+ AssemblyDirectory = 0x2,
+ LegacyBehavior = 0x0
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/ExternalException.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/ExternalException.cs
new file mode 100644
index 0000000000..160fe301e8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/ExternalException.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception base class for all errors from Interop or Structured
+** Exception Handling code.
+**
+**
+=============================================================================*/
+
+using System;
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System.Runtime.InteropServices
+{
+ // Base exception for COM Interop errors &; Structured Exception Handler
+ // exceptions.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ExternalException : SystemException
+ {
+ public ExternalException()
+ : base(SR.Arg_ExternalException)
+ {
+ HResult = HResults.E_FAIL;
+ }
+
+ public ExternalException(string message)
+ : base(message)
+ {
+ HResult = HResults.E_FAIL;
+ }
+
+ public ExternalException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.E_FAIL;
+ }
+
+ public ExternalException(string message, int errorCode)
+ : base(message)
+ {
+ HResult = errorCode;
+ }
+
+ protected ExternalException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+
+ public virtual int ErrorCode
+ {
+ get
+ {
+ return HResult;
+ }
+ }
+
+ public override string ToString()
+ {
+ string message = Message;
+ string className = GetType().ToString();
+
+ string s = className + " (0x" + HResult.ToString("X8", CultureInfo.InvariantCulture) + ")";
+
+ if (!(String.IsNullOrEmpty(message)))
+ {
+ s = s + ": " + message;
+ }
+
+ Exception innerException = InnerException;
+
+ if (innerException != null)
+ {
+ s = s + " ---> " + innerException.ToString();
+ }
+
+ if (StackTrace != null)
+ s += Environment.NewLine + StackTrace;
+
+ return s;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/FieldOffsetAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/FieldOffsetAttribute.cs
new file mode 100644
index 0000000000..27e1097749
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/FieldOffsetAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class FieldOffsetAttribute : Attribute
+ {
+ public FieldOffsetAttribute(int offset)
+ {
+ Value = offset;
+ }
+
+ public int Value { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/GuidAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/GuidAttribute.cs
new file mode 100644
index 0000000000..cf60b9bf70
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/GuidAttribute.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct | AttributeTargets.Delegate, Inherited = false)]
+ public sealed class GuidAttribute : Attribute
+ {
+ public GuidAttribute(string guid)
+ {
+ Value = guid;
+ }
+
+ public string Value { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/HandleRef.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/HandleRef.cs
new file mode 100644
index 0000000000..64d0553130
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/HandleRef.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ public struct HandleRef
+ {
+ // ! Do not add or rearrange fields as the EE depends on this layout.
+ //------------------------------------------------------------------
+ private object _wrapper;
+ private IntPtr _handle;
+ //------------------------------------------------------------------
+
+ public HandleRef(object wrapper, IntPtr handle)
+ {
+ _wrapper = wrapper;
+ _handle = handle;
+ }
+
+ public object Wrapper
+ {
+ get
+ {
+ return _wrapper;
+ }
+ }
+
+ public IntPtr Handle
+ {
+ get
+ {
+ return _handle;
+ }
+ }
+
+ public static explicit operator IntPtr(HandleRef value)
+ {
+ return value._handle;
+ }
+
+ public static IntPtr ToIntPtr(HandleRef value)
+ {
+ return value._handle;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/InAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/InAttribute.cs
new file mode 100644
index 0000000000..39f5a958bc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/InAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ public sealed class InAttribute : Attribute
+ {
+ public InAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/LayoutKind.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/LayoutKind.cs
new file mode 100644
index 0000000000..dbd7ec62d5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/LayoutKind.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ // Used in the StructLayoutAttribute class
+ public enum LayoutKind
+ {
+ Sequential = 0,
+ Explicit = 2,
+ Auto = 3,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MarshalAsAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MarshalAsAttribute.cs
new file mode 100644
index 0000000000..4a64050ed1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MarshalAsAttribute.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.ReturnValue, Inherited = false)]
+ public sealed class MarshalAsAttribute : Attribute
+ {
+ public MarshalAsAttribute(UnmanagedType unmanagedType)
+ {
+ Value = unmanagedType;
+ }
+ public MarshalAsAttribute(short unmanagedType)
+ {
+ Value = (UnmanagedType)unmanagedType;
+ }
+
+ public UnmanagedType Value { get; }
+
+ // Fields used with SubType = SafeArray.
+ public VarEnum SafeArraySubType;
+ public Type SafeArrayUserDefinedSubType;
+
+ // Field used with iid_is attribute (interface pointers).
+ public int IidParameterIndex;
+
+ // Fields used with SubType = ByValArray and LPArray.
+ // Array size = parameter(PI) * PM + C
+ public UnmanagedType ArraySubType;
+ public short SizeParamIndex; // param index PI
+ public int SizeConst; // constant C
+
+ // Fields used with SubType = CustomMarshaler
+ public string MarshalType; // Name of marshaler class
+ public Type MarshalTypeRef; // Type of marshaler class
+ public string MarshalCookie; // cookie to pass to marshaler
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MarshalDirectiveException.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MarshalDirectiveException.cs
new file mode 100644
index 0000000000..1d0d59fab6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MarshalDirectiveException.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+** Purpose: This exception is thrown when the marshaller encounters a signature
+** that has an invalid MarshalAs CA for a given argument or is not
+** supported.
+**
+=============================================================================*/
+
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.Runtime.InteropServices
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class MarshalDirectiveException : SystemException
+ {
+ public MarshalDirectiveException()
+ : base(SR.Arg_MarshalDirectiveException)
+ {
+ HResult = HResults.COR_E_MARSHALDIRECTIVE;
+ }
+
+ public MarshalDirectiveException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_MARSHALDIRECTIVE;
+ }
+
+ public MarshalDirectiveException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_MARSHALDIRECTIVE;
+ }
+
+ protected MarshalDirectiveException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs
new file mode 100644
index 0000000000..80ae450b51
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs
@@ -0,0 +1,232 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Collections.Generic;
+using Internal.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices
+{
+ /// <summary>
+ /// Provides a collection of methods for interoperating with <see cref="Memory{T}"/>, <see cref="ReadOnlyMemory{T}"/>,
+ /// <see cref="Span{T}"/>, and <see cref="ReadOnlySpan{T}"/>.
+ /// </summary>
+ public static partial class MemoryMarshal
+ {
+ /// <summary>
+ /// Casts a Span of one primitive type <typeparamref name="T"/> to Span of bytes.
+ /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ /// </summary>
+ /// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when <typeparamref name="T"/> contains pointers.
+ /// </exception>
+ /// <exception cref="System.OverflowException">
+ /// Thrown if the Length property of the new Span would exceed Int32.MaxValue.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<byte> AsBytes<T>(Span<T> span)
+ where T : struct
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+
+ return new Span<byte>(
+ ref Unsafe.As<T, byte>(ref GetReference(span)),
+ checked(span.Length * Unsafe.SizeOf<T>()));
+ }
+
+ /// <summary>
+ /// Casts a ReadOnlySpan of one primitive type <typeparamref name="T"/> to ReadOnlySpan of bytes.
+ /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ /// </summary>
+ /// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when <typeparamref name="T"/> contains pointers.
+ /// </exception>
+ /// <exception cref="System.OverflowException">
+ /// Thrown if the Length property of the new Span would exceed Int32.MaxValue.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<byte> AsBytes<T>(ReadOnlySpan<T> span)
+ where T : struct
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+
+ return new ReadOnlySpan<byte>(
+ ref Unsafe.As<T, byte>(ref GetReference(span)),
+ checked(span.Length * Unsafe.SizeOf<T>()));
+ }
+
+ /// <summary>Creates a <see cref="Memory{T}"/> from a <see cref="ReadOnlyMemory{T}"/>.</summary>
+ /// <param name="memory">The <see cref="ReadOnlyMemory{T}"/>.</param>
+ /// <returns>A <see cref="Memory{T}"/> representing the same memory as the <see cref="ReadOnlyMemory{T}"/>, but writable.</returns>
+ /// <remarks>
+ /// <see cref="AsMemory{T}(ReadOnlyMemory{T})"/> must be used with extreme caution. <see cref="ReadOnlyMemory{T}"/> is used
+ /// to represent immutable data and other memory that is not meant to be written to; <see cref="Memory{T}"/> instances created
+ /// by <see cref="AsMemory{T}(ReadOnlyMemory{T})"/> should not be written to. The method exists to enable variables typed
+ /// as <see cref="Memory{T}"/> but only used for reading to store a <see cref="ReadOnlyMemory{T}"/>.
+ /// </remarks>
+ public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> memory) =>
+ Unsafe.As<ReadOnlyMemory<T>, Memory<T>>(ref memory);
+
+ /// <summary>
+ /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
+ /// would have been stored. Such a reference may or may not be null. It can be used for pinning but must never be dereferenced.
+ /// </summary>
+ public static ref T GetReference<T>(Span<T> span) => ref span._pointer.Value;
+
+ /// <summary>
+ /// Returns a reference to the 0th element of the ReadOnlySpan. If the ReadOnlySpan is empty, returns a reference to the location where the 0th element
+ /// would have been stored. Such a reference may or may not be null. It can be used for pinning but must never be dereferenced.
+ /// </summary>
+ public static ref T GetReference<T>(ReadOnlySpan<T> span) => ref span._pointer.Value;
+
+ /// <summary>
+ /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to fake non-null pointer. Such a reference can be used
+ /// for pinning but must never be dereferenced. This is useful for interop with methods that do not accept null pointers for zero-sized buffers.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe ref T GetNonNullPinnableReference<T>(Span<T> span) => ref (span.Length != 0) ? ref span._pointer.Value : ref Unsafe.AsRef<T>((void*)1);
+
+ /// <summary>
+ /// Returns a reference to the 0th element of the ReadOnlySpan. If the ReadOnlySpan is empty, returns a reference to fake non-null pointer. Such a reference
+ /// can be used for pinning but must never be dereferenced. This is useful for interop with methods that do not accept null pointers for zero-sized buffers.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe ref T GetNonNullPinnableReference<T>(ReadOnlySpan<T> span) => ref (span.Length != 0) ? ref span._pointer.Value : ref Unsafe.AsRef<T>((void*)1);
+
+ /// <summary>
+ /// Casts a Span of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
+ /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ /// </summary>
+ /// <remarks>
+ /// Supported only for platforms that support misaligned memory access.
+ /// </remarks>
+ /// <param name="span">The source slice, of type <typeparamref name="TFrom"/>.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span)
+ where TFrom : struct
+ where TTo : struct
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TFrom>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom));
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TTo>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo));
+
+ // Use unsigned integers - unsigned division by constant (especially by power of 2)
+ // and checked casts are faster and smaller.
+ uint fromSize = (uint)Unsafe.SizeOf<TFrom>();
+ uint toSize = (uint)Unsafe.SizeOf<TTo>();
+ uint fromLength = (uint)span.Length;
+ int toLength;
+ if (fromSize == toSize)
+ {
+ // Special case for same size types - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize`
+ // should be optimized to just `length` but the JIT doesn't do that today.
+ toLength = (int)fromLength;
+ }
+ else if (fromSize == 1)
+ {
+ // Special case for byte sized TFrom - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize`
+ // becomes `(ulong)fromLength / (ulong)toSize` but the JIT can't narrow it down to `int`
+ // and can't eliminate the checked cast. This also avoids a 32 bit specific issue,
+ // the JIT can't eliminate long multiply by 1.
+ toLength = (int)(fromLength / toSize);
+ }
+ else
+ {
+ // Ensure that casts are done in such a way that the JIT is able to "see"
+ // the uint->ulong casts and the multiply together so that on 32 bit targets
+ // 32x32to64 multiplication is used.
+ ulong toLengthUInt64 = (ulong)fromLength * (ulong)fromSize / (ulong)toSize;
+ toLength = checked((int)toLengthUInt64);
+ }
+
+ return new Span<TTo>(
+ ref Unsafe.As<TFrom, TTo>(ref span._pointer.Value),
+ toLength);
+ }
+
+ /// <summary>
+ /// Casts a ReadOnlySpan of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
+ /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ /// </summary>
+ /// <remarks>
+ /// Supported only for platforms that support misaligned memory access.
+ /// </remarks>
+ /// <param name="span">The source slice, of type <typeparamref name="TFrom"/>.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span)
+ where TFrom : struct
+ where TTo : struct
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TFrom>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom));
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TTo>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo));
+
+ // Use unsigned integers - unsigned division by constant (especially by power of 2)
+ // and checked casts are faster and smaller.
+ uint fromSize = (uint)Unsafe.SizeOf<TFrom>();
+ uint toSize = (uint)Unsafe.SizeOf<TTo>();
+ uint fromLength = (uint)span.Length;
+ int toLength;
+ if (fromSize == toSize)
+ {
+ // Special case for same size types - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize`
+ // should be optimized to just `length` but the JIT doesn't do that today.
+ toLength = (int)fromLength;
+ }
+ else if (fromSize == 1)
+ {
+ // Special case for byte sized TFrom - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize`
+ // becomes `(ulong)fromLength / (ulong)toSize` but the JIT can't narrow it down to `int`
+ // and can't eliminate the checked cast. This also avoids a 32 bit specific issue,
+ // the JIT can't eliminate long multiply by 1.
+ toLength = (int)(fromLength / toSize);
+ }
+ else
+ {
+ // Ensure that casts are done in such a way that the JIT is able to "see"
+ // the uint->ulong casts and the multiply together so that on 32 bit targets
+ // 32x32to64 multiplication is used.
+ ulong toLengthUInt64 = (ulong)fromLength * (ulong)fromSize / (ulong)toSize;
+ toLength = checked((int)toLengthUInt64);
+ }
+
+ return new ReadOnlySpan<TTo>(
+ ref Unsafe.As<TFrom, TTo>(ref MemoryMarshal.GetReference(span)),
+ toLength);
+ }
+
+ /// <summary>
+ /// Create a new span over a portion of a regular managed object. This can be useful
+ /// if part of a managed object represents a "fixed array." This is dangerous because the
+ /// <paramref name="length"/> is not checked.
+ /// </summary>
+ /// <param name="reference">A reference to data.</param>
+ /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span<T> CreateSpan<T>(ref T reference, int length) => new Span<T>(ref reference, length);
+
+ /// <summary>
+ /// Create a new read-only span over a portion of a regular managed object. This can be useful
+ /// if part of a managed object represents a "fixed array." This is dangerous because the
+ /// <paramref name="length"/> is not checked.
+ /// </summary>
+ /// <param name="reference">A reference to data.</param>
+ /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length) => new ReadOnlySpan<T>(ref reference, length);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs
new file mode 100644
index 0000000000..8eec0fd07c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs
@@ -0,0 +1,281 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+namespace System.Runtime.InteropServices
+{
+ /// <summary>
+ /// Provides a collection of methods for interoperating with <see cref="Memory{T}"/>, <see cref="ReadOnlyMemory{T}"/>,
+ /// <see cref="Span{T}"/>, and <see cref="ReadOnlySpan{T}"/>.
+ /// </summary>
+ public static partial class MemoryMarshal
+ {
+ /// <summary>
+ /// Get an array segment from the underlying memory.
+ /// If unable to get the array segment, return false with a default array segment.
+ /// </summary>
+ public static bool TryGetArray<T>(ReadOnlyMemory<T> memory, out ArraySegment<T> segment)
+ {
+ object obj = memory.GetObjectStartLength(out int index, out int length);
+ if (index < 0)
+ {
+ Debug.Assert(length >= 0);
+ if (((MemoryManager<T>)obj).TryGetArray(out ArraySegment<T> arraySegment))
+ {
+ segment = new ArraySegment<T>(arraySegment.Array, arraySegment.Offset + (index & ReadOnlyMemory<T>.RemoveFlagsBitMask), length);
+ return true;
+ }
+ }
+ else if (obj is T[] arr)
+ {
+ segment = new ArraySegment<T>(arr, index, length & ReadOnlyMemory<T>.RemoveFlagsBitMask);
+ return true;
+ }
+
+ if ((length & ReadOnlyMemory<T>.RemoveFlagsBitMask) == 0)
+ {
+#if FEATURE_PORTABLE_SPAN
+ segment = new ArraySegment<T>(SpanHelpers.PerTypeValues<T>.EmptyArray);
+#else
+ segment = ArraySegment<T>.Empty;
+#endif // FEATURE_PORTABLE_SPAN
+ return true;
+ }
+
+ segment = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Gets an <see cref="MemoryManager{T}"/> from the underlying read-only memory.
+ /// If unable to get the <typeparamref name="TManager"/> type, returns false.
+ /// </summary>
+ /// <typeparam name="T">The element type of the <paramref name="memory" />.</typeparam>
+ /// <typeparam name="TManager">The type of <see cref="MemoryManager{T}"/> to try and retrive.</typeparam>
+ /// <param name="memory">The memory to get the manager for.</param>
+ /// <param name="manager">The returned manager of the <see cref="ReadOnlyMemory{T}"/>.</param>
+ /// <returns>A <see cref="bool"/> indicating if it was successful.</returns>
+ public static bool TryGetMemoryManager<T, TManager>(ReadOnlyMemory<T> memory, out TManager manager)
+ where TManager : MemoryManager<T>
+ {
+ TManager localManager; // Use register for null comparison rather than byref
+ manager = localManager = memory.GetObjectStartLength(out _, out _) as TManager;
+ return !ReferenceEquals(manager, null);
+ }
+
+ /// <summary>
+ /// Gets an <see cref="MemoryManager{T}"/> and <paramref name="start" />, <paramref name="length" /> from the underlying read-only memory.
+ /// If unable to get the <typeparamref name="TManager"/> type, returns false.
+ /// </summary>
+ /// <typeparam name="T">The element type of the <paramref name="memory" />.</typeparam>
+ /// <typeparam name="TManager">The type of <see cref="MemoryManager{T}"/> to try and retrive.</typeparam>
+ /// <param name="memory">The memory to get the manager for.</param>
+ /// <param name="manager">The returned manager of the <see cref="ReadOnlyMemory{T}"/>.</param>
+ /// <param name="start">The offset from the start of the <paramref name="manager" /> that the <paramref name="memory" /> represents.</param>
+ /// <param name="length">The length of the <paramref name="manager" /> that the <paramref name="memory" /> represents.</param>
+ /// <returns>A <see cref="bool"/> indicating if it was successful.</returns>
+ public static bool TryGetMemoryManager<T, TManager>(ReadOnlyMemory<T> memory, out TManager manager, out int start, out int length)
+ where TManager : MemoryManager<T>
+ {
+ TManager localManager; // Use register for null comparison rather than byref
+ manager = localManager = memory.GetObjectStartLength(out start, out length) as TManager;
+ start &= ReadOnlyMemory<T>.RemoveFlagsBitMask;
+
+ Debug.Assert(length >= 0);
+
+ if (ReferenceEquals(manager, null))
+ {
+ start = default;
+ length = default;
+ return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Creates an <see cref="IEnumerable{T}"/> view of the given <paramref name="memory" /> to allow
+ /// the <paramref name="memory" /> to be used in existing APIs that take an <see cref="IEnumerable{T}"/>.
+ /// </summary>
+ /// <typeparam name="T">The element type of the <paramref name="memory" />.</typeparam>
+ /// <param name="memory">The ReadOnlyMemory to view as an <see cref="IEnumerable{T}"/></param>
+ /// <returns>An <see cref="IEnumerable{T}"/> view of the given <paramref name="memory" /></returns>
+ public static IEnumerable<T> ToEnumerable<T>(ReadOnlyMemory<T> memory)
+ {
+ for (int i = 0; i < memory.Length; i++)
+ yield return memory.Span[i];
+ }
+
+ /// <summary>Attempts to get the underlying <see cref="string"/> from a <see cref="ReadOnlyMemory{T}"/>.</summary>
+ /// <param name="memory">The memory that may be wrapping a <see cref="string"/> object.</param>
+ /// <param name="text">The string.</param>
+ /// <param name="start">The starting location in <paramref name="text"/>.</param>
+ /// <param name="length">The number of items in <paramref name="text"/>.</param>
+ /// <returns></returns>
+ public static bool TryGetString(ReadOnlyMemory<char> memory, out string text, out int start, out int length)
+ {
+ if (memory.GetObjectStartLength(out int offset, out int count) is string s)
+ {
+ Debug.Assert(offset >= 0);
+ Debug.Assert(count >= 0);
+ text = s;
+ start = offset;
+ length = count;
+ return true;
+ }
+ else
+ {
+ text = null;
+ start = 0;
+ length = 0;
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Reads a structure of type T out of a read-only span of bytes.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Read<T>(ReadOnlySpan<byte> source)
+ where T : struct
+ {
+#if netstandard
+ if (SpanHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T));
+ }
+#else
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+ }
+#endif
+ if (Unsafe.SizeOf<T>() > source.Length)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
+ }
+ return Unsafe.ReadUnaligned<T>(ref GetReference(source));
+ }
+
+ /// <summary>
+ /// Reads a structure of type T out of a span of bytes.
+ /// <returns>If the span is too small to contain the type T, return false.</returns>
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryRead<T>(ReadOnlySpan<byte> source, out T value)
+ where T : struct
+ {
+#if netstandard
+ if (SpanHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T));
+ }
+#else
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+ }
+#endif
+ if (Unsafe.SizeOf<T>() > (uint)source.Length)
+ {
+ value = default;
+ return false;
+ }
+ value = Unsafe.ReadUnaligned<T>(ref GetReference(source));
+ return true;
+ }
+
+ /// <summary>
+ /// Writes a structure of type T into a span of bytes.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Write<T>(Span<byte> destination, ref T value)
+ where T : struct
+ {
+#if netstandard
+ if (SpanHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T));
+ }
+#else
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+ }
+#endif
+ if ((uint)Unsafe.SizeOf<T>() > (uint)destination.Length)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
+ }
+ Unsafe.WriteUnaligned<T>(ref GetReference(destination), value);
+ }
+
+ /// <summary>
+ /// Writes a structure of type T into a span of bytes.
+ /// <returns>If the span is too small to contain the type T, return false.</returns>
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryWrite<T>(Span<byte> destination, ref T value)
+ where T : struct
+ {
+#if netstandard
+ if (SpanHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T));
+ }
+#else
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+ }
+#endif
+ if (Unsafe.SizeOf<T>() > (uint)destination.Length)
+ {
+ return false;
+ }
+ Unsafe.WriteUnaligned<T>(ref GetReference(destination), value);
+ return true;
+ }
+
+ /// <summary>
+ /// Creates a new memory over the portion of the pre-pinned target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="array">The pre-pinned target array.</param>
+ /// <param name="start">The index at which to begin the memory.</param>
+ /// <param name="length">The number of items in the memory.</param>
+ /// <remarks>This method should only be called on an array that is already pinned and
+ /// that array should not be unpinned while the returned Memory<typeparamref name="T"/> is still in use.
+ /// Calling this method on an unpinned array could result in memory corruption.</remarks>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory<T> CreateFromPinnedArray<T>(T[] array, int start, int length)
+ {
+ if (array == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ return default;
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+ if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ // Before using _length, check if _length < 0, then 'and' it with RemoveFlagsBitMask
+ return new Memory<T>((object)array, start, length | (1 << 31));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/OptionalAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/OptionalAttribute.cs
new file mode 100644
index 0000000000..5ac75d7b3e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/OptionalAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ public sealed class OptionalAttribute : Attribute
+ {
+ public OptionalAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/OutAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/OutAttribute.cs
new file mode 100644
index 0000000000..338ceac91e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/OutAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+ public sealed class OutAttribute : Attribute
+ {
+ public OutAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/PreserveSigAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/PreserveSigAttribute.cs
new file mode 100644
index 0000000000..464e1abcbe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/PreserveSigAttribute.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public sealed class PreserveSigAttribute : Attribute
+ {
+ public PreserveSigAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/SafeBuffer.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/SafeBuffer.cs
new file mode 100644
index 0000000000..455e413928
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/SafeBuffer.cs
@@ -0,0 +1,405 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Purpose: Unsafe code that uses pointers should use
+** SafePointer to fix subtle lifetime problems with the
+** underlying resource.
+**
+===========================================================*/
+
+// Design points:
+// *) Avoid handle-recycling problems (including ones triggered via
+// resurrection attacks) for all accesses via pointers. This requires tying
+// together the lifetime of the unmanaged resource with the code that reads
+// from that resource, in a package that uses synchronization to enforce
+// the correct semantics during finalization. We're using SafeHandle's
+// ref count as a gate on whether the pointer can be dereferenced because that
+// controls the lifetime of the resource.
+//
+// *) Keep the penalties for using this class small, both in terms of space
+// and time. Having multiple threads reading from a memory mapped file
+// will already require 2 additional interlocked operations. If we add in
+// a "current position" concept, that requires additional space in memory and
+// synchronization. Since the position in memory is often (but not always)
+// something that can be stored on the stack, we can save some memory by
+// excluding it from this object. However, avoiding the need for
+// synchronization is a more significant win. This design allows multiple
+// threads to read and write memory simultaneously without locks (as long as
+// you don't write to a region of memory that overlaps with what another
+// thread is accessing).
+//
+// *) Space-wise, we use the following memory, including SafeHandle's fields:
+// Object Header MT* handle int bool bool <2 pad bytes> length
+// On 32 bit platforms: 24 bytes. On 64 bit platforms: 40 bytes.
+// (We can safe 4 bytes on x86 only by shrinking SafeHandle)
+//
+// *) Wrapping a SafeHandle would have been a nice solution, but without an
+// ordering between critical finalizable objects, it would have required
+// changes to each SafeHandle subclass to opt in to being usable from a
+// SafeBuffer (or some clever exposure of SafeHandle's state fields and a
+// way of forcing ReleaseHandle to run even after the SafeHandle has been
+// finalized with a ref count > 1). We can use less memory and create fewer
+// objects by simply inserting a SafeBuffer into the class hierarchy.
+//
+// *) In an ideal world, we could get marshaling support for SafeBuffer that
+// would allow us to annotate a P/Invoke declaration, saying this parameter
+// specifies the length of the buffer, and the units of that length are X.
+// P/Invoke would then pass that size parameter to SafeBuffer.
+// [DllImport(...)]
+// static extern SafeMemoryHandle AllocCharBuffer(int numChars);
+// If we could put an attribute on the SafeMemoryHandle saying numChars is
+// the element length, and it must be multiplied by 2 to get to the byte
+// length, we can simplify the usage model for SafeBuffer.
+//
+// *) This class could benefit from a constraint saying T is a value type
+// containing no GC references.
+
+// Implementation notes:
+// *) The Initialize method must be called before you use any instance of
+// a SafeBuffer. To avoid race conditions when storing SafeBuffers in statics,
+// you either need to take a lock when publishing the SafeBuffer, or you
+// need to create a local, initialize the SafeBuffer, then assign to the
+// static variable (perhaps using Interlocked.CompareExchange). Of course,
+// assignments in a static class constructor are under a lock implicitly.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using Internal.Runtime.CompilerServices;
+using Microsoft.Win32.SafeHandles;
+
+namespace System.Runtime.InteropServices
+{
+ public abstract unsafe class SafeBuffer : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ // Steal UIntPtr.MaxValue as our uninitialized value.
+ private static readonly UIntPtr Uninitialized = (UIntPtr.Size == 4) ?
+ ((UIntPtr)UInt32.MaxValue) : ((UIntPtr)UInt64.MaxValue);
+
+ private UIntPtr _numBytes;
+
+ protected SafeBuffer(bool ownsHandle) : base(ownsHandle)
+ {
+ _numBytes = Uninitialized;
+ }
+
+ /// <summary>
+ /// Specifies the size of the region of memory, in bytes. Must be
+ /// called before using the SafeBuffer.
+ /// </summary>
+ /// <param name="numBytes">Number of valid bytes in memory.</param>
+ [CLSCompliant(false)]
+ public void Initialize(ulong numBytes)
+ {
+ if (IntPtr.Size == 4 && numBytes > UInt32.MaxValue)
+ throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_AddressSpace);
+
+ if (numBytes >= (ulong)Uninitialized)
+ throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_UIntPtrMax);
+
+ _numBytes = (UIntPtr)numBytes;
+ }
+
+ /// <summary>
+ /// Specifies the size of the region in memory, as the number of
+ /// elements in an array. Must be called before using the SafeBuffer.
+ /// </summary>
+ [CLSCompliant(false)]
+ public void Initialize(uint numElements, uint sizeOfEachElement)
+ {
+ if (IntPtr.Size == 4 && numElements * sizeOfEachElement > UInt32.MaxValue)
+ throw new ArgumentOutOfRangeException("numBytes", SR.ArgumentOutOfRange_AddressSpace);
+
+ if (numElements * sizeOfEachElement >= (ulong)Uninitialized)
+ throw new ArgumentOutOfRangeException(nameof(numElements), SR.ArgumentOutOfRange_UIntPtrMax);
+
+ _numBytes = checked((UIntPtr)(numElements * sizeOfEachElement));
+ }
+
+ /// <summary>
+ /// Specifies the size of the region in memory, as the number of
+ /// elements in an array. Must be called before using the SafeBuffer.
+ /// </summary>
+ [CLSCompliant(false)]
+ public void Initialize<T>(uint numElements) where T : struct
+ {
+ Initialize(numElements, AlignedSizeOf<T>());
+ }
+
+ // Callers should ensure that they check whether the pointer ref param
+ // is null when AcquirePointer returns. If it is not null, they must
+ // call ReleasePointer. This method calls DangerousAddRef
+ // & exposes the pointer. Unlike Read, it does not alter the "current
+ // position" of the pointer. Here's how to use it:
+ //
+ // byte* pointer = null;
+ // try {
+ // safeBuffer.AcquirePointer(ref pointer);
+ // // Use pointer here, with your own bounds checking
+ // }
+ // finally {
+ // if (pointer != null)
+ // safeBuffer.ReleasePointer();
+ // }
+ //
+ // Note: If you cast this byte* to a T*, you have to worry about
+ // whether your pointer is aligned. Additionally, you must take
+ // responsibility for all bounds checking with this pointer.
+ /// <summary>
+ /// Obtain the pointer from a SafeBuffer for a block of code,
+ /// with the express responsibility for bounds checking and calling
+ /// ReleasePointer later to ensure the pointer can be freed later.
+ /// This method either completes successfully or throws an exception
+ /// and returns with pointer set to null.
+ /// </summary>
+ /// <param name="pointer">A byte*, passed by reference, to receive
+ /// the pointer from within the SafeBuffer. You must set
+ /// pointer to null before calling this method.</param>
+ [CLSCompliant(false)]
+ public void AcquirePointer(ref byte* pointer)
+ {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ pointer = null;
+
+ bool junk = false;
+ DangerousAddRef(ref junk);
+ pointer = (byte*)handle;
+ }
+
+ public void ReleasePointer()
+ {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ DangerousRelease();
+ }
+
+ /// <summary>
+ /// Read a value type from memory at the given offset. This is
+ /// equivalent to: return *(T*)(bytePtr + byteOffset);
+ /// </summary>
+ /// <typeparam name="T">The value type to read</typeparam>
+ /// <param name="byteOffset">Where to start reading from memory. You
+ /// may have to consider alignment.</param>
+ /// <returns>An instance of T read from memory.</returns>
+ [CLSCompliant(false)]
+ public T Read<T>(ulong byteOffset) where T : struct
+ {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ uint sizeofT = SizeOf<T>();
+ byte* ptr = (byte*)handle + byteOffset;
+ SpaceCheck(ptr, sizeofT);
+
+ // return *(T*) (_ptr + byteOffset);
+ T value = default(T);
+ bool mustCallRelease = false;
+ try
+ {
+ DangerousAddRef(ref mustCallRelease);
+
+ fixed (byte* pStructure = &Unsafe.As<T, byte>(ref value))
+ Buffer.Memmove(pStructure, ptr, sizeofT);
+ }
+ finally
+ {
+ if (mustCallRelease)
+ DangerousRelease();
+ }
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ public void ReadArray<T>(ulong byteOffset, T[] array, int index, int count)
+ where T : struct
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - index < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ uint sizeofT = SizeOf<T>();
+ uint alignedSizeofT = AlignedSizeOf<T>();
+ byte* ptr = (byte*)handle + byteOffset;
+ SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count)));
+
+ bool mustCallRelease = false;
+ try
+ {
+ DangerousAddRef(ref mustCallRelease);
+
+ if (count > 0)
+ {
+ unsafe
+ {
+ fixed (byte* pStructure = &Unsafe.As<T, byte>(ref array[index]))
+ {
+ for (int i = 0; i < count; i++)
+ Buffer.Memmove(pStructure + sizeofT * i, ptr + alignedSizeofT * i, sizeofT);
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (mustCallRelease)
+ DangerousRelease();
+ }
+ }
+
+ /// <summary>
+ /// Write a value type to memory at the given offset. This is
+ /// equivalent to: *(T*)(bytePtr + byteOffset) = value;
+ /// </summary>
+ /// <typeparam name="T">The type of the value type to write to memory.</typeparam>
+ /// <param name="byteOffset">The location in memory to write to. You
+ /// may have to consider alignment.</param>
+ /// <param name="value">The value type to write to memory.</param>
+ [CLSCompliant(false)]
+ public void Write<T>(ulong byteOffset, T value) where T : struct
+ {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ uint sizeofT = SizeOf<T>();
+ byte* ptr = (byte*)handle + byteOffset;
+ SpaceCheck(ptr, sizeofT);
+
+ // *((T*) (_ptr + byteOffset)) = value;
+ bool mustCallRelease = false;
+ try
+ {
+ DangerousAddRef(ref mustCallRelease);
+
+ fixed (byte* pStructure = &Unsafe.As<T, byte>(ref value))
+ Buffer.Memmove(ptr, pStructure, sizeofT);
+ }
+ finally
+ {
+ if (mustCallRelease)
+ DangerousRelease();
+ }
+ }
+
+ [CLSCompliant(false)]
+ public void WriteArray<T>(ulong byteOffset, T[] array, int index, int count)
+ where T : struct
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (array.Length - index < count)
+ throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ uint sizeofT = SizeOf<T>();
+ uint alignedSizeofT = AlignedSizeOf<T>();
+ byte* ptr = (byte*)handle + byteOffset;
+ SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count)));
+
+ bool mustCallRelease = false;
+ try
+ {
+ DangerousAddRef(ref mustCallRelease);
+
+ if (count > 0)
+ {
+ unsafe
+ {
+ fixed (byte* pStructure = &Unsafe.As<T, byte>(ref array[index]))
+ {
+ for (int i = 0; i < count; i++)
+ Buffer.Memmove(ptr + alignedSizeofT * i, pStructure + sizeofT * i, sizeofT);
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (mustCallRelease)
+ DangerousRelease();
+ }
+ }
+
+ /// <summary>
+ /// Returns the number of bytes in the memory region.
+ /// </summary>
+ [CLSCompliant(false)]
+ public ulong ByteLength
+ {
+ get
+ {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ return (ulong)_numBytes;
+ }
+ }
+
+ /* No indexer. The perf would be misleadingly bad. People should use
+ * AcquirePointer and ReleasePointer instead. */
+
+ private void SpaceCheck(byte* ptr, ulong sizeInBytes)
+ {
+ if ((ulong)_numBytes < sizeInBytes)
+ NotEnoughRoom();
+ if ((ulong)(ptr - (byte*)handle) > ((ulong)_numBytes) - sizeInBytes)
+ NotEnoughRoom();
+ }
+
+ private static void NotEnoughRoom()
+ {
+ throw new ArgumentException(SR.Arg_BufferTooSmall);
+ }
+
+ private static InvalidOperationException NotInitialized()
+ {
+ return new InvalidOperationException(SR.InvalidOperation_MustCallInitialize);
+ }
+
+ /// <summary>
+ /// Returns the size that SafeBuffer (and hence, UnmanagedMemoryAccessor) reserves in the unmanaged buffer for each element of an array of T. This is not the same
+ /// value that sizeof(T) returns! Since the primary use case is to parse memory mapped files, we cannot change this algorithm as this defines a de-facto serialization format.
+ /// Throws if T contains GC references.
+ /// </summary>
+ internal static uint AlignedSizeOf<T>() where T : struct
+ {
+ uint size = SizeOf<T>();
+ if (size == 1 || size == 2)
+ {
+ return size;
+ }
+
+ return (uint)(((size + 3) & (~3)));
+ }
+
+ /// <summary>
+ /// Returns same value as sizeof(T) but throws if T contains GC references.
+ /// </summary>
+ internal static uint SizeOf<T>() where T : struct
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ throw new ArgumentException(SR.Argument_NeedStructWithNoRefs);
+
+ return (uint)Unsafe.SizeOf<T>();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/StructLayoutAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/StructLayoutAttribute.cs
new file mode 100644
index 0000000000..c4cce9956e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/StructLayoutAttribute.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
+ public sealed class StructLayoutAttribute : Attribute
+ {
+ public StructLayoutAttribute(LayoutKind layoutKind)
+ {
+ Value = layoutKind;
+ }
+
+ public StructLayoutAttribute(short layoutKind)
+ {
+ Value = (LayoutKind)layoutKind;
+ }
+
+ public LayoutKind Value { get; }
+
+ public int Pack;
+ public int Size;
+ public CharSet CharSet;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/UnmanagedFunctionPointerAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/UnmanagedFunctionPointerAttribute.cs
new file mode 100644
index 0000000000..c4f96903ee
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/UnmanagedFunctionPointerAttribute.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ [AttributeUsage(AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
+ public sealed class UnmanagedFunctionPointerAttribute : Attribute
+ {
+ public UnmanagedFunctionPointerAttribute()
+ {
+ CallingConvention = CallingConvention.Winapi;
+ }
+
+ public UnmanagedFunctionPointerAttribute(CallingConvention callingConvention)
+ {
+ CallingConvention = callingConvention;
+ }
+
+ public CallingConvention CallingConvention { get; }
+
+ public bool BestFitMapping;
+ public bool SetLastError;
+ public bool ThrowOnUnmappableChar;
+ public CharSet CharSet;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/UnmanagedType.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/UnmanagedType.cs
new file mode 100644
index 0000000000..4deca7fe0c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/UnmanagedType.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ public enum UnmanagedType
+ {
+ Bool = 0x2, // 4 byte boolean value (true != 0, false == 0)
+ I1 = 0x3, // 1 byte signed value
+ U1 = 0x4, // 1 byte unsigned value
+ I2 = 0x5, // 2 byte signed value
+ U2 = 0x6, // 2 byte unsigned value
+ I4 = 0x7, // 4 byte signed value
+ U4 = 0x8, // 4 byte unsigned value
+ I8 = 0x9, // 8 byte signed value
+ U8 = 0xa, // 8 byte unsigned value
+ R4 = 0xb, // 4 byte floating point
+ R8 = 0xc, // 8 byte floating point
+ Currency = 0xf, // A currency
+ BStr = 0x13, // OLE Unicode BSTR
+ LPStr = 0x14, // Ptr to SBCS string
+ LPWStr = 0x15, // Ptr to Unicode string
+ LPTStr = 0x16, // Ptr to OS preferred (SBCS/Unicode) string
+ ByValTStr = 0x17, // OS preferred (SBCS/Unicode) inline string (only valid in structs)
+ IUnknown = 0x19, // COM IUnknown pointer.
+ IDispatch = 0x1a, // COM IDispatch pointer
+ Struct = 0x1b, // Structure
+ Interface = 0x1c, // COM interface
+ SafeArray = 0x1d, // OLE SafeArray
+ ByValArray = 0x1e, // Array of fixed size (only valid in structs)
+ SysInt = 0x1f, // Hardware natural sized signed integer
+ SysUInt = 0x20,
+ VBByRefStr = 0x22,
+ AnsiBStr = 0x23, // OLE BSTR containing SBCS characters
+ TBStr = 0x24, // Ptr to OS preferred (SBCS/Unicode) BSTR
+ VariantBool = 0x25, // OLE defined BOOLEAN (2 bytes, true == -1, false == 0)
+ FunctionPtr = 0x26, // Function pointer
+ AsAny = 0x28, // Paired with Object type and does runtime marshalling determination
+ LPArray = 0x2a, // C style array
+ LPStruct = 0x2b, // Pointer to a structure
+ CustomMarshaler = 0x2c,
+ Error = 0x2d,
+ IInspectable = 0x2e,
+ HString = 0x2f, // Windows Runtime HSTRING
+ LPUTF8Str = 0x30, // UTF8 string
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/VarEnum.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/VarEnum.cs
new file mode 100644
index 0000000000..495aeca6d1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/VarEnum.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ public enum VarEnum
+ {
+ VT_EMPTY = 0,
+ VT_NULL = 1,
+ VT_I2 = 2,
+ VT_I4 = 3,
+ VT_R4 = 4,
+ VT_R8 = 5,
+ VT_CY = 6,
+ VT_DATE = 7,
+ VT_BSTR = 8,
+ VT_DISPATCH = 9,
+ VT_ERROR = 10,
+ VT_BOOL = 11,
+ VT_VARIANT = 12,
+ VT_UNKNOWN = 13,
+ VT_DECIMAL = 14,
+ VT_I1 = 16,
+ VT_UI1 = 17,
+ VT_UI2 = 18,
+ VT_UI4 = 19,
+ VT_I8 = 20,
+ VT_UI8 = 21,
+ VT_INT = 22,
+ VT_UINT = 23,
+ VT_VOID = 24,
+ VT_HRESULT = 25,
+ VT_PTR = 26,
+ VT_SAFEARRAY = 27,
+ VT_CARRAY = 28,
+ VT_USERDEFINED = 29,
+ VT_LPSTR = 30,
+ VT_LPWSTR = 31,
+ VT_RECORD = 36,
+ VT_FILETIME = 64,
+ VT_BLOB = 65,
+ VT_STREAM = 66,
+ VT_STORAGE = 67,
+ VT_STREAMED_OBJECT = 68,
+ VT_STORED_OBJECT = 69,
+ VT_BLOB_OBJECT = 70,
+ VT_CF = 71,
+ VT_CLSID = 72,
+ VT_VECTOR = 0x1000,
+ VT_ARRAY = 0x2000,
+ VT_BYREF = 0x4000
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IDeserializationCallback.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IDeserializationCallback.cs
new file mode 100644
index 0000000000..a1c1671a8b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IDeserializationCallback.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ public interface IDeserializationCallback
+ {
+ void OnDeserialization(object sender);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IFormatterConverter.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IFormatterConverter.cs
new file mode 100644
index 0000000000..c173144854
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IFormatterConverter.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ [CLSCompliant(false)]
+ public interface IFormatterConverter
+ {
+ object Convert(object value, Type type);
+ object Convert(object value, TypeCode typeCode);
+ bool ToBoolean(object value);
+ char ToChar(object value);
+ sbyte ToSByte(object value);
+ byte ToByte(object value);
+ short ToInt16(object value);
+ ushort ToUInt16(object value);
+ int ToInt32(object value);
+ uint ToUInt32(object value);
+ long ToInt64(object value);
+ ulong ToUInt64(object value);
+ float ToSingle(object value);
+ double ToDouble(object value);
+ decimal ToDecimal(object value);
+ DateTime ToDateTime(object value);
+ string ToString(object value);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IObjectReference.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IObjectReference.cs
new file mode 100644
index 0000000000..d41bc50dde
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/IObjectReference.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ public interface IObjectReference
+ {
+ object GetRealObject(StreamingContext context);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/ISafeSerializationData.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/ISafeSerializationData.cs
new file mode 100644
index 0000000000..5089d134c3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/ISafeSerializationData.cs
@@ -0,0 +1,207 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ //
+ // #SafeSerialization
+ //
+ // Types which are serializable via the ISerializable interface have a problem when it comes to allowing
+ // transparent subtypes which can allow themselves to serialize since the GetObjectData method is
+ // SecurityCritical.
+ //
+ // For instance, System.Exception implements ISerializable, however it is also desirable to have
+ // transparent exceptions with their own fields that need to be serialized. (For instance, in transparent
+ // assemblies such as the DLR and F#, or even in partial trust application code). Since overriding
+ // GetObjectData requires that the overriding method be security critical, this won't work directly.
+ //
+ // SafeSerializationManager solves this problem by allowing any partial trust code to contribute
+ // individual chunks of serializable data to be included in the serialized version of the derived class.
+ // These chunks are then deserialized back out of the serialized type and notified that they should
+ // populate the fields of the deserialized object when serialization is complete. This allows partial
+ // trust or transparent code to participate in serialization of an ISerializable type without having to
+ // override GetObjectData or implement the ISerializable constructor.
+ //
+ // On the serialization side, SafeSerializationManager has an event SerializeObjectState which it will
+ // fire in response to serialization in order to gather the units of serializable data that should be
+ // stored with the rest of the object during serialization. Methods which respond to these events
+ // create serializable objects which implement the ISafeSerializationData interface and add them to the
+ // collection of other serialized data by calling AddSerializedState on the SafeSerializationEventArgs
+ // passed into the event.
+ //
+ // By using an event rather than a virtual method on the base ISerializable object, we allow multiple
+ // potentially untrusted subclasses to participate in serialization, without each one having to ensure
+ // that it calls up to the base type in order for the whole system to work. (For instance Exception :
+ // TrustedException : UntrustedException, in this scenario UntrustedException would be able to override
+ // the virtual method an prevent TrustedException from ever seeing the method call, either accidentally
+ // or maliciously).
+ //
+ // Further, by only allowing additions of new chunks of serialization state rather than exposing the
+ // whole underlying list, we avoid exposing potentially sensitive serialized state to any of the
+ // potentially untrusted subclasses.
+ //
+ // At deserialization time, SafeSerializationManager performs the reverse operation. It deserializes the
+ // chunks of serialized state, and then notifies them that the object they belong to is deserialized by
+ // calling their CompleteSerialization method. In repsonse to this call, the state objects populate the
+ // fields of the object being deserialized with the state that they held.
+ //
+ // From a security perspective, the chunks of serialized state can only contain data that the specific
+ // subclass itself had access to read (otherwise it wouldn't be able to populate the type with that
+ // data), as opposed to having access to far more data in the SerializationInfo that GetObjectData uses.
+ // Similarly, at deserialization time, the serialized state can only modify fields that the type itself
+ // has access to (again, as opposed to the full SerializationInfo which could be modified).
+ //
+ // Individual types which wish to participate in safe serialization do so by containing an instance of a
+ // SafeSerializationManager and exposing its serialization event. During GetObjectData, the
+ // SafeSerializationManager is serialized just like any other field of the containing type. However, at
+ // the end of serialization it is called back one last time to CompleteSerialization.
+ //
+ // In CompleteSerialization, if the SafeSerializationManager detects that it has extra chunks of
+ // data to handle, it substitutes the root type being serialized (formerly the real type hosting the
+ // SafeSerializationManager) with itself. This allows it to gain more control over the deserialization
+ // process. It also saves away an extra bit of state in the serialization info indicating the real type
+ // of object that should be recreated during deserialization.
+ //
+ // At this point the serialized state looks like this:
+ // Data:
+ // realSerializedData1
+ // ...
+ // realSerializedDataN
+ // safeSerializationData -> this is the serialization data member of the parent type
+ // _serializedState -> list of saved serialized states from subclasses responding to the safe
+ // serialization event
+ // RealTypeSerializationName -> type which is using safe serialization
+ // Type:
+ // SafeSerializationManager
+ //
+ // That is, the serialized data claims to be of type SafeSerializationManager, however contains only the
+ // data from the real object being serialized along with one bit of safe serialization metadata.
+ //
+ // At deserialization time, since the serialized data claims to be of type SafeSerializationManager, the
+ // root object being created is an instance of the SafeSerializationManager class. However, it detects
+ // that this isn't a real SafeSerializationManager (by looking for the real type field in the metadata),
+ // and simply saves away the SerializationInfo and the real type being deserialized.
+ //
+ // Since SafeSerializationManager implements IObjectReference, the next step of deserialization is the
+ // GetRealObject callback. This callback is the one responsible for getting the
+ // SafeSerializationManager out of the way and instead creating an instance of the actual type which was
+ // serialized.
+ //
+ // It does this by first creating an instance of the real type being deserialzed (saved away in the
+ // deserialzation constructor), but not running any of its constructors. Instead, it walks the
+ // inheritance hierarchy (moving toward the most derived type) looking for the last full trust type to
+ // implement the standard ISerializable constructor before any type does not implement the constructor.
+ // It is this last type's deserialization constructor which is then invoked, passing in the saved
+ // SerializationInfo. Once the constructors are run, we return this object as the real deserialized
+ // object.
+ //
+ // The reason that we do this walk is so that ISerializable types can protect themselves from malicious
+ // input during deserialization by making their deserialization constructors unavailable to partial
+ // trust code. By not requiring every type have a copy of this constructor, partial trust code can
+ // participate in safe serialization and not be required to have access to the parent's constructor.
+ //
+ // It should be noted however, that this heuristic means that if a full trust type does derive from
+ // a transparent or partial trust type using this safe serialization mechanism, that full trust type
+ // will not have its constructor called. Further, the protection of not invoking partial trust
+ // deserialization constructors only comes into play if SafeSerializationManager is in control of
+ // deserialization, which means there must be at least one (even empty) safe serialization event
+ // handler registered.
+ //
+ // Another interesting note is that at this point there are now two SafeSerializationManagers alive for
+ // this deserialization. The first object is the one which is controlling the deserialization and was
+ // created as the root object of the deserialization. The second one is the object which contains the
+ // serialized data chunks and is a data member of the real object being deserialized. For this reason,
+ // the data objects cannot be notified that the deserialization is complete during GetRealObject since
+ // the ISafeSerializationData objects are not members of the active SafeSerializationManager instance.
+ //
+ // The next step is the OnDeserialized callback, which comes to SafeSerializableObject since it was
+ // pretending to be the root object of the deserialization. It responds to this callback by calling
+ // any existing OnDeserialized callback on the real type that was deserialized.
+ //
+ // The real type needs to call its data member SafeSerializationData object's CompleteDeserialization
+ // method in response to the OnDeserialized call. This CompleteDeserialization call will then iterate
+ // through the ISafeSerializationData objects calling each of their CompleteDeserialization methods so
+ // that they can plug the nearly-complete object with their saved data.
+ //
+ // The reason for having a new ISafeSerializationData interface which is basically identical to
+ // IDeserializationCallback is that IDeserializationCallback will be called on the stored data chunks
+ // by the serialization code when they are deserialized, and that's not a desirable behavior.
+ // Essentially, we need to change the meaning of the object parameter to mean "parent object which
+ // participated in safe serialization", rather than "this object".
+ //
+ // Implementing safe serialization on an ISerialiable type is relatively straight forward. (For an
+ // example, see System.Exception):
+ //
+ // 1. Include a data member of type SafeSerializationManager:
+ //
+ // private SafeSerializationManager _safeSerializationManager;
+ //
+ // 2. Add a protected SerializeObjectState event, which passes through to the SafeSerializationManager:
+ //
+ // protected event EventHandler<SafeSerializationEventArgs> SerializeObjectState
+ // {
+ // add { _safeSerializationManager.SerializeObjectState += value; }
+ // remove { _safeSerializationManager.SerializeObjectState -= value; }
+ // }
+ //
+ // 3. Serialize the safe serialization object in GetObjectData, and call its CompleteSerialization method:
+ //
+ // {
+ // info.AddValue("_safeSerializationManager", _safeSerializationManager, typeof(SafeSerializationManager));
+ // _safeSerializationManager.CompleteSerialization(this, info, context);
+ // }
+ //
+ // 4. Add an OnDeserialized handler if one doesn't already exist, and call CompleteDeserialization in it:
+ //
+ // [OnDeserialized]
+ // private void OnDeserialized(StreamingContext context)
+ // {
+ // _safeSerializationManager.CompleteDeserialization(this);
+ // }
+ //
+ // On the client side, using safe serialization is also pretty easy. For example:
+ //
+ // [Serializable]
+ // public class TransparentException : Exception
+ // {
+ // [Serializable]
+ // private struct TransparentExceptionState : ISafeSerializationData
+ // {
+ // public string _extraData;
+ //
+ // void ISafeSerializationData.CompleteDeserialization(object obj)
+ // {
+ // TransparentException exception = obj as TransparentException;
+ // exception._state = this;
+ // }
+ // }
+ //
+ // [NonSerialized]
+ // private TransparentExceptionState _state = new TransparentExceptionState();
+ //
+ // public TransparentException()
+ // {
+ // SerializeObjectState += delegate(object exception, SafeSerializationEventArgs eventArgs)
+ // {
+ // eventArgs.AddSerializedState(_state);
+ // };
+ // }
+ //
+ // public string ExtraData
+ // {
+ // get { return _state._extraData; }
+ // set { _state._extraData = value; }
+ // }
+ // }
+ //
+
+ // Interface to be supported by objects which are stored in safe serialization stores
+ public interface ISafeSerializationData
+ {
+ // CompleteDeserialization is called when the object to which the extra serialized data was attached
+ // has completed its deserialization, and now needs to be populated with the extra data stored in
+ // this object.
+ void CompleteDeserialization(object deserialized);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/ISerializable.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/ISerializable.cs
new file mode 100644
index 0000000000..383b3f07af
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/ISerializable.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ public interface ISerializable
+ {
+ void GetObjectData(SerializationInfo info, StreamingContext context);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnDeserializedAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnDeserializedAttribute.cs
new file mode 100644
index 0000000000..408a55ccf9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnDeserializedAttribute.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public sealed class OnDeserializedAttribute : Attribute
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnDeserializingAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnDeserializingAttribute.cs
new file mode 100644
index 0000000000..162857e8d3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnDeserializingAttribute.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public sealed class OnDeserializingAttribute : Attribute
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnSerializedAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnSerializedAttribute.cs
new file mode 100644
index 0000000000..020dd0257c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnSerializedAttribute.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public sealed class OnSerializedAttribute : Attribute
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnSerializingAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnSerializingAttribute.cs
new file mode 100644
index 0000000000..8dc8af3f23
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OnSerializingAttribute.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ public sealed class OnSerializingAttribute : Attribute
+ {
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OptionalFieldAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OptionalFieldAttribute.cs
new file mode 100644
index 0000000000..84daa539be
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/OptionalFieldAttribute.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class OptionalFieldAttribute : Attribute
+ {
+ private int _versionAdded = 1;
+
+ public int VersionAdded
+ {
+ get { return _versionAdded; }
+ set
+ {
+ if (value < 1)
+ {
+ throw new ArgumentException(SR.Serialization_OptionalFieldVersionValue);
+ }
+ _versionAdded = value;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SafeSerializationEventArgs.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SafeSerializationEventArgs.cs
new file mode 100644
index 0000000000..896b91fca0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SafeSerializationEventArgs.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Runtime.Serialization
+{
+ // SafeSerializationEventArgs are provided to the delegates which do safe serialization. Each delegate
+ // serializes its own state into an IDeserializationCallback instance which must, itself, be serializable.
+ // These indivdiual states are then added to the SafeSerializationEventArgs in order to be saved away when
+ // the original ISerializable type is serialized.
+ public sealed class SafeSerializationEventArgs : EventArgs
+ {
+ private readonly List<object> _serializedStates = new List<object>();
+
+ internal SafeSerializationEventArgs() { }
+
+ public void AddSerializedState(ISafeSerializationData serializedState)
+ {
+ if (serializedState == null)
+ throw new ArgumentNullException(nameof(serializedState));
+ if (!serializedState.GetType().IsSerializable)
+ throw new ArgumentException(SR.Format(SR.Serialization_NonSerType, serializedState.GetType(), serializedState.GetType().Assembly.FullName));
+
+ _serializedStates.Add(serializedState);
+ }
+
+ public StreamingContext StreamingContext { get; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SerializationException.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SerializationException.cs
new file mode 100644
index 0000000000..1c9c21eabb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SerializationException.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Runtime.Serialization
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class SerializationException : SystemException
+ {
+ private static String s_nullMessage = SR.SerializationException;
+
+ // Creates a new SerializationException with its message
+ // string set to a default message.
+ public SerializationException()
+ : base(s_nullMessage)
+ {
+ HResult = HResults.COR_E_SERIALIZATION;
+ }
+
+ public SerializationException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_SERIALIZATION;
+ }
+
+ public SerializationException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_SERIALIZATION;
+ }
+
+ protected SerializationException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SerializationInfoEnumerator.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SerializationInfoEnumerator.cs
new file mode 100644
index 0000000000..6399510736
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/SerializationInfoEnumerator.cs
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Diagnostics;
+
+namespace System.Runtime.Serialization
+{
+ public struct SerializationEntry
+ {
+ private string _name;
+ private object _value;
+ private Type _type;
+
+ internal SerializationEntry(string entryName, object entryValue, Type entryType)
+ {
+ _name = entryName;
+ _value = entryValue;
+ _type = entryType;
+ }
+
+ public object Value => _value;
+ public string Name => _name;
+ public Type ObjectType => _type;
+ }
+
+ public sealed class SerializationInfoEnumerator : IEnumerator
+ {
+ private readonly string[] _members;
+ private readonly object[] _data;
+ private readonly Type[] _types;
+ private readonly int _numItems;
+ private int _currItem;
+ private bool _current;
+
+ internal SerializationInfoEnumerator(string[] members, object[] info, Type[] types, int numItems)
+ {
+ Debug.Assert(members != null, "[SerializationInfoEnumerator.ctor]members!=null");
+ Debug.Assert(info != null, "[SerializationInfoEnumerator.ctor]info!=null");
+ Debug.Assert(types != null, "[SerializationInfoEnumerator.ctor]types!=null");
+ Debug.Assert(numItems >= 0, "[SerializationInfoEnumerator.ctor]numItems>=0");
+ Debug.Assert(members.Length >= numItems, "[SerializationInfoEnumerator.ctor]members.Length>=numItems");
+ Debug.Assert(info.Length >= numItems, "[SerializationInfoEnumerator.ctor]info.Length>=numItems");
+ Debug.Assert(types.Length >= numItems, "[SerializationInfoEnumerator.ctor]types.Length>=numItems");
+
+ _members = members;
+ _data = info;
+ _types = types;
+
+ //The MoveNext semantic is much easier if we enforce that [0..m_numItems] are valid entries
+ //in the enumerator, hence we subtract 1.
+ _numItems = numItems - 1;
+ _currItem = -1;
+ _current = false;
+ }
+
+ public bool MoveNext()
+ {
+ if (_currItem < _numItems)
+ {
+ _currItem++;
+ _current = true;
+ }
+ else
+ {
+ _current = false;
+ }
+
+ return _current;
+ }
+
+ object IEnumerator.Current => Current;
+
+ public SerializationEntry Current
+ {
+ get
+ {
+ if (_current == false)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ }
+ return new SerializationEntry(_members[_currItem], _data[_currItem], _types[_currItem]);
+ }
+ }
+
+ public void Reset()
+ {
+ _currItem = -1;
+ _current = false;
+ }
+
+ public string Name
+ {
+ get
+ {
+ if (_current == false)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ }
+ return _members[_currItem];
+ }
+ }
+ public object Value
+ {
+ get
+ {
+ if (_current == false)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ }
+ return _data[_currItem];
+ }
+ }
+ public Type ObjectType
+ {
+ get
+ {
+ if (_current == false)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+ }
+ return _types[_currItem];
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Serialization/StreamingContext.cs b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/StreamingContext.cs
new file mode 100644
index 0000000000..cdcb1c335b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Serialization/StreamingContext.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.Serialization
+{
+ public readonly struct StreamingContext
+ {
+ private readonly object _additionalContext;
+ private readonly StreamingContextStates _state;
+
+ public StreamingContext(StreamingContextStates state) : this(state, null)
+ {
+ }
+
+ public StreamingContext(StreamingContextStates state, object additional)
+ {
+ _state = state;
+ _additionalContext = additional;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is StreamingContext))
+ {
+ return false;
+ }
+ StreamingContext ctx = (StreamingContext)obj;
+ return ctx._additionalContext == _additionalContext && ctx._state == _state;
+ }
+
+ public override int GetHashCode() => (int)_state;
+
+ public StreamingContextStates State => _state;
+
+ public object Context => _additionalContext;
+ }
+
+ [Flags]
+ public enum StreamingContextStates
+ {
+ CrossProcess = 0x01,
+ CrossMachine = 0x02,
+ File = 0x04,
+ Persistence = 0x08,
+ Remoting = 0x10,
+ Other = 0x20,
+ Clone = 0x40,
+ CrossAppDomain = 0x80,
+ All = 0xFF,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Versioning/NonVersionableAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/Versioning/NonVersionableAttribute.cs
new file mode 100644
index 0000000000..e4809953bc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Versioning/NonVersionableAttribute.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** The [NonVersionable] attribute is applied to indicate that the implementation
+** of a particular member or layout of a struct cannot be changed for given platform in incompatible way.
+** This allows cross-module inlining of methods and data structures whose implementation
+** is never changed in ReadyToRun native images. Any changes to such members or types would be
+** breaking changes for ReadyToRun.
+**
+** Applying this type also has the side effect that the inlining tables in R2R images will not
+** report that inlining of NonVersionable attributed methods occured. These inlining tables are used
+** by profilers to figure out the set of methods that need to be rejited when one method is instrumented,
+** so in effect NonVersionable methods are also non-instrumentable. Generally this is OK for
+** extremely trivial low level methods where NonVersionable gets used, but if there is any plan to
+** significantly extend its usage or allow 3rd parties to use it please discuss with the diagnostics team.
+===========================================================*/
+
+using System;
+using System.Diagnostics;
+
+namespace System.Runtime.Versioning
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Constructor,
+ AllowMultiple = false, Inherited = false)]
+ internal sealed class NonVersionableAttribute : Attribute
+ {
+ public NonVersionableAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Versioning/TargetFrameworkAttribute.cs b/src/System.Private.CoreLib/shared/System/Runtime/Versioning/TargetFrameworkAttribute.cs
new file mode 100644
index 0000000000..a819066382
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/Versioning/TargetFrameworkAttribute.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Identifies which SKU and version of the .NET
+** Framework that a particular library was compiled against.
+** Emitted by VS, and can help catch deployment problems.
+**
+===========================================================*/
+
+using System;
+
+namespace System.Runtime.Versioning
+{
+ [AttributeUsageAttribute(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
+ public sealed class TargetFrameworkAttribute : Attribute
+ {
+ private String _frameworkName; // A target framework moniker
+ private String _frameworkDisplayName;
+
+ // The frameworkName parameter is intended to be the string form of a FrameworkName instance.
+ public TargetFrameworkAttribute(String frameworkName)
+ {
+ if (frameworkName == null)
+ throw new ArgumentNullException(nameof(frameworkName));
+ _frameworkName = frameworkName;
+ }
+
+ // The target framework moniker that this assembly was compiled against.
+ // Use the FrameworkName class to interpret target framework monikers.
+ public String FrameworkName
+ {
+ get { return _frameworkName; }
+ }
+
+ public String FrameworkDisplayName
+ {
+ get { return _frameworkDisplayName; }
+ set { _frameworkDisplayName = value; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SByte.cs b/src/System.Private.CoreLib/shared/System/SByte.cs
new file mode 100644
index 0000000000..c7cee2adc2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SByte.cs
@@ -0,0 +1,332 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [CLSCompliant(false)] [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct SByte : IComparable, IConvertible, IFormattable, IComparable<SByte>, IEquatable<SByte>, ISpanFormattable
+ {
+ private sbyte m_value; // Do not rename (binary serialization)
+
+ // The maximum value that a Byte may represent: 127.
+ public const sbyte MaxValue = (sbyte)0x7F;
+
+ // The minimum value that a Byte may represent: -128.
+ public const sbyte MinValue = unchecked((sbyte)0x80);
+
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type SByte, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object obj)
+ {
+ if (obj == null)
+ {
+ return 1;
+ }
+ if (!(obj is SByte))
+ {
+ throw new ArgumentException(SR.Arg_MustBeSByte);
+ }
+ return m_value - ((SByte)obj).m_value;
+ }
+
+ public int CompareTo(SByte value)
+ {
+ return m_value - value;
+ }
+
+ // Determines whether two Byte objects are equal.
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is SByte))
+ {
+ return false;
+ }
+ return m_value == ((SByte)obj).m_value;
+ }
+
+ [NonVersionable]
+ public bool Equals(SByte obj)
+ {
+ return m_value == obj;
+ }
+
+ // Gets a hash code for this instance.
+ public override int GetHashCode()
+ {
+ return ((int)m_value ^ (int)m_value << 8);
+ }
+
+
+ // Provides a string representation of a byte.
+ public override String ToString()
+ {
+ return Number.FormatInt32(m_value, null, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatInt32(m_value, null, provider);
+ }
+
+ public String ToString(String format)
+ {
+ return ToString(format, null);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ if (m_value < 0 && format != null && format.Length > 0 && (format[0] == 'X' || format[0] == 'x'))
+ {
+ uint temp = (uint)(m_value & 0x000000FF);
+ return Number.FormatUInt32(temp, format, provider);
+ }
+ return Number.FormatInt32(m_value, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ if (m_value < 0 && format.Length > 0 && (format[0] == 'X' || format[0] == 'x'))
+ {
+ uint temp = (uint)(m_value & 0x000000FF);
+ return Number.TryFormatUInt32(temp, format, provider, destination, out charsWritten);
+ }
+ return Number.TryFormatInt32(m_value, format, provider, destination, out charsWritten);
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
+ }
+
+ // Parses a signed byte from a String in the given style. If
+ // a NumberFormatInfo isn't specified, the current culture's
+ // NumberFormatInfo is assumed.
+ //
+ [CLSCompliant(false)]
+ public static sbyte Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static sbyte Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Parse(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ private static sbyte Parse(String s, NumberStyles style, NumberFormatInfo info)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, info);
+ }
+
+ private static sbyte Parse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info)
+ {
+ int i = 0;
+ try
+ {
+ i = Number.ParseInt32(s, style, info);
+ }
+ catch (OverflowException e)
+ {
+ throw new OverflowException(SR.Overflow_SByte, e);
+ }
+
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ { // We are parsing a hexadecimal number
+ if ((i < 0) || i > Byte.MaxValue)
+ {
+ throw new OverflowException(SR.Overflow_SByte);
+ }
+ return (sbyte)i;
+ }
+
+ if (i < MinValue || i > MaxValue) throw new OverflowException(SR.Overflow_SByte);
+ return (sbyte)i;
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(String s, out SByte result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<char> s, out sbyte result)
+ {
+ return TryParse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(String s, NumberStyles style, IFormatProvider provider, out SByte result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out sbyte result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return TryParse(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ private static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out SByte result)
+ {
+ result = 0;
+ int i;
+ if (!Number.TryParseInt32(s, style, info, out i))
+ {
+ return false;
+ }
+
+ if ((style & NumberStyles.AllowHexSpecifier) != 0)
+ { // We are parsing a hexadecimal number
+ if ((i < 0) || i > Byte.MaxValue)
+ {
+ return false;
+ }
+ result = (sbyte)i;
+ return true;
+ }
+
+ if (i < MinValue || i > MaxValue)
+ {
+ return false;
+ }
+ result = (sbyte)i;
+ return true;
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.SByte;
+ }
+
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(m_value);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "SByte", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Security/AllowPartiallyTrustedCallersAttribute.cs b/src/System.Private.CoreLib/shared/System/Security/AllowPartiallyTrustedCallersAttribute.cs
new file mode 100644
index 0000000000..84ad65c4c0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/AllowPartiallyTrustedCallersAttribute.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ // AllowPartiallyTrustedCallersAttribute:
+ // Indicates that the Assembly is secure and can be used by untrusted
+ // and semitrusted clients
+ // For v.1, this is valid only on Assemblies, but could be expanded to
+ // include Module, Method, class
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
+ public sealed class AllowPartiallyTrustedCallersAttribute : Attribute
+ {
+ public AllowPartiallyTrustedCallersAttribute() { }
+ public PartialTrustVisibilityLevel PartialTrustVisibilityLevel { get; set; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/CryptographicException.cs b/src/System.Private.CoreLib/shared/System/Security/CryptographicException.cs
new file mode 100644
index 0000000000..78ee290693
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/CryptographicException.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System.Security.Cryptography
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class CryptographicException : SystemException
+ {
+ public CryptographicException()
+ : base(SR.Arg_CryptographyException)
+ {
+ }
+
+ public CryptographicException(int hr)
+ : base(SR.Arg_CryptographyException)
+ {
+ HResult = hr;
+ }
+
+ public CryptographicException(string message)
+ : base(message)
+ {
+ }
+
+ public CryptographicException(string message, Exception inner)
+ : base(message, inner)
+ {
+ }
+
+ public CryptographicException(string format, string insert)
+ : base(string.Format(CultureInfo.CurrentCulture, format, insert))
+ {
+ }
+
+ protected CryptographicException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Security/PartialTrustVisibilityLevel.cs b/src/System.Private.CoreLib/shared/System/Security/PartialTrustVisibilityLevel.cs
new file mode 100644
index 0000000000..a0cb5789ac
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/PartialTrustVisibilityLevel.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ public enum PartialTrustVisibilityLevel
+ {
+ VisibleToAllHosts = 0,
+ NotVisibleByDefault = 1
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/SafeBSTRHandle.cs b/src/System.Private.CoreLib/shared/System/Security/SafeBSTRHandle.cs
new file mode 100644
index 0000000000..bc93fecef1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SafeBSTRHandle.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Security
+{
+ internal sealed class SafeBSTRHandle : SafeBuffer
+ {
+ internal SafeBSTRHandle() : base(true) { }
+
+ internal static SafeBSTRHandle Allocate(uint lenInChars)
+ {
+ ulong lenInBytes = (ulong)lenInChars * sizeof(char);
+ SafeBSTRHandle bstr = Interop.OleAut32.SysAllocStringLen(IntPtr.Zero, lenInChars);
+ if (bstr.IsInvalid) // SysAllocStringLen returns a NULL ptr when there's insufficient memory
+ {
+ throw new OutOfMemoryException();
+ }
+ bstr.Initialize(lenInBytes);
+ return bstr;
+ }
+
+ override protected bool ReleaseHandle()
+ {
+ RuntimeImports.RhZeroMemory(handle, (UIntPtr)(Interop.OleAut32.SysStringLen(handle) * sizeof(char)));
+ Interop.OleAut32.SysFreeString(handle);
+ return true;
+ }
+
+ internal unsafe void ClearBuffer()
+ {
+ byte* bufferPtr = null;
+ try
+ {
+ AcquirePointer(ref bufferPtr);
+ RuntimeImports.RhZeroMemory((IntPtr)bufferPtr, (UIntPtr)(Interop.OleAut32.SysStringLen((IntPtr)bufferPtr) * sizeof(char)));
+ }
+ finally
+ {
+ if (bufferPtr != null)
+ {
+ ReleasePointer();
+ }
+ }
+ }
+
+ internal unsafe uint Length => Interop.OleAut32.SysStringLen(this);
+
+ internal static unsafe void Copy(SafeBSTRHandle source, SafeBSTRHandle target, uint bytesToCopy)
+ {
+ if (bytesToCopy == 0)
+ {
+ return;
+ }
+
+ byte* sourcePtr = null, targetPtr = null;
+ try
+ {
+ source.AcquirePointer(ref sourcePtr);
+ target.AcquirePointer(ref targetPtr);
+
+ Debug.Assert(source.ByteLength >= bytesToCopy, "Source buffer is too small.");
+ Buffer.MemoryCopy(sourcePtr, targetPtr, target.ByteLength, bytesToCopy);
+ }
+ finally
+ {
+ if (targetPtr != null)
+ {
+ target.ReleasePointer();
+ }
+ if (sourcePtr != null)
+ {
+ source.ReleasePointer();
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecureString.Unix.cs b/src/System.Private.CoreLib/shared/System/Security/SecureString.Unix.cs
new file mode 100644
index 0000000000..cfeebc1daf
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecureString.Unix.cs
@@ -0,0 +1,325 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Security
+{
+ // SecureString attempts to provide a defense-in-depth solution.
+ //
+ // On Windows, this is done with several mechanisms:
+ // 1. keeping the data in unmanaged memory so that copies of it aren't implicitly made by the GC moving it around
+ // 2. zero'ing out that unmanaged memory so that the string is reliably removed from memory when done with it
+ // 3. encrypting the data while it's not being used (it's unencrypted to manipulate and use it)
+ //
+ // On Unix, we do 1 and 2, but we don't do 3 as there's no CryptProtectData equivalent.
+
+ public sealed partial class SecureString
+ {
+ private UnmanagedBuffer _buffer;
+
+ internal SecureString(SecureString str)
+ {
+ // Allocate enough space to store the provided string
+ EnsureCapacity(str._decryptedLength);
+ _decryptedLength = str._decryptedLength;
+
+ // Copy the string into the newly allocated space
+ if (_decryptedLength > 0)
+ {
+ UnmanagedBuffer.Copy(str._buffer, _buffer, (ulong)(str._decryptedLength * sizeof(char)));
+ }
+ }
+
+ private unsafe void InitializeSecureString(char* value, int length)
+ {
+ // Allocate enough space to store the provided string
+ EnsureCapacity(length);
+ _decryptedLength = length;
+ if (length == 0)
+ {
+ return;
+ }
+
+ // Copy the string into the newly allocated space
+ byte* ptr = null;
+ try
+ {
+ _buffer.AcquirePointer(ref ptr);
+ Buffer.MemoryCopy(value, ptr, _buffer.ByteLength, (ulong)(length * sizeof(char)));
+ }
+ finally
+ {
+ if (ptr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+
+ private void DisposeCore()
+ {
+ if (_buffer != null && !_buffer.IsInvalid)
+ {
+ _buffer.Dispose();
+ _buffer = null;
+ }
+ }
+
+ private void EnsureNotDisposed()
+ {
+ if (_buffer == null)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+ }
+
+ private void ClearCore()
+ {
+ _decryptedLength = 0;
+ _buffer.Clear();
+ }
+
+ private unsafe void AppendCharCore(char c)
+ {
+ // Make sure we have enough space for the new character, then write it at the end.
+ EnsureCapacity(_decryptedLength + 1);
+ _buffer.Write((ulong)(_decryptedLength * sizeof(char)), c);
+ _decryptedLength++;
+ }
+
+ private unsafe void InsertAtCore(int index, char c)
+ {
+ // Make sure we have enough space for the new character, then shift all of the characters above it and insert it.
+ EnsureCapacity(_decryptedLength + 1);
+ byte* ptr = null;
+ try
+ {
+ _buffer.AcquirePointer(ref ptr);
+ ptr += index * sizeof(char);
+ long bytesToShift = (_decryptedLength - index) * sizeof(char);
+ Buffer.MemoryCopy(ptr, ptr + sizeof(char), bytesToShift, bytesToShift);
+ *((char*)ptr) = c;
+ ++_decryptedLength;
+ }
+ finally
+ {
+ if (ptr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+
+ private unsafe void RemoveAtCore(int index)
+ {
+ // Shift down all values above the specified index, then null out the empty space at the end.
+ byte* ptr = null;
+ try
+ {
+ _buffer.AcquirePointer(ref ptr);
+ ptr += index * sizeof(char);
+ long bytesToShift = (_decryptedLength - index - 1) * sizeof(char);
+ Buffer.MemoryCopy(ptr + sizeof(char), ptr, bytesToShift, bytesToShift);
+ *((char*)(ptr + bytesToShift)) = (char)0;
+ --_decryptedLength;
+ }
+ finally
+ {
+ if (ptr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+
+ private void SetAtCore(int index, char c)
+ {
+ // Overwrite the character at the specified index
+ _buffer.Write((ulong)(index * sizeof(char)), c);
+ }
+
+ internal unsafe IntPtr MarshalToBSTR()
+ {
+ int length = _decryptedLength;
+ IntPtr ptr = IntPtr.Zero;
+ IntPtr result = IntPtr.Zero;
+ byte* bufferPtr = null;
+
+ try
+ {
+ _buffer.AcquirePointer(ref bufferPtr);
+ int resultByteLength = (length + 1) * sizeof(char);
+
+ ptr = PInvokeMarshal.AllocBSTR(length);
+
+ Buffer.MemoryCopy(bufferPtr, (byte*)ptr, resultByteLength, length * sizeof(char));
+
+ result = ptr;
+ }
+ finally
+ {
+ // If we failed for any reason, free the new buffer
+ if (result == IntPtr.Zero && ptr != IntPtr.Zero)
+ {
+ RuntimeImports.RhZeroMemory(ptr, (UIntPtr)(length * sizeof(char)));
+ PInvokeMarshal.FreeBSTR(ptr);
+ }
+
+ if (bufferPtr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ return result;
+ }
+
+ internal unsafe IntPtr MarshalToStringCore(bool globalAlloc, bool unicode)
+ {
+ int length = _decryptedLength;
+
+ byte* bufferPtr = null;
+ IntPtr stringPtr = IntPtr.Zero, result = IntPtr.Zero;
+ try
+ {
+ _buffer.AcquirePointer(ref bufferPtr);
+ if (unicode)
+ {
+ int resultLength = (length + 1) * sizeof(char);
+ stringPtr = globalAlloc ? Marshal.AllocHGlobal(resultLength) : Marshal.AllocCoTaskMem(resultLength);
+ Buffer.MemoryCopy(
+ source: bufferPtr,
+ destination: (byte*)stringPtr.ToPointer(),
+ destinationSizeInBytes: resultLength,
+ sourceBytesToCopy: length * sizeof(char));
+ *(length + (char*)stringPtr) = '\0';
+ }
+ else
+ {
+ int resultLength = Encoding.UTF8.GetByteCount((char*)bufferPtr, length) + 1;
+ stringPtr = globalAlloc ? Marshal.AllocHGlobal(resultLength) : Marshal.AllocCoTaskMem(resultLength);
+ int encodedLength = Encoding.UTF8.GetBytes((char*)bufferPtr, length, (byte*)stringPtr, resultLength);
+ Debug.Assert(encodedLength + 1 == resultLength, $"Expected encoded length to match result, got {encodedLength} != {resultLength}");
+ *(resultLength - 1 + (byte*)stringPtr) = 0;
+ }
+
+ result = stringPtr;
+ }
+ finally
+ {
+ // If there was a failure, such that result isn't initialized,
+ // release the string if we had one.
+ if (stringPtr != IntPtr.Zero && result == IntPtr.Zero)
+ {
+ RuntimeImports.RhZeroMemory(stringPtr, (UIntPtr)(length * sizeof(char)));
+ MarshalFree(stringPtr, globalAlloc);
+ }
+
+ if (bufferPtr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+
+ return result;
+ }
+
+ // -----------------------------
+ // ---- PAL layer ends here ----
+ // -----------------------------
+
+ private void EnsureCapacity(int capacity)
+ {
+ // Make sure the requested capacity doesn't exceed SecureString's defined limit
+ if (capacity > MaxLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity);
+ }
+
+ // If we already have enough space allocated, we're done
+ if (_buffer != null && (capacity * sizeof(char)) <= (int)_buffer.ByteLength)
+ {
+ return;
+ }
+
+ // We need more space, so allocate a new buffer, copy all our data into it,
+ // and then swap the new for the old.
+ UnmanagedBuffer newBuffer = UnmanagedBuffer.Allocate(capacity * sizeof(char));
+ if (_buffer != null)
+ {
+ UnmanagedBuffer.Copy(_buffer, newBuffer, _buffer.ByteLength);
+ _buffer.Dispose();
+ }
+ _buffer = newBuffer;
+ }
+
+ /// <summary>SafeBuffer for managing memory meant to be kept confidential.</summary>
+ private sealed class UnmanagedBuffer : SafeBuffer
+ {
+ internal UnmanagedBuffer() : base(true) { }
+
+ internal static UnmanagedBuffer Allocate(int bytes)
+ {
+ Debug.Assert(bytes >= 0);
+ UnmanagedBuffer buffer = new UnmanagedBuffer();
+ buffer.SetHandle(Marshal.AllocHGlobal(bytes));
+ buffer.Initialize((ulong)bytes);
+ return buffer;
+ }
+
+ internal unsafe void Clear()
+ {
+ byte* ptr = null;
+ try
+ {
+ AcquirePointer(ref ptr);
+ RuntimeImports.RhZeroMemory((IntPtr)ptr, (UIntPtr)ByteLength);
+ }
+ finally
+ {
+ if (ptr != null)
+ {
+ ReleasePointer();
+ }
+ }
+ }
+
+ internal static unsafe void Copy(UnmanagedBuffer source, UnmanagedBuffer destination, ulong bytesLength)
+ {
+ if (bytesLength == 0)
+ {
+ return;
+ }
+
+ byte* srcPtr = null, dstPtr = null;
+ try
+ {
+ source.AcquirePointer(ref srcPtr);
+ destination.AcquirePointer(ref dstPtr);
+ Buffer.MemoryCopy(srcPtr, dstPtr, destination.ByteLength, bytesLength);
+ }
+ finally
+ {
+ if (dstPtr != null)
+ {
+ destination.ReleasePointer();
+ }
+ if (srcPtr != null)
+ {
+ source.ReleasePointer();
+ }
+ }
+ }
+
+ protected override unsafe bool ReleaseHandle()
+ {
+ Marshal.FreeHGlobal(handle);
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecureString.Windows.cs b/src/System.Private.CoreLib/shared/System/Security/SecureString.Windows.cs
new file mode 100644
index 0000000000..2a80081912
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecureString.Windows.cs
@@ -0,0 +1,308 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using Microsoft.Win32;
+
+namespace System.Security
+{
+ public sealed partial class SecureString
+ {
+ internal SecureString(SecureString str)
+ {
+ Debug.Assert(str != null, "Expected non-null SecureString");
+ Debug.Assert(str._buffer != null, "Expected other SecureString's buffer to be non-null");
+ Debug.Assert(str._encrypted, "Expected to be used only on encrypted SecureStrings");
+
+ AllocateBuffer(str._buffer.Length);
+ SafeBSTRHandle.Copy(str._buffer, _buffer, str._buffer.Length * sizeof(char));
+
+ _decryptedLength = str._decryptedLength;
+ _encrypted = str._encrypted;
+ }
+
+ private unsafe void InitializeSecureString(char* value, int length)
+ {
+ Debug.Assert(length >= 0, $"Expected non-negative length, got {length}");
+
+ AllocateBuffer((uint)length);
+ _decryptedLength = length;
+
+ byte* bufferPtr = null;
+ try
+ {
+ _buffer.AcquirePointer(ref bufferPtr);
+ Buffer.MemoryCopy((byte*)value, bufferPtr, (long)_buffer.ByteLength, length * sizeof(char));
+ }
+ finally
+ {
+ if (bufferPtr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+
+ ProtectMemory();
+ }
+
+ private void AppendCharCore(char c)
+ {
+ UnprotectMemory();
+ try
+ {
+ EnsureCapacity(_decryptedLength + 1);
+ _buffer.Write<char>((uint)_decryptedLength * sizeof(char), c);
+ _decryptedLength++;
+ }
+ finally
+ {
+ ProtectMemory();
+ }
+ }
+
+ private void ClearCore()
+ {
+ _decryptedLength = 0;
+ _buffer.ClearBuffer();
+ }
+
+ private void DisposeCore()
+ {
+ if (_buffer != null)
+ {
+ _buffer.Dispose();
+ _buffer = null;
+ }
+ }
+
+ private unsafe void InsertAtCore(int index, char c)
+ {
+ byte* bufferPtr = null;
+ UnprotectMemory();
+ try
+ {
+ EnsureCapacity(_decryptedLength + 1);
+
+ _buffer.AcquirePointer(ref bufferPtr);
+ char* pBuffer = (char*)bufferPtr;
+
+ for (int i = _decryptedLength; i > index; i--)
+ {
+ pBuffer[i] = pBuffer[i - 1];
+ }
+ pBuffer[index] = c;
+ ++_decryptedLength;
+ }
+ finally
+ {
+ ProtectMemory();
+ if (bufferPtr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+
+ private unsafe void RemoveAtCore(int index)
+ {
+ byte* bufferPtr = null;
+ UnprotectMemory();
+ try
+ {
+ _buffer.AcquirePointer(ref bufferPtr);
+ char* pBuffer = (char*)bufferPtr;
+
+ for (int i = index; i < _decryptedLength - 1; i++)
+ {
+ pBuffer[i] = pBuffer[i + 1];
+ }
+ pBuffer[--_decryptedLength] = (char)0;
+ }
+ finally
+ {
+ ProtectMemory();
+ if (bufferPtr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ }
+
+ private void SetAtCore(int index, char c)
+ {
+ UnprotectMemory();
+ try
+ {
+ _buffer.Write<char>((uint)index * sizeof(char), c);
+ }
+ finally
+ {
+ ProtectMemory();
+ }
+ }
+
+ internal unsafe IntPtr MarshalToBSTR()
+ {
+ int length = _decryptedLength;
+ IntPtr ptr = IntPtr.Zero;
+ IntPtr result = IntPtr.Zero;
+ byte* bufferPtr = null;
+
+ UnprotectMemory();
+ try
+ {
+ _buffer.AcquirePointer(ref bufferPtr);
+ int resultByteLength = (length + 1) * sizeof(char);
+
+ ptr = PInvokeMarshal.AllocBSTR(length);
+
+ Buffer.MemoryCopy(bufferPtr, (byte*)ptr, resultByteLength, length * sizeof(char));
+
+ result = ptr;
+ }
+ finally
+ {
+ ProtectMemory();
+
+ // If we failed for any reason, free the new buffer
+ if (result == IntPtr.Zero && ptr != IntPtr.Zero)
+ {
+ RuntimeImports.RhZeroMemory(ptr, (UIntPtr)(length * sizeof(char)));
+ PInvokeMarshal.FreeBSTR(ptr);
+ }
+
+ if (bufferPtr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ return result;
+ }
+
+ internal unsafe IntPtr MarshalToStringCore(bool globalAlloc, bool unicode)
+ {
+ int length = _decryptedLength;
+ IntPtr ptr = IntPtr.Zero;
+ IntPtr result = IntPtr.Zero;
+ byte* bufferPtr = null;
+
+ UnprotectMemory();
+ try
+ {
+ _buffer.AcquirePointer(ref bufferPtr);
+ if (unicode)
+ {
+ int resultByteLength = (length + 1) * sizeof(char);
+ ptr = globalAlloc ? Marshal.AllocHGlobal(resultByteLength) : Marshal.AllocCoTaskMem(resultByteLength);
+ Buffer.MemoryCopy(bufferPtr, (byte*)ptr, resultByteLength, length * sizeof(char));
+ *(length + (char*)ptr) = '\0';
+ }
+ else
+ {
+ uint defaultChar = '?';
+ int resultByteLength = 1 + Interop.Kernel32.WideCharToMultiByte(
+ Interop.Kernel32.CP_ACP, Interop.Kernel32.WC_NO_BEST_FIT_CHARS, (char*)bufferPtr, length, null, 0, (IntPtr)(&defaultChar), IntPtr.Zero);
+ ptr = globalAlloc ? Marshal.AllocHGlobal(resultByteLength) : Marshal.AllocCoTaskMem(resultByteLength);
+ Interop.Kernel32.WideCharToMultiByte(
+ Interop.Kernel32.CP_ACP, Interop.Kernel32.WC_NO_BEST_FIT_CHARS, (char*)bufferPtr, length, (byte*)ptr, resultByteLength - 1, (IntPtr)(&defaultChar), IntPtr.Zero);
+ *(resultByteLength - 1 + (byte*)ptr) = 0;
+ }
+ result = ptr;
+ }
+ finally
+ {
+ ProtectMemory();
+
+ // If we failed for any reason, free the new buffer
+ if (result == IntPtr.Zero && ptr != IntPtr.Zero)
+ {
+ RuntimeImports.RhZeroMemory(ptr, (UIntPtr)(length * sizeof(char)));
+ MarshalFree(ptr, globalAlloc);
+ }
+
+ if (bufferPtr != null)
+ {
+ _buffer.ReleasePointer();
+ }
+ }
+ return result;
+ }
+
+ private void EnsureNotDisposed()
+ {
+ if (_buffer == null)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+ }
+
+ // -----------------------------
+ // ---- PAL layer ends here ----
+ // -----------------------------
+
+ private const int BlockSize = (int)Interop.Crypt32.CRYPTPROTECTMEMORY_BLOCK_SIZE / sizeof(char);
+ private SafeBSTRHandle _buffer;
+ private bool _encrypted;
+
+ private void AllocateBuffer(uint size)
+ {
+ _buffer = SafeBSTRHandle.Allocate(GetAlignedSize(size));
+ }
+
+ private static uint GetAlignedSize(uint size) =>
+ size == 0 || size % BlockSize != 0 ?
+ BlockSize + ((size / BlockSize) * BlockSize) :
+ size;
+
+ private void EnsureCapacity(int capacity)
+ {
+ if (capacity > MaxLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity);
+ }
+
+ if (((uint)capacity * sizeof(char)) <= _buffer.ByteLength)
+ {
+ return;
+ }
+
+ var oldBuffer = _buffer;
+ SafeBSTRHandle newBuffer = SafeBSTRHandle.Allocate(GetAlignedSize((uint)capacity));
+ SafeBSTRHandle.Copy(oldBuffer, newBuffer, (uint)_decryptedLength * sizeof(char));
+ _buffer = newBuffer;
+ oldBuffer.Dispose();
+ }
+
+ private void ProtectMemory()
+ {
+ Debug.Assert(!_buffer.IsInvalid, "Invalid buffer!");
+
+ if (_decryptedLength != 0 &&
+ !_encrypted &&
+ !Interop.Crypt32.CryptProtectMemory(_buffer, _buffer.Length * sizeof(char), Interop.Crypt32.CRYPTPROTECTMEMORY_SAME_PROCESS))
+ {
+ throw new CryptographicException(Marshal.GetLastWin32Error());
+ }
+
+ _encrypted = true;
+ }
+
+ private void UnprotectMemory()
+ {
+ Debug.Assert(!_buffer.IsInvalid, "Invalid buffer!");
+
+ if (_decryptedLength != 0 &&
+ _encrypted &&
+ !Interop.Crypt32.CryptUnprotectMemory(_buffer, _buffer.Length * sizeof(char), Interop.Crypt32.CRYPTPROTECTMEMORY_SAME_PROCESS))
+ {
+ throw new CryptographicException(Marshal.GetLastWin32Error());
+ }
+
+ _encrypted = false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecureString.cs b/src/System.Private.CoreLib/shared/System/Security/SecureString.cs
new file mode 100644
index 0000000000..22f15accaa
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecureString.cs
@@ -0,0 +1,180 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.Security
+{
+ public sealed partial class SecureString : IDisposable
+ {
+ private const int MaxLength = 65536;
+ private readonly object _methodLock = new object();
+ private bool _readOnly;
+ private int _decryptedLength;
+
+ public unsafe SecureString()
+ {
+ InitializeSecureString(null, 0);
+ }
+
+ [CLSCompliant(false)]
+ public unsafe SecureString(char* value, int length)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ if (length > MaxLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Length);
+ }
+
+ InitializeSecureString(value, length);
+ }
+
+ public int Length
+ {
+ get
+ {
+ EnsureNotDisposed();
+ return Volatile.Read(ref _decryptedLength);
+ }
+ }
+
+ public void AppendChar(char c)
+ {
+ lock (_methodLock)
+ {
+ EnsureNotDisposed();
+ EnsureNotReadOnly();
+ AppendCharCore(c);
+ }
+ }
+
+ // clears the current contents. Only available if writable
+ public void Clear()
+ {
+ lock (_methodLock)
+ {
+ EnsureNotDisposed();
+ EnsureNotReadOnly();
+ ClearCore();
+ }
+ }
+
+ // Do a deep-copy of the SecureString
+ public SecureString Copy()
+ {
+ lock (_methodLock)
+ {
+ EnsureNotDisposed();
+ return new SecureString(this);
+ }
+ }
+
+ public void Dispose()
+ {
+ lock (_methodLock)
+ {
+ DisposeCore();
+ }
+ }
+
+ public void InsertAt(int index, char c)
+ {
+ lock (_methodLock)
+ {
+ if (index < 0 || index > _decryptedLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString);
+ }
+
+ EnsureNotDisposed();
+ EnsureNotReadOnly();
+
+ InsertAtCore(index, c);
+ }
+ }
+
+ public bool IsReadOnly()
+ {
+ EnsureNotDisposed();
+ return Volatile.Read(ref _readOnly);
+ }
+
+ public void MakeReadOnly()
+ {
+ EnsureNotDisposed();
+ Volatile.Write(ref _readOnly, true);
+ }
+
+ public void RemoveAt(int index)
+ {
+ lock (_methodLock)
+ {
+ EnsureNotDisposed();
+ EnsureNotReadOnly();
+
+ if (index < 0 || index >= _decryptedLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString);
+ }
+
+ RemoveAtCore(index);
+ }
+ }
+
+ public void SetAt(int index, char c)
+ {
+ lock (_methodLock)
+ {
+ if (index < 0 || index >= _decryptedLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString);
+ }
+ Debug.Assert(index <= Int32.MaxValue / sizeof(char));
+
+ EnsureNotDisposed();
+ EnsureNotReadOnly();
+
+ SetAtCore(index, c);
+ }
+ }
+
+ private void EnsureNotReadOnly()
+ {
+ if (_readOnly)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ }
+ }
+
+ internal unsafe IntPtr MarshalToString(bool globalAlloc, bool unicode)
+ {
+ lock (_methodLock)
+ {
+ EnsureNotDisposed();
+ return MarshalToStringCore(globalAlloc, unicode);
+ }
+ }
+
+ private static void MarshalFree(IntPtr ptr, bool globalAlloc)
+ {
+ if (globalAlloc)
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+ else
+ {
+ Marshal.FreeCoTaskMem(ptr);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecurityCriticalAttribute.cs b/src/System.Private.CoreLib/shared/System/Security/SecurityCriticalAttribute.cs
new file mode 100644
index 0000000000..2bf1700afb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecurityCriticalAttribute.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ // SecurityCriticalAttribute
+ // Indicates that the decorated code or assembly performs security critical operations (e.g. Assert, "unsafe", LinkDemand, etc.)
+ // The attribute can be placed on most targets, except on arguments/return values.
+ [AttributeUsage(AttributeTargets.Assembly |
+ AttributeTargets.Class |
+ AttributeTargets.Struct |
+ AttributeTargets.Enum |
+ AttributeTargets.Constructor |
+ AttributeTargets.Method |
+ AttributeTargets.Field |
+ AttributeTargets.Interface |
+ AttributeTargets.Delegate,
+ AllowMultiple = false,
+ Inherited = false)]
+ public sealed class SecurityCriticalAttribute : Attribute
+ {
+#pragma warning disable 618 // We still use SecurityCriticalScope for v2 compat
+ public SecurityCriticalAttribute() { }
+
+ public SecurityCriticalAttribute(SecurityCriticalScope scope)
+ {
+ Scope = scope;
+ }
+
+ [Obsolete("SecurityCriticalScope is only used for .NET 2.0 transparency compatibility.")]
+ public SecurityCriticalScope Scope { get; }
+#pragma warning restore 618
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecurityCriticalScope.cs b/src/System.Private.CoreLib/shared/System/Security/SecurityCriticalScope.cs
new file mode 100644
index 0000000000..e0f5a8e2cd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecurityCriticalScope.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ [Obsolete("SecurityCriticalScope is only used for .NET 2.0 transparency compatibility.")]
+ public enum SecurityCriticalScope
+ {
+ Explicit = 0,
+ Everything = 0x1
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecurityException.cs b/src/System.Private.CoreLib/shared/System/Security/SecurityException.cs
new file mode 100644
index 0000000000..61504c3ba1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecurityException.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Runtime.Serialization;
+
+namespace System.Security
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class SecurityException : SystemException
+ {
+ private const string DemandedName = "Demanded";
+ private const string GrantedSetName = "GrantedSet";
+ private const string RefusedSetName = "RefusedSet";
+ private const string DeniedName = "Denied";
+ private const string PermitOnlyName = "PermitOnly";
+ private const string UrlName = "Url";
+
+ public SecurityException()
+ : base(SR.Arg_SecurityException)
+ {
+ HResult = HResults.COR_E_SECURITY;
+ }
+
+ public SecurityException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_SECURITY;
+ }
+
+ public SecurityException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_SECURITY;
+ }
+
+ public SecurityException(string message, Type type)
+ : base(message)
+ {
+ HResult = HResults.COR_E_SECURITY;
+ PermissionType = type;
+ }
+
+ public SecurityException(string message, Type type, string state)
+ : base(message)
+ {
+ HResult = HResults.COR_E_SECURITY;
+ PermissionType = type;
+ PermissionState = state;
+ }
+
+ protected SecurityException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ Demanded = (string)info.GetValueNoThrow(DemandedName, typeof(string));
+ GrantedSet = (string)info.GetValueNoThrow(GrantedSetName, typeof(string));
+ RefusedSet = (string)info.GetValueNoThrow(RefusedSetName, typeof(string));
+ DenySetInstance = (string)info.GetValueNoThrow(DeniedName, typeof(string));
+ PermitOnlySetInstance = (string)info.GetValueNoThrow(PermitOnlyName, typeof(string));
+ Url = (string)info.GetValueNoThrow(UrlName, typeof(string));
+ }
+
+ public override string ToString() => base.ToString();
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue(DemandedName, Demanded, typeof(string));
+ info.AddValue(GrantedSetName, GrantedSet, typeof(string));
+ info.AddValue(RefusedSetName, RefusedSet, typeof(string));
+ info.AddValue(DeniedName, DenySetInstance, typeof(string));
+ info.AddValue(PermitOnlyName, PermitOnlySetInstance, typeof(string));
+ info.AddValue(UrlName, Url, typeof(string));
+ }
+
+ public object Demanded { get; set; }
+ public object DenySetInstance { get; set; }
+ public AssemblyName FailedAssemblyInfo { get; set; }
+ public string GrantedSet { get; set; }
+ public MethodInfo Method { get; set; }
+ public string PermissionState { get; set; }
+ public Type PermissionType { get; set; }
+ public object PermitOnlySetInstance { get; set; }
+ public string RefusedSet { get; set; }
+ public string Url { get; set; }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecurityRuleSet.cs b/src/System.Private.CoreLib/shared/System/Security/SecurityRuleSet.cs
new file mode 100644
index 0000000000..1b62fd4e7d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecurityRuleSet.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ public enum SecurityRuleSet : byte
+ {
+ None = 0,
+ Level1 = 1, // v2.0 transparency model
+ Level2 = 2, // v4.0 transparency model
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecurityRulesAttribute.cs b/src/System.Private.CoreLib/shared/System/Security/SecurityRulesAttribute.cs
new file mode 100644
index 0000000000..ad17087f8b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecurityRulesAttribute.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ // SecurityRulesAttribute
+ //
+ // Indicates which set of security rules an assembly was authored against, and therefore which set of
+ // rules the runtime should enforce on the assembly. For instance, an assembly marked with
+ // [SecurityRules(SecurityRuleSet.Level1)] will follow the v2.0 transparency rules, where transparent code
+ // can call a LinkDemand by converting it to a full demand, public critical methods are implicitly
+ // treat as safe, and the remainder of the v2.0 rules apply.
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
+ public sealed class SecurityRulesAttribute : Attribute
+ {
+ public SecurityRulesAttribute(SecurityRuleSet ruleSet)
+ {
+ RuleSet = ruleSet;
+ }
+
+ // Should fully trusted transparent code skip IL verification
+ public bool SkipVerificationInFullTrust { get; set; }
+
+ public SecurityRuleSet RuleSet { get; }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecuritySafeCriticalAttribute.cs b/src/System.Private.CoreLib/shared/System/Security/SecuritySafeCriticalAttribute.cs
new file mode 100644
index 0000000000..ee2e4b0499
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecuritySafeCriticalAttribute.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ // SecuritySafeCriticalAttribute:
+ // Indicates that the code may contain violations to the security critical rules (e.g. transitions from
+ // critical to non-public transparent, transparent to non-public critical, etc.), has been audited for
+ // security concerns and is considered security clean. Also indicates that the code is considered SecurityCritical.
+ // The effect of this attribute is as if the code was marked [SecurityCritical][SecurityTreatAsSafe].
+ // At assembly-scope, all rule checks will be suppressed within the assembly and for calls made against the assembly.
+ // At type-scope, all rule checks will be suppressed for members within the type and for calls made against the type.
+ // At member level (e.g. field and method) the code will be treated as public - i.e. no rule checks for the members.
+
+ [AttributeUsage(AttributeTargets.Class |
+ AttributeTargets.Struct |
+ AttributeTargets.Enum |
+ AttributeTargets.Constructor |
+ AttributeTargets.Method |
+ AttributeTargets.Field |
+ AttributeTargets.Interface |
+ AttributeTargets.Delegate,
+ AllowMultiple = false,
+ Inherited = false)]
+ public sealed class SecuritySafeCriticalAttribute : Attribute
+ {
+ public SecuritySafeCriticalAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecurityTransparentAttribute.cs b/src/System.Private.CoreLib/shared/System/Security/SecurityTransparentAttribute.cs
new file mode 100644
index 0000000000..03f41387ae
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecurityTransparentAttribute.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ // SecurityTransparentAttribute:
+ // Indicates the assembly contains only transparent code.
+ // Security critical actions will be restricted or converted into less critical actions. For example,
+ // Assert will be restricted, SuppressUnmanagedCode, LinkDemand, unsafe, and unverifiable code will be converted
+ // into Full-Demands.
+
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
+ public sealed class SecurityTransparentAttribute : Attribute
+ {
+ public SecurityTransparentAttribute() { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/SecurityTreatAsSafeAttribute.cs b/src/System.Private.CoreLib/shared/System/Security/SecurityTreatAsSafeAttribute.cs
new file mode 100644
index 0000000000..7a95122bf0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SecurityTreatAsSafeAttribute.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ // SecurityTreatAsSafeAttribute:
+ // Indicates that the code may contain violations to the security critical rules (e.g. transitions from
+ // critical to non-public transparent, transparent to non-public critical, etc.), has been audited for
+ // security concerns and is considered security clean.
+ // At assembly-scope, all rule checks will be suppressed within the assembly and for calls made against the assembly.
+ // At type-scope, all rule checks will be suppressed for members within the type and for calls made against the type.
+ // At member level (e.g. field and method) the code will be treated as public - i.e. no rule checks for the members.
+
+ [AttributeUsage(AttributeTargets.Assembly |
+ AttributeTargets.Class |
+ AttributeTargets.Struct |
+ AttributeTargets.Enum |
+ AttributeTargets.Constructor |
+ AttributeTargets.Method |
+ AttributeTargets.Field |
+ AttributeTargets.Interface |
+ AttributeTargets.Delegate,
+ AllowMultiple = false,
+ Inherited = false)]
+ [Obsolete("SecurityTreatAsSafe is only used for .NET 2.0 transparency compatibility. Please use the SecuritySafeCriticalAttribute instead.")]
+ public sealed class SecurityTreatAsSafeAttribute : Attribute
+ {
+ public SecurityTreatAsSafeAttribute() { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/SuppressUnmanagedCodeSecurityAttribute.cs b/src/System.Private.CoreLib/shared/System/Security/SuppressUnmanagedCodeSecurityAttribute.cs
new file mode 100644
index 0000000000..a60b8d3668
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/SuppressUnmanagedCodeSecurityAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ // SuppressUnmanagedCodeSecurityAttribute:
+ // Indicates that the target P/Invoke method(s) should skip the per-call
+ // security checked for unmanaged code permission.
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = true, Inherited = false)]
+ public sealed class SuppressUnmanagedCodeSecurityAttribute : Attribute
+ {
+ public SuppressUnmanagedCodeSecurityAttribute() { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/UnverifiableCodeAttribute.cs b/src/System.Private.CoreLib/shared/System/Security/UnverifiableCodeAttribute.cs
new file mode 100644
index 0000000000..1560b6617b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/UnverifiableCodeAttribute.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security
+{
+ // UnverifiableCodeAttribute:
+ // Indicates that the target module contains unverifiable code.
+ [AttributeUsage(AttributeTargets.Module, AllowMultiple = true, Inherited = false)]
+ public sealed class UnverifiableCodeAttribute : Attribute
+ {
+ public UnverifiableCodeAttribute() { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Security/VerificationException.cs b/src/System.Private.CoreLib/shared/System/Security/VerificationException.cs
new file mode 100644
index 0000000000..e2afd4cabe
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Security/VerificationException.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Security
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class VerificationException : SystemException
+ {
+ public VerificationException()
+ : base(SR.Verification_Exception)
+ {
+ HResult = HResults.COR_E_VERIFICATION;
+ }
+
+ public VerificationException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_VERIFICATION;
+ }
+
+ public VerificationException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_VERIFICATION;
+ }
+
+ protected VerificationException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SerializableAttribute.cs b/src/System.Private.CoreLib/shared/System/SerializableAttribute.cs
new file mode 100644
index 0000000000..c256931373
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SerializableAttribute.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)]
+ public sealed class SerializableAttribute : Attribute
+ {
+ public SerializableAttribute() { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Single.cs b/src/System.Private.CoreLib/shared/System/Single.cs
new file mode 100644
index 0000000000..a74d3770fa
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Single.cs
@@ -0,0 +1,443 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: A wrapper class for the primitive type float.
+**
+**
+===========================================================*/
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct Single : IComparable, IConvertible, IFormattable, IComparable<Single>, IEquatable<Single>, ISpanFormattable
+ {
+ private float m_value; // Do not rename (binary serialization)
+
+ //
+ // Public constants
+ //
+ public const float MinValue = (float)-3.40282346638528859e+38;
+ public const float Epsilon = (float)1.4e-45;
+ public const float MaxValue = (float)3.40282346638528859e+38;
+ public const float PositiveInfinity = (float)1.0 / (float)0.0;
+ public const float NegativeInfinity = (float)-1.0 / (float)0.0;
+ public const float NaN = (float)0.0 / (float)0.0;
+
+ // We use this explicit definition to avoid the confusion between 0.0 and -0.0.
+ internal const float NegativeZero = (float)-0.0;
+
+ /// <summary>Determines whether the specified value is finite (zero, subnormal, or normal).</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsFinite(float f)
+ {
+ var bits = BitConverter.SingleToInt32Bits(f);
+ return (bits & 0x7FFFFFFF) < 0x7F800000;
+ }
+
+ /// <summary>Determines whether the specified value is infinite.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsInfinity(float f)
+ {
+ var bits = BitConverter.SingleToInt32Bits(f);
+ return (bits & 0x7FFFFFFF) == 0x7F800000;
+ }
+
+ /// <summary>Determines whether the specified value is NaN.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsNaN(float f)
+ {
+ var bits = BitConverter.SingleToInt32Bits(f);
+ return (bits & 0x7FFFFFFF) > 0x7F800000;
+ }
+
+ /// <summary>Determines whether the specified value is negative.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsNegative(float f)
+ {
+ var bits = unchecked((uint)BitConverter.SingleToInt32Bits(f));
+ return (bits & 0x80000000) == 0x80000000;
+ }
+
+ /// <summary>Determines whether the specified value is negative infinity.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsNegativeInfinity(float f)
+ {
+ return (f == float.NegativeInfinity);
+ }
+
+ /// <summary>Determines whether the specified value is normal.</summary>
+ [NonVersionable]
+ // This is probably not worth inlining, it has branches and should be rarely called
+ public static unsafe bool IsNormal(float f)
+ {
+ var bits = BitConverter.SingleToInt32Bits(f);
+ bits &= 0x7FFFFFFF;
+ return (bits < 0x7F800000) && (bits != 0) && ((bits & 0x7F800000) != 0);
+ }
+
+ /// <summary>Determines whether the specified value is positive infinity.</summary>
+ [NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsPositiveInfinity(float f)
+ {
+ return (f == float.PositiveInfinity);
+ }
+
+ /// <summary>Determines whether the specified value is subnormal.</summary>
+ [NonVersionable]
+ // This is probably not worth inlining, it has branches and should be rarely called
+ public static unsafe bool IsSubnormal(float f)
+ {
+ var bits = BitConverter.SingleToInt32Bits(f);
+ bits &= 0x7FFFFFFF;
+ return (bits < 0x7F800000) && (bits != 0) && ((bits & 0x7F800000) == 0);
+ }
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type Single, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (value is Single)
+ {
+ float f = (float)value;
+ if (m_value < f) return -1;
+ if (m_value > f) return 1;
+ if (m_value == f) return 0;
+
+ // At least one of the values is NaN.
+ if (IsNaN(m_value))
+ return (IsNaN(f) ? 0 : -1);
+ else // f is NaN.
+ return 1;
+ }
+ throw new ArgumentException(SR.Arg_MustBeSingle);
+ }
+
+
+ public int CompareTo(Single value)
+ {
+ if (m_value < value) return -1;
+ if (m_value > value) return 1;
+ if (m_value == value) return 0;
+
+ // At least one of the values is NaN.
+ if (IsNaN(m_value))
+ return (IsNaN(value) ? 0 : -1);
+ else // f is NaN.
+ return 1;
+ }
+
+ [NonVersionable]
+ public static bool operator ==(Single left, Single right)
+ {
+ return left == right;
+ }
+
+ [NonVersionable]
+ public static bool operator !=(Single left, Single right)
+ {
+ return left != right;
+ }
+
+ [NonVersionable]
+ public static bool operator <(Single left, Single right)
+ {
+ return left < right;
+ }
+
+ [NonVersionable]
+ public static bool operator >(Single left, Single right)
+ {
+ return left > right;
+ }
+
+ [NonVersionable]
+ public static bool operator <=(Single left, Single right)
+ {
+ return left <= right;
+ }
+
+ [NonVersionable]
+ public static bool operator >=(Single left, Single right)
+ {
+ return left >= right;
+ }
+
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is Single))
+ {
+ return false;
+ }
+ float temp = ((Single)obj).m_value;
+ if (temp == m_value)
+ {
+ return true;
+ }
+
+ return IsNaN(temp) && IsNaN(m_value);
+ }
+
+ public bool Equals(Single obj)
+ {
+ if (obj == m_value)
+ {
+ return true;
+ }
+
+ return IsNaN(obj) && IsNaN(m_value);
+ }
+
+ public override int GetHashCode()
+ {
+ var bits = Unsafe.As<float, int>(ref m_value);
+
+ // Optimized check for IsNan() || IsZero()
+ if (((bits - 1) & 0x7FFFFFFF) >= 0x7F800000)
+ {
+ // Ensure that all NaNs and both zeros have the same hash code
+ bits &= 0x7F800000;
+ }
+
+ return bits;
+ }
+
+ public override String ToString()
+ {
+ return Number.FormatSingle(m_value, null, NumberFormatInfo.CurrentInfo);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatSingle(m_value, null, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public String ToString(String format)
+ {
+ return Number.FormatSingle(m_value, format, NumberFormatInfo.CurrentInfo);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return Number.FormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ return Number.TryFormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten);
+ }
+
+ // Parses a float from a String in the given style. If
+ // a NumberFormatInfo isn't specified, the current culture's
+ // NumberFormatInfo is assumed.
+ //
+ // This method will not throw an OverflowException, but will return
+ // PositiveInfinity or NegativeInfinity for a number that is too
+ // large or too small.
+ //
+ public static float Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseSingle(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static float Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseSingle(s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ public static float Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseSingle(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static float Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseSingle(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static float Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ return Number.ParseSingle(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ public static Boolean TryParse(String s, out Single result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, out float result)
+ {
+ return TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static Boolean TryParse(String s, NumberStyles style, IFormatProvider provider, out Single result)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out float result)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ return TryParse(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ private static Boolean TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out Single result)
+ {
+ bool success = Number.TryParseSingle(s, style, info, out result);
+ if (!success)
+ {
+ ReadOnlySpan<char> sTrim = s.Trim();
+ if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol))
+ {
+ result = PositiveInfinity;
+ }
+ else if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol))
+ {
+ result = NegativeInfinity;
+ }
+ else if (sTrim.EqualsOrdinal(info.NaNSymbol))
+ {
+ result = NaN;
+ }
+ else
+ {
+ return false; // We really failed
+ }
+ }
+ return true;
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Single;
+ }
+
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Single", "Char"));
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Single", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Span.Fast.cs b/src/System.Private.CoreLib/shared/System/Span.Fast.cs
new file mode 100644
index 0000000000..131694bc0c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Span.Fast.cs
@@ -0,0 +1,357 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+using Internal.Runtime.CompilerServices;
+
+#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)'
+
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
+namespace System
+{
+ /// <summary>
+ /// Span represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed
+ /// or native memory, or to memory allocated on the stack. It is type- and memory-safe.
+ /// </summary>
+ [DebuggerTypeProxy(typeof(SpanDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ [NonVersionable]
+ public readonly ref partial struct Span<T>
+ {
+ /// <summary>A byref or a native ptr.</summary>
+ internal readonly ByReference<T> _pointer;
+ /// <summary>The number of elements this Span contains.</summary>
+#if PROJECTN
+ [Bound]
+#endif
+ private readonly int _length;
+
+ /// <summary>
+ /// Creates a new span over the entirety of the target array.
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// reference (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span(T[] array)
+ {
+ if (array == null)
+ {
+ this = default;
+ return; // returns default
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+
+ _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()));
+ _length = array.Length;
+ }
+
+ /// <summary>
+ /// Creates a new span over the portion of the target array beginning
+ /// at 'start' index and ending at 'end' index (exclusive).
+ /// </summary>
+ /// <param name="array">The target array.</param>
+ /// <param name="start">The index at which to begin the span.</param>
+ /// <param name="length">The number of items in the span.</param>
+ /// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
+ /// reference (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span(T[] array, int start, int length)
+ {
+ if (array == null)
+ {
+ if (start != 0 || length != 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+ this = default;
+ return; // returns default
+ }
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ ThrowHelper.ThrowArrayTypeMismatchException();
+ if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _pointer = new ByReference<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start));
+ _length = length;
+ }
+
+ /// <summary>
+ /// Creates a new span over the target unmanaged buffer. Clearly this
+ /// is quite dangerous, because we are creating arbitrarily typed T's
+ /// out of a void*-typed block of memory. And the length is not checked.
+ /// But if this creation is correct, then all subsequent uses are correct.
+ /// </summary>
+ /// <param name="pointer">An unmanaged pointer to memory.</param>
+ /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when <typeparamref name="T"/> is reference type or contains pointers and hence cannot be stored in unmanaged memory.
+ /// </exception>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="length"/> is negative.
+ /// </exception>
+ [CLSCompliant(false)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe Span(void* pointer, int length)
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
+ if (length < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref *(byte*)pointer));
+ _length = length;
+ }
+
+ // Constructor for internal use only.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Span(ref T ptr, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ _pointer = new ByReference<T>(ref ptr);
+ _length = length;
+ }
+
+ /// Returns a reference to specified element of the Span.
+ /// </summary>
+ /// <param name="index"></param>
+ /// <returns></returns>
+ /// <exception cref="System.IndexOutOfRangeException">
+ /// Thrown when index less than 0 or index greater than or equal to Length
+ /// </exception>
+ public ref T this[int index]
+ {
+#if PROJECTN
+ [BoundsChecking]
+ get
+ {
+ return ref Unsafe.Add(ref _pointer.Value, index);
+ }
+#else
+ [Intrinsic]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [NonVersionable]
+ get
+ {
+ if ((uint)index >= (uint)_length)
+ ThrowHelper.ThrowIndexOutOfRangeException();
+ return ref Unsafe.Add(ref _pointer.Value, index);
+ }
+#endif
+ }
+
+ /// <summary>
+ /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference.
+ /// It can be used for pinning and is required to support the use of span within a fixed statement.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public unsafe ref T GetPinnableReference() => ref (_length != 0) ? ref _pointer.Value : ref Unsafe.AsRef<T>(null);
+
+ /// <summary>
+ /// Clears the contents of this span.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Clear()
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
+ {
+ SpanHelpers.ClearWithReferences(ref Unsafe.As<T, IntPtr>(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf<T>() / sizeof(nuint)));
+ }
+ else
+ {
+ SpanHelpers.ClearWithoutReferences(ref Unsafe.As<T, byte>(ref _pointer.Value), (nuint)_length * (nuint)Unsafe.SizeOf<T>());
+ }
+ }
+
+ /// <summary>
+ /// Fills the contents of this span with the given value.
+ /// </summary>
+ public void Fill(T value)
+ {
+ if (Unsafe.SizeOf<T>() == 1)
+ {
+ uint length = (uint)_length;
+ if (length == 0)
+ return;
+
+ T tmp = value; // Avoid taking address of the "value" argument. It would regress performance of the loop below.
+ Unsafe.InitBlockUnaligned(ref Unsafe.As<T, byte>(ref _pointer.Value), Unsafe.As<T, byte>(ref tmp), length);
+ }
+ else
+ {
+ // Do all math as nuint to avoid unnecessary 64->32->64 bit integer truncations
+ nuint length = (uint)_length;
+ if (length == 0)
+ return;
+
+ ref T r = ref _pointer.Value;
+
+ // TODO: Create block fill for value types of power of two sizes e.g. 2,4,8,16
+
+ nuint elementSize = (uint)Unsafe.SizeOf<T>();
+ nuint i = 0;
+ for (; i < (length & ~(nuint)7); i += 8)
+ {
+ Unsafe.AddByteOffset<T>(ref r, (i + 0) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 1) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 2) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 3) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 4) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 5) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 6) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 7) * elementSize) = value;
+ }
+ if (i < (length & ~(nuint)3))
+ {
+ Unsafe.AddByteOffset<T>(ref r, (i + 0) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 1) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 2) * elementSize) = value;
+ Unsafe.AddByteOffset<T>(ref r, (i + 3) * elementSize) = value;
+ i += 4;
+ }
+ for (; i < length; i++)
+ {
+ Unsafe.AddByteOffset<T>(ref r, i * elementSize) = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies the contents of this span into destination span. If the source
+ /// and destinations overlap, this method behaves as if the original values in
+ /// a temporary location before the destination is overwritten.
+ /// </summary>
+ /// <param name="destination">The span to copy items into.</param>
+ /// <exception cref="System.ArgumentException">
+ /// Thrown when the destination Span is shorter than the source Span.
+ /// </exception>
+ public void CopyTo(Span<T> destination)
+ {
+ // Using "if (!TryCopyTo(...))" results in two branches: one for the length
+ // check, and one for the result of TryCopyTo. Since these checks are equivalent,
+ // we can optimize by performing the check once ourselves then calling Memmove directly.
+
+ if ((uint)_length <= (uint)destination.Length)
+ {
+ Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length);
+ }
+ else
+ {
+ ThrowHelper.ThrowArgumentException_DestinationTooShort();
+ }
+ }
+
+ /// <summary>
+ /// Copies the contents of this span into destination span. If the source
+ /// and destinations overlap, this method behaves as if the original values in
+ /// a temporary location before the destination is overwritten.
+ /// </summary>
+ /// <param name="destination">The span to copy items into.</param>
+ /// <returns>If the destination span is shorter than the source span, this method
+ /// return false and no data is written to the destination.</returns>
+ public bool TryCopyTo(Span<T> destination)
+ {
+ bool retVal = false;
+ if ((uint)_length <= (uint)destination.Length)
+ {
+ Buffer.Memmove(ref destination._pointer.Value, ref _pointer.Value, (nuint)_length);
+ retVal = true;
+ }
+ return retVal;
+ }
+
+ /// <summary>
+ /// Returns true if left and right point at the same memory and have the same length. Note that
+ /// this does *not* check to see if the *contents* are equal.
+ /// </summary>
+ public static bool operator ==(Span<T> left, Span<T> right)
+ {
+ return left._length == right._length && Unsafe.AreSame<T>(ref left._pointer.Value, ref right._pointer.Value);
+ }
+
+ /// <summary>
+ /// Defines an implicit conversion of a <see cref="Span{T}"/> to a <see cref="ReadOnlySpan{T}"/>
+ /// </summary>
+ public static implicit operator ReadOnlySpan<T>(Span<T> span) => new ReadOnlySpan<T>(ref span._pointer.Value, span._length);
+
+ /// <summary>
+ /// For <see cref="Span{Char}"/>, returns a new instance of string that represents the characters pointed to by the span.
+ /// Otherwise, returns a <see cref="String"/> with the name of the type and the number of elements.
+ /// </summary>
+ public override string ToString()
+ {
+ if (typeof(T) == typeof(char))
+ {
+ unsafe
+ {
+ fixed (char* src = &Unsafe.As<T, char>(ref _pointer.Value))
+ return new string(src, 0, _length);
+ }
+ }
+ return string.Format("System.Span<{0}>[{1}]", typeof(T).Name, _length);
+ }
+
+ /// <summary>
+ /// Forms a slice out of the given span, beginning at 'start'.
+ /// </summary>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> index is not in range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span<T> Slice(int start)
+ {
+ if ((uint)start > (uint)_length)
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), _length - start);
+ }
+
+ /// <summary>
+ /// Forms a slice out of the given span, beginning at 'start', of given length
+ /// </summary>
+ /// <param name="start">The index at which to begin this slice.</param>
+ /// <param name="length">The desired length for the slice (exclusive).</param>
+ /// <exception cref="System.ArgumentOutOfRangeException">
+ /// Thrown when the specified <paramref name="start"/> or end index is not in range (&lt;0 or &gt;=Length).
+ /// </exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span<T> Slice(int start, int length)
+ {
+ if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
+ ThrowHelper.ThrowArgumentOutOfRangeException();
+
+ return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
+ }
+
+ /// <summary>
+ /// Copies the contents of this span into a new array. This heap
+ /// allocates, so should generally be avoided, however it is sometimes
+ /// necessary to bridge the gap with APIs written in terms of arrays.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public T[] ToArray()
+ {
+ if (_length == 0)
+ return Array.Empty<T>();
+
+ var destination = new T[_length];
+ Buffer.Memmove(ref Unsafe.As<byte, T>(ref destination.GetRawSzArrayData()), ref _pointer.Value, (nuint)_length);
+ return destination;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Span.cs b/src/System.Private.CoreLib/shared/System/Span.cs
new file mode 100644
index 0000000000..81288a7627
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Span.cs
@@ -0,0 +1,142 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+#if !FEATURE_PORTABLE_SPAN
+using System.Runtime.Versioning;
+#endif // !FEATURE_PORTABLE_SPAN
+
+#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)'
+
+namespace System
+{
+ /// <summary>
+ /// Span represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed
+ /// or native memory, or to memory allocated on the stack. It is type- and memory-safe.
+ /// </summary>
+ [DebuggerTypeProxy(typeof(SpanDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ public readonly ref partial struct Span<T>
+ {
+ /// <summary>
+ /// The number of items in the span.
+ /// </summary>
+ public int Length
+ {
+#if !FEATURE_PORTABLE_SPAN
+ [NonVersionable]
+#endif // !FEATURE_PORTABLE_SPAN
+ get
+ {
+ return _length;
+ }
+ }
+
+ /// <summary>
+ /// Returns true if Length is 0.
+ /// </summary>
+ public bool IsEmpty
+ {
+#if !FEATURE_PORTABLE_SPAN
+ [NonVersionable]
+#endif // !FEATURE_PORTABLE_SPAN
+ get
+ {
+ return _length == 0;
+ }
+ }
+
+ /// <summary>
+ /// Returns false if left and right point at the same memory and have the same length. Note that
+ /// this does *not* check to see if the *contents* are equal.
+ /// </summary>
+ public static bool operator !=(Span<T> left, Span<T> right) => !(left == right);
+
+ /// <summary>
+ /// This method is not supported as spans cannot be boxed. To compare two spans, use operator==.
+ /// <exception cref="System.NotSupportedException">
+ /// Always thrown by this method.
+ /// </exception>
+ /// </summary>
+ [Obsolete("Equals() on Span will always throw an exception. Use == instead.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj)
+ {
+ throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan);
+ }
+
+ /// <summary>
+ /// This method is not supported as spans cannot be boxed.
+ /// <exception cref="System.NotSupportedException">
+ /// Always thrown by this method.
+ /// </exception>
+ /// </summary>
+ [Obsolete("GetHashCode() on Span will always throw an exception.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode()
+ {
+ throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan);
+ }
+
+ /// <summary>
+ /// Defines an implicit conversion of an array to a <see cref="Span{T}"/>
+ /// </summary>
+ public static implicit operator Span<T>(T[] array) => new Span<T>(array);
+
+ /// <summary>
+ /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Span{T}"/>
+ /// </summary>
+ public static implicit operator Span<T>(ArraySegment<T> segment)
+ => new Span<T>(segment.Array, segment.Offset, segment.Count);
+
+ /// <summary>
+ /// Returns an empty <see cref="Span{T}"/>
+ /// </summary>
+ public static Span<T> Empty => default(Span<T>);
+
+ /// <summary>Gets an enumerator for this span.</summary>
+ public Enumerator GetEnumerator() => new Enumerator(this);
+
+ /// <summary>Enumerates the elements of a <see cref="Span{T}"/>.</summary>
+ public ref struct Enumerator
+ {
+ /// <summary>The span being enumerated.</summary>
+ private readonly Span<T> _span;
+ /// <summary>The next index to yield.</summary>
+ private int _index;
+
+ /// <summary>Initialize the enumerator.</summary>
+ /// <param name="span">The span to enumerate.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Enumerator(Span<T> span)
+ {
+ _span = span;
+ _index = -1;
+ }
+
+ /// <summary>Advances the enumerator to the next element of the span.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ int index = _index + 1;
+ if (index < _span.Length)
+ {
+ _index = index;
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>Gets the element at the current position of the enumerator.</summary>
+ public ref T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ref _span[_index];
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanDebugView.cs b/src/System.Private.CoreLib/shared/System/SpanDebugView.cs
new file mode 100644
index 0000000000..caa12ef9ed
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanDebugView.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ internal sealed class SpanDebugView<T>
+ {
+ private readonly T[] _array;
+
+ public SpanDebugView(Span<T> span)
+ {
+ _array = span.ToArray();
+ }
+
+ public SpanDebugView(ReadOnlySpan<T> span)
+ {
+ _array = span.ToArray();
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items => _array;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.BinarySearch.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.BinarySearch.cs
new file mode 100644
index 0000000000..656b864e22
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.BinarySearch.cs
@@ -0,0 +1,83 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int BinarySearch<T, TComparable>(
+ this ReadOnlySpan<T> span, TComparable comparable)
+ where TComparable : IComparable<T>
+ {
+ if (comparable == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparable);
+
+ return BinarySearch(ref MemoryMarshal.GetReference(span), span.Length, comparable);
+ }
+
+ public static int BinarySearch<T, TComparable>(
+ ref T spanStart, int length, TComparable comparable)
+ where TComparable : IComparable<T>
+ {
+ int lo = 0;
+ int hi = length - 1;
+ // If length == 0, hi == -1, and loop will not be entered
+ while (lo <= hi)
+ {
+ // PERF: `lo` or `hi` will never be negative inside the loop,
+ // so computing median using uints is safe since we know
+ // `length <= int.MaxValue`, and indices are >= 0
+ // and thus cannot overflow an uint.
+ // Saves one subtraction per loop compared to
+ // `int i = lo + ((hi - lo) >> 1);`
+ int i = (int)(((uint)hi + (uint)lo) >> 1);
+
+ int c = comparable.CompareTo(Unsafe.Add(ref spanStart, i));
+ if (c == 0)
+ {
+ return i;
+ }
+ else if (c > 0)
+ {
+ lo = i + 1;
+ }
+ else
+ {
+ hi = i - 1;
+ }
+ }
+ // If none found, then a negative number that is the bitwise complement
+ // of the index of the next element that is larger than or, if there is
+ // no larger element, the bitwise complement of `length`, which
+ // is `lo` at this point.
+ return ~lo;
+ }
+
+ // Helper to allow sharing all code via IComparable<T> inlineable
+ internal struct ComparerComparable<T, TComparer> : IComparable<T>
+ where TComparer : IComparer<T>
+ {
+ readonly T _value;
+ readonly TComparer _comparer;
+
+ public ComparerComparable(T value, TComparer comparer)
+ {
+ _value = value;
+ _comparer = comparer;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int CompareTo(T other) => _comparer.Compare(_value, other);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs
new file mode 100644
index 0000000000..2b91b640e2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.Byte.cs
@@ -0,0 +1,1092 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+#if !netstandard11
+using System.Numerics;
+#endif
+
+#if netstandard
+using nuint = System.NUInt;
+#else
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif // BIT64
+#endif // netstandard
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ public static int IndexOf(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength)
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ byte valueHead = value;
+ ref byte valueTail = ref Unsafe.Add(ref value, 1);
+ int valueTailLength = valueLength - 1;
+
+ int index = 0;
+ for (; ; )
+ {
+ Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
+ int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
+ if (remainingSearchSpaceLength <= 0)
+ break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
+
+ // Do a quick search for the first element of "value".
+ int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, index), valueHead, remainingSearchSpaceLength);
+ if (relativeIndex == -1)
+ break;
+ index += relativeIndex;
+
+ // Found the first element of "value". See if the tail matches.
+ if (SequenceEqual(ref Unsafe.Add(ref searchSpace, index + 1), ref valueTail, valueTailLength))
+ return index; // The tail matched. Return a successful find.
+
+ index++;
+ }
+ return -1;
+ }
+
+ public static int IndexOfAny(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength)
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ int index = -1;
+ for (int i = 0; i < valueLength; i++)
+ {
+ var tempIndex = IndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
+ if ((uint)tempIndex < (uint)index)
+ {
+ index = tempIndex;
+ // Reduce space for search, cause we don't care if we find the search value after the index of a previously found value
+ searchSpaceLength = tempIndex;
+
+ if (index == 0) break;
+ }
+ }
+ return index;
+ }
+
+ public static int LastIndexOfAny(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength)
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ int index = -1;
+ for (int i = 0; i < valueLength; i++)
+ {
+ var tempIndex = LastIndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
+ if (tempIndex > index) index = tempIndex;
+ }
+ return index;
+ }
+
+ public static unsafe int IndexOf(ref byte searchSpace, byte value, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)((Vector<byte>.Count - unaligned) & (Vector<byte>.Count - 1));
+ }
+ SequentialScan:
+#endif
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index))
+ goto Found;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 1))
+ goto Found1;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 2))
+ goto Found2;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 3))
+ goto Found3;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 4))
+ goto Found4;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 5))
+ goto Found5;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 6))
+ goto Found6;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 7))
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index))
+ goto Found;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 1))
+ goto Found1;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 2))
+ goto Found2;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 3))
+ goto Found3;
+
+ index += 4;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index))
+ goto Found;
+
+ index += 1;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
+ {
+ nLength = (IntPtr)((length - (int)(byte*)index) & ~(Vector<byte>.Count - 1));
+
+ // Get comparison Vector
+ Vector<byte> vComparison = GetVector(value);
+
+ while ((byte*)nLength > (byte*)index)
+ {
+ var vMatches = Vector.Equals(vComparison, Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index)));
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index += Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)index + LocateFirstFoundByte(vMatches);
+ }
+
+ if ((int)(byte*)index < length)
+ {
+ nLength = (IntPtr)(length - (int)(byte*)index);
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static int LastIndexOf(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength)
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ byte valueHead = value;
+ ref byte valueTail = ref Unsafe.Add(ref value, 1);
+ int valueTailLength = valueLength - 1;
+
+ int index = 0;
+ for (; ; )
+ {
+ Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
+ int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
+ if (remainingSearchSpaceLength <= 0)
+ break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
+
+ // Do a quick search for the first element of "value".
+ int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength);
+ if (relativeIndex == -1)
+ break;
+
+ // Found the first element of "value". See if the tail matches.
+ if (SequenceEqual(ref Unsafe.Add(ref searchSpace, relativeIndex + 1), ref valueTail, valueTailLength))
+ return relativeIndex; // The tail matched. Return a successful find.
+
+ index += remainingSearchSpaceLength - relativeIndex;
+ }
+ return -1;
+ }
+
+ public static unsafe int LastIndexOf(ref byte searchSpace, byte value, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)length; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(((length & (Vector<byte>.Count - 1)) + unaligned) & (Vector<byte>.Count - 1));
+ }
+ SequentialScan:
+#endif
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+ index -= 8;
+
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 7))
+ goto Found7;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 6))
+ goto Found6;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 5))
+ goto Found5;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 4))
+ goto Found4;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 3))
+ goto Found3;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 2))
+ goto Found2;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 1))
+ goto Found1;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index))
+ goto Found;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+ index -= 4;
+
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 3))
+ goto Found3;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 2))
+ goto Found2;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index + 1))
+ goto Found1;
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index))
+ goto Found;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+ index -= 1;
+
+ if (uValue == Unsafe.AddByteOffset(ref searchSpace, index))
+ goto Found;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((byte*)index > (byte*)0))
+ {
+ nLength = (IntPtr)((int)(byte*)index & ~(Vector<byte>.Count - 1));
+
+ // Get comparison Vector
+ Vector<byte> vComparison = GetVector(value);
+
+ while ((byte*)nLength > (byte*)(Vector<byte>.Count - 1))
+ {
+ var vMatches = Vector.Equals(vComparison, Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index - Vector<byte>.Count)));
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index -= Vector<byte>.Count;
+ nLength -= Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(index) - Vector<byte>.Count + LocateLastFoundByte(vMatches);
+ }
+ if ((byte*)index > (byte*)0)
+ {
+ nLength = index;
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe int IndexOfAny(ref byte searchSpace, byte value0, byte value1, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)((Vector<byte>.Count - unaligned) & (Vector<byte>.Count - 1));
+ }
+ SequentialScan:
+#endif
+ uint lookUp;
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 4);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found4;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 5);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found5;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 6);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found6;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 7);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found3;
+
+ index += 4;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+
+ index += 1;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
+ {
+ nLength = (IntPtr)((length - (int)(byte*)index) & ~(Vector<byte>.Count - 1));
+
+ // Get comparison Vector
+ Vector<byte> values0 = GetVector(value0);
+ Vector<byte> values1 = GetVector(value1);
+
+ while ((byte*)nLength > (byte*)index)
+ {
+ Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index));
+ var vMatches = Vector.BitwiseOr(
+ Vector.Equals(vData, values0),
+ Vector.Equals(vData, values1));
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index += Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)index + LocateFirstFoundByte(vMatches);
+ }
+
+ if ((int)(byte*)index < length)
+ {
+ nLength = (IntPtr)(length - (int)(byte*)index);
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue2 = value2; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)((Vector<byte>.Count - unaligned) & (Vector<byte>.Count - 1));
+ }
+ SequentialScan:
+#endif
+ uint lookUp;
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 4);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found4;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 5);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found5;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 6);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found6;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 7);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found3;
+
+ index += 4;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+
+ index += 1;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
+ {
+ nLength = (IntPtr)((length - (int)(byte*)index) & ~(Vector<byte>.Count - 1));
+
+ // Get comparison Vector
+ Vector<byte> values0 = GetVector(value0);
+ Vector<byte> values1 = GetVector(value1);
+ Vector<byte> values2 = GetVector(value2);
+
+ while ((byte*)nLength > (byte*)index)
+ {
+ Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index));
+
+ var vMatches = Vector.BitwiseOr(
+ Vector.BitwiseOr(
+ Vector.Equals(vData, values0),
+ Vector.Equals(vData, values1)),
+ Vector.Equals(vData, values2));
+
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index += Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(byte*)index + LocateFirstFoundByte(vMatches);
+ }
+
+ if ((int)(byte*)index < length)
+ {
+ nLength = (IntPtr)(length - (int)(byte*)index);
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)length; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(((length & (Vector<byte>.Count - 1)) + unaligned) & (Vector<byte>.Count - 1));
+ }
+ SequentialScan:
+#endif
+ uint lookUp;
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+ index -= 8;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 7);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found7;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 6);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found6;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 5);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found5;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 4);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found4;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+ index -= 4;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+ index -= 1;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp)
+ goto Found;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((byte*)index > (byte*)0))
+ {
+ nLength = (IntPtr)((int)(byte*)index & ~(Vector<byte>.Count - 1));
+
+ // Get comparison Vector
+ Vector<byte> values0 = GetVector(value0);
+ Vector<byte> values1 = GetVector(value1);
+
+ while ((byte*)nLength > (byte*)(Vector<byte>.Count - 1))
+ {
+ Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index - Vector<byte>.Count));
+ var vMatches = Vector.BitwiseOr(
+ Vector.Equals(vData, values0),
+ Vector.Equals(vData, values1));
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index -= Vector<byte>.Count;
+ nLength -= Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(index) - Vector<byte>.Count + LocateLastFoundByte(vMatches);
+ }
+
+ if ((byte*)index > (byte*)0)
+ {
+ nLength = index;
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static unsafe int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ uint uValue2 = value2; // Use uint for comparisons to avoid unnecessary 8->32 extensions
+ IntPtr index = (IntPtr)length; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr nLength = (IntPtr)length;
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<byte>.Count * 2)
+ {
+ int unaligned = (int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1);
+ nLength = (IntPtr)(((length & (Vector<byte>.Count - 1)) + unaligned) & (Vector<byte>.Count - 1));
+ }
+ SequentialScan:
+#endif
+ uint lookUp;
+ while ((byte*)nLength >= (byte*)8)
+ {
+ nLength -= 8;
+ index -= 8;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 7);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found7;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 6);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found6;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 5);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found5;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 4);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found4;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ }
+
+ if ((byte*)nLength >= (byte*)4)
+ {
+ nLength -= 4;
+ index -= 4;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 3);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found3;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 2);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found2;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index + 1);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found1;
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ }
+
+ while ((byte*)nLength > (byte*)0)
+ {
+ nLength -= 1;
+ index -= 1;
+
+ lookUp = Unsafe.AddByteOffset(ref searchSpace, index);
+ if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp)
+ goto Found;
+ }
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && ((byte*)index > (byte*)0))
+ {
+ nLength = (IntPtr)((int)(byte*)index & ~(Vector<byte>.Count - 1));
+
+ // Get comparison Vector
+ Vector<byte> values0 = GetVector(value0);
+ Vector<byte> values1 = GetVector(value1);
+ Vector<byte> values2 = GetVector(value2);
+
+ while ((byte*)nLength > (byte*)(Vector<byte>.Count - 1))
+ {
+ Vector<byte> vData = Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref searchSpace, index - Vector<byte>.Count));
+
+ var vMatches = Vector.BitwiseOr(
+ Vector.BitwiseOr(
+ Vector.Equals(vData, values0),
+ Vector.Equals(vData, values1)),
+ Vector.Equals(vData, values2));
+
+ if (Vector<byte>.Zero.Equals(vMatches))
+ {
+ index -= Vector<byte>.Count;
+ nLength -= Vector<byte>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(index) - Vector<byte>.Count + LocateLastFoundByte(vMatches);
+ }
+
+ if ((byte*)index > (byte*)0)
+ {
+ nLength = index;
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ // Optimized byte-based SequenceEquals. The "length" parameter for this one is declared a nuint rather than int as we also use it for types other than byte
+ // where the length can exceed 2Gb once scaled by sizeof(T).
+ public static unsafe bool SequenceEqual(ref byte first, ref byte second, nuint length)
+ {
+ if (Unsafe.AreSame(ref first, ref second))
+ goto Equal;
+
+ IntPtr i = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr n = (IntPtr)(void*)length;
+
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && (byte*)n >= (byte*)Vector<byte>.Count)
+ {
+ n -= Vector<byte>.Count;
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref first, i)) !=
+ Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref second, i)))
+ {
+ goto NotEqual;
+ }
+ i += Vector<byte>.Count;
+ }
+ return Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref first, n)) ==
+ Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref second, n));
+ }
+#endif
+
+ if ((byte*)n >= (byte*)sizeof(UIntPtr))
+ {
+ n -= sizeof(UIntPtr);
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref first, i)) !=
+ Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref second, i)))
+ {
+ goto NotEqual;
+ }
+ i += sizeof(UIntPtr);
+ }
+ return Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref first, n)) ==
+ Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref second, n));
+ }
+
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.AddByteOffset(ref first, i) != Unsafe.AddByteOffset(ref second, i))
+ goto NotEqual;
+ i += 1;
+ }
+
+ Equal:
+ return true;
+
+ NotEqual: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return false;
+ }
+
+#if !netstandard11
+ // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateFirstFoundByte(Vector<byte> match)
+ {
+ var vector64 = Vector.AsVectorUInt64(match);
+ ulong candidate = 0;
+ int i = 0;
+ // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
+ for (; i < Vector<ulong>.Count; i++)
+ {
+ candidate = vector64[i];
+ if (candidate != 0)
+ {
+ break;
+ }
+ }
+
+ // Single LEA instruction with jitted const (using function result)
+ return i * 8 + LocateFirstFoundByte(candidate);
+ }
+#endif
+
+ public static unsafe int SequenceCompareTo(ref byte first, int firstLength, ref byte second, int secondLength)
+ {
+ Debug.Assert(firstLength >= 0);
+ Debug.Assert(secondLength >= 0);
+
+ if (Unsafe.AreSame(ref first, ref second))
+ goto Equal;
+
+ IntPtr minLength = (IntPtr)((firstLength < secondLength) ? firstLength : secondLength);
+
+ IntPtr i = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ IntPtr n = (IntPtr)(void*)minLength;
+
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && (byte*)n > (byte*)Vector<byte>.Count)
+ {
+ n -= Vector<byte>.Count;
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref first, i)) !=
+ Unsafe.ReadUnaligned<Vector<byte>>(ref Unsafe.AddByteOffset(ref second, i)))
+ {
+ goto NotEqual;
+ }
+ i += Vector<byte>.Count;
+ }
+ goto NotEqual;
+ }
+#endif
+
+ if ((byte*)n > (byte*)sizeof(UIntPtr))
+ {
+ n -= sizeof(UIntPtr);
+ while ((byte*)n > (byte*)i)
+ {
+ if (Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref first, i)) !=
+ Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.AddByteOffset(ref second, i)))
+ {
+ goto NotEqual;
+ }
+ i += sizeof(UIntPtr);
+ }
+ }
+
+ NotEqual: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ while ((byte*)minLength > (byte*)i)
+ {
+ int result = Unsafe.AddByteOffset(ref first, i).CompareTo(Unsafe.AddByteOffset(ref second, i));
+ if (result != 0) return result;
+ i += 1;
+ }
+
+ Equal:
+ return firstLength - secondLength;
+ }
+
+#if !netstandard11
+ // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateLastFoundByte(Vector<byte> match)
+ {
+ var vector64 = Vector.AsVectorUInt64(match);
+ ulong candidate = 0;
+ int i = Vector<ulong>.Count - 1;
+ // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
+ for (; i >= 0; i--)
+ {
+ candidate = vector64[i];
+ if (candidate != 0)
+ {
+ break;
+ }
+ }
+
+ // Single LEA instruction with jitted const (using function result)
+ return i * 8 + LocateLastFoundByte(candidate);
+ }
+#endif
+
+#if !netstandard11
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateFirstFoundByte(ulong match)
+ {
+ // Flag least significant power of two bit
+ var powerOfTwoFlag = match ^ (match - 1);
+ // Shift all powers of two into the high byte and extract
+ return (int)((powerOfTwoFlag * XorPowerOfTwoToHighByte) >> 57);
+ }
+#endif
+
+#if !netstandard11
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateLastFoundByte(ulong match)
+ {
+ // Find the most significant byte that has its highest bit set
+ int index = 7;
+ while ((long)match > 0)
+ {
+ match = match << 8;
+ index--;
+ }
+ return index;
+ }
+#endif
+
+#if !netstandard11
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector<byte> GetVector(byte vectorByte)
+ {
+#if !netcoreapp
+ // Vector<byte> .ctor doesn't become an intrinsic due to detection issue
+ // However this does cause it to become an intrinsic (with additional multiply and reg->reg copy)
+ // https://github.com/dotnet/coreclr/issues/7459#issuecomment-253965670
+ return Vector.AsVectorByte(new Vector<uint>(vectorByte * 0x01010101u));
+#else
+ return new Vector<byte>(vectorByte);
+#endif
+ }
+#endif
+
+#if !netstandard11
+ private const ulong XorPowerOfTwoToHighByte = (0x07ul |
+ 0x06ul << 8 |
+ 0x05ul << 16 |
+ 0x04ul << 24 |
+ 0x03ul << 32 |
+ 0x02ul << 40 |
+ 0x01ul << 48) + 1;
+#endif
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs
new file mode 100644
index 0000000000..51ace58a39
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs
@@ -0,0 +1,341 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+#if !netstandard11
+using System.Numerics;
+#endif
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ public static unsafe int SequenceCompareTo(ref char first, int firstLength, ref char second, int secondLength)
+ {
+ Debug.Assert(firstLength >= 0);
+ Debug.Assert(secondLength >= 0);
+
+ int lengthDelta = firstLength - secondLength;
+
+ if (Unsafe.AreSame(ref first, ref second))
+ goto Equal;
+
+ IntPtr minLength = (IntPtr)((firstLength < secondLength) ? firstLength : secondLength);
+ IntPtr i = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+
+ if ((byte*)minLength >= (byte*)(sizeof(UIntPtr) / sizeof(char)))
+ {
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && (byte*)minLength >= (byte*)Vector<ushort>.Count)
+ {
+ IntPtr nLength = minLength - Vector<ushort>.Count;
+ do
+ {
+ if (Unsafe.ReadUnaligned<Vector<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
+ Unsafe.ReadUnaligned<Vector<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
+ {
+ break;
+ }
+ i += Vector<ushort>.Count;
+ }
+ while ((byte*)nLength >= (byte*)i);
+ }
+#endif
+
+ while ((byte*)minLength >= (byte*)(i + sizeof(UIntPtr) / sizeof(char)))
+ {
+ if (Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
+ Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
+ {
+ break;
+ }
+ i += sizeof(UIntPtr) / sizeof(char);
+ }
+ }
+
+ if (sizeof(UIntPtr) > sizeof(int) && (byte*)minLength >= (byte*)(i + sizeof(int) / sizeof(char)))
+ {
+ if (Unsafe.ReadUnaligned<int>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) ==
+ Unsafe.ReadUnaligned<int>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
+ {
+ i += sizeof(int) / sizeof(char);
+ }
+ }
+
+ while ((byte*)i < (byte*)minLength)
+ {
+ int result = Unsafe.Add(ref first, i).CompareTo(Unsafe.Add(ref second, i));
+ if (result != 0)
+ return result;
+ i += 1;
+ }
+
+ Equal:
+ return lengthDelta;
+ }
+
+ public static unsafe int IndexOf(ref char searchSpace, char value, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ fixed (char* pChars = &searchSpace)
+ {
+ char* pCh = pChars;
+ char* pEndCh = pCh + length;
+
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
+ {
+ // Figure out how many characters to read sequentially until we are vector aligned
+ // This is equivalent to:
+ // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
+ // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
+ const int elementsPerByte = sizeof(ushort) / sizeof(byte);
+ int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
+ length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
+ }
+ SequentialScan:
+#endif
+ while (length >= 4)
+ {
+ length -= 4;
+
+ if (*pCh == value)
+ goto Found;
+ if (*(pCh + 1) == value)
+ goto Found1;
+ if (*(pCh + 2) == value)
+ goto Found2;
+ if (*(pCh + 3) == value)
+ goto Found3;
+
+ pCh += 4;
+ }
+
+ while (length > 0)
+ {
+ length -= 1;
+
+ if (*pCh == value)
+ goto Found;
+
+ pCh += 1;
+ }
+#if !netstandard11
+ // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
+ // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
+ if (Vector.IsHardwareAccelerated && pCh < pEndCh)
+ {
+ // Get the highest multiple of Vector<ushort>.Count that is within the search space.
+ // That will be how many times we iterate in the loop below.
+ // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
+ length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
+
+ // Get comparison Vector
+ Vector<ushort> vComparison = new Vector<ushort>(value);
+
+ while (length > 0)
+ {
+ // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
+ Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
+ Vector<ushort> vMatches = Vector.Equals(vComparison, Unsafe.Read<Vector<ushort>>(pCh));
+ if (Vector<ushort>.Zero.Equals(vMatches))
+ {
+ pCh += Vector<ushort>.Count;
+ length -= Vector<ushort>.Count;
+ continue;
+ }
+ // Find offset of first match
+ return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
+ }
+
+ if (pCh < pEndCh)
+ {
+ length = (int)(pEndCh - pCh);
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found3:
+ pCh++;
+ Found2:
+ pCh++;
+ Found1:
+ pCh++;
+ Found:
+ return (int)(pCh - pChars);
+ }
+ }
+
+ public static unsafe int LastIndexOf(ref char searchSpace, char value, int length)
+ {
+ Debug.Assert(length >= 0);
+
+ fixed (char* pChars = &searchSpace)
+ {
+ char* pCh = pChars + length;
+ char* pEndCh = pChars;
+
+#if !netstandard11
+ if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
+ {
+ // Figure out how many characters to read sequentially from the end until we are vector aligned
+ // This is equivalent to: length = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
+ const int elementsPerByte = sizeof(ushort) / sizeof(byte);
+ length = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
+ }
+ SequentialScan:
+#endif
+ while (length >= 4)
+ {
+ length -= 4;
+ pCh -= 4;
+
+ if (*(pCh + 3) == value)
+ goto Found3;
+ if (*(pCh + 2) == value)
+ goto Found2;
+ if (*(pCh + 1) == value)
+ goto Found1;
+ if (*pCh == value)
+ goto Found;
+ }
+
+ while (length > 0)
+ {
+ length -= 1;
+ pCh -= 1;
+
+ if (*pCh == value)
+ goto Found;
+ }
+#if !netstandard11
+ // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
+ // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
+ if (Vector.IsHardwareAccelerated && pCh > pEndCh)
+ {
+ // Get the highest multiple of Vector<ushort>.Count that is within the search space.
+ // That will be how many times we iterate in the loop below.
+ // This is equivalent to: length = Vector<ushort>.Count * ((int)(pCh - pEndCh) / Vector<ushort>.Count)
+ length = (int)((pCh - pEndCh) & ~(Vector<ushort>.Count - 1));
+
+ // Get comparison Vector
+ Vector<ushort> vComparison = new Vector<ushort>(value);
+
+ while (length > 0)
+ {
+ char* pStart = pCh - Vector<ushort>.Count;
+ // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh (and hence pSart) is always vector aligned
+ Debug.Assert(((int)pStart & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
+ Vector<ushort> vMatches = Vector.Equals(vComparison, Unsafe.Read<Vector<ushort>>(pStart));
+ if (Vector<ushort>.Zero.Equals(vMatches))
+ {
+ pCh -= Vector<ushort>.Count;
+ length -= Vector<ushort>.Count;
+ continue;
+ }
+ // Find offset of last match
+ return (int)(pStart - pEndCh) + LocateLastFoundChar(vMatches);
+ }
+
+ if (pCh > pEndCh)
+ {
+ length = (int)(pCh - pEndCh);
+ goto SequentialScan;
+ }
+ }
+#endif
+ return -1;
+ Found:
+ return (int)(pCh - pEndCh);
+ Found1:
+ return (int)(pCh - pEndCh) + 1;
+ Found2:
+ return (int)(pCh - pEndCh) + 2;
+ Found3:
+ return (int)(pCh - pEndCh) + 3;
+ }
+ }
+
+#if !netstandard11
+ // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateFirstFoundChar(Vector<ushort> match)
+ {
+ var vector64 = Vector.AsVectorUInt64(match);
+ ulong candidate = 0;
+ int i = 0;
+ // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
+ for (; i < Vector<ulong>.Count; i++)
+ {
+ candidate = vector64[i];
+ if (candidate != 0)
+ {
+ break;
+ }
+ }
+
+ // Single LEA instruction with jitted const (using function result)
+ return i * 4 + LocateFirstFoundChar(candidate);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateFirstFoundChar(ulong match)
+ {
+ unchecked
+ {
+ // Flag least significant power of two bit
+ var powerOfTwoFlag = match ^ (match - 1);
+ // Shift all powers of two into the high byte and extract
+ return (int)((powerOfTwoFlag * XorPowerOfTwoToHighChar) >> 49);
+ }
+ }
+
+ private const ulong XorPowerOfTwoToHighChar = (0x03ul |
+ 0x02ul << 16 |
+ 0x01ul << 32) + 1;
+
+ // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateLastFoundChar(Vector<ushort> match)
+ {
+ var vector64 = Vector.AsVectorUInt64(match);
+ ulong candidate = 0;
+ int i = Vector<ulong>.Count - 1;
+ // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
+ for (; i >= 0; i--)
+ {
+ candidate = vector64[i];
+ if (candidate != 0)
+ {
+ break;
+ }
+ }
+
+ // Single LEA instruction with jitted const (using function result)
+ return i * 4 + LocateLastFoundChar(candidate);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int LocateLastFoundChar(ulong match)
+ {
+ // Find the most significant char that has its highest bit set
+ int index = 3;
+ while ((long)match > 0)
+ {
+ match = match << 16;
+ index--;
+ }
+ return index;
+ }
+#endif
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.T.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.T.cs
new file mode 100644
index 0000000000..4567deddb9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.T.cs
@@ -0,0 +1,686 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+#if !netstandard11
+using System.Numerics;
+#endif
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ public static int IndexOf<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ T valueHead = value;
+ ref T valueTail = ref Unsafe.Add(ref value, 1);
+ int valueTailLength = valueLength - 1;
+
+ int index = 0;
+ for (; ; )
+ {
+ Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
+ int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
+ if (remainingSearchSpaceLength <= 0)
+ break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
+
+ // Do a quick search for the first element of "value".
+ int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, index), valueHead, remainingSearchSpaceLength);
+ if (relativeIndex == -1)
+ break;
+ index += relativeIndex;
+
+ // Found the first element of "value". See if the tail matches.
+ if (SequenceEqual(ref Unsafe.Add(ref searchSpace, index + 1), ref valueTail, valueTailLength))
+ return index; // The tail matched. Return a successful find.
+
+ index++;
+ }
+ return -1;
+ }
+
+ public static unsafe int IndexOf<T>(ref T searchSpace, T value, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ while (length >= 8)
+ {
+ length -= 8;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, index)))
+ goto Found;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 1)))
+ goto Found1;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 2)))
+ goto Found2;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 3)))
+ goto Found3;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 4)))
+ goto Found4;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 5)))
+ goto Found5;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 6)))
+ goto Found6;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 7)))
+ goto Found7;
+
+ index += 8;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, index)))
+ goto Found;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 1)))
+ goto Found1;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 2)))
+ goto Found2;
+ if (value.Equals(Unsafe.Add(ref searchSpace, index + 3)))
+ goto Found3;
+
+ index += 4;
+ }
+
+ while (length > 0)
+ {
+ if (value.Equals(Unsafe.Add(ref searchSpace, index)))
+ goto Found;
+
+ index += 1;
+ length--;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return (int)(byte*)index;
+ Found1:
+ return (int)(byte*)(index + 1);
+ Found2:
+ return (int)(byte*)(index + 2);
+ Found3:
+ return (int)(byte*)(index + 3);
+ Found4:
+ return (int)(byte*)(index + 4);
+ Found5:
+ return (int)(byte*)(index + 5);
+ Found6:
+ return (int)(byte*)(index + 6);
+ Found7:
+ return (int)(byte*)(index + 7);
+ }
+
+ public static int IndexOfAny<T>(ref T searchSpace, T value0, T value1, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ T lookUp;
+ int index = 0;
+ while ((length - index) >= 8)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 4);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, index + 5);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, index + 6);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, index + 7);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((length - index) >= 4)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found3;
+
+ index += 4;
+ }
+
+ while (index < length)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+
+ index++;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return index;
+ Found1:
+ return index + 1;
+ Found2:
+ return index + 2;
+ Found3:
+ return index + 3;
+ Found4:
+ return index + 4;
+ Found5:
+ return index + 5;
+ Found6:
+ return index + 6;
+ Found7:
+ return index + 7;
+ }
+
+ public static int IndexOfAny<T>(ref T searchSpace, T value0, T value1, T value2, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ T lookUp;
+ int index = 0;
+ while ((length - index) >= 8)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, index + 4);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, index + 5);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, index + 6);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, index + 7);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found7;
+
+ index += 8;
+ }
+
+ if ((length - index) >= 4)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ lookUp = Unsafe.Add(ref searchSpace, index + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, index + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, index + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found3;
+
+ index += 4;
+ }
+
+ while (index < length)
+ {
+ lookUp = Unsafe.Add(ref searchSpace, index);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+
+ index++;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return index;
+ Found1:
+ return index + 1;
+ Found2:
+ return index + 2;
+ Found3:
+ return index + 3;
+ Found4:
+ return index + 4;
+ Found5:
+ return index + 5;
+ Found6:
+ return index + 6;
+ Found7:
+ return index + 7;
+ }
+
+ public static int IndexOfAny<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ int index = -1;
+ for (int i = 0; i < valueLength; i++)
+ {
+ var tempIndex = IndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
+ if ((uint)tempIndex < (uint)index)
+ {
+ index = tempIndex;
+ // Reduce space for search, cause we don't care if we find the search value after the index of a previously found value
+ searchSpaceLength = tempIndex;
+
+ if (index == 0) break;
+ }
+ }
+ return index;
+ }
+
+ public static int LastIndexOf<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ T valueHead = value;
+ ref T valueTail = ref Unsafe.Add(ref value, 1);
+ int valueTailLength = valueLength - 1;
+
+ int index = 0;
+ for (; ; )
+ {
+ Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
+ int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
+ if (remainingSearchSpaceLength <= 0)
+ break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
+
+ // Do a quick search for the first element of "value".
+ int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength);
+ if (relativeIndex == -1)
+ break;
+
+ // Found the first element of "value". See if the tail matches.
+ if (SequenceEqual(ref Unsafe.Add(ref searchSpace, relativeIndex + 1), ref valueTail, valueTailLength))
+ return relativeIndex; // The tail matched. Return a successful find.
+
+ index += remainingSearchSpaceLength - relativeIndex;
+ }
+ return -1;
+ }
+
+ public static int LastIndexOf<T>(ref T searchSpace, T value, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ while (length >= 8)
+ {
+ length -= 8;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 7)))
+ goto Found7;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 6)))
+ goto Found6;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 5)))
+ goto Found5;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 4)))
+ goto Found4;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 3)))
+ goto Found3;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 2)))
+ goto Found2;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 1)))
+ goto Found1;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length)))
+ goto Found;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 3)))
+ goto Found3;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 2)))
+ goto Found2;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length + 1)))
+ goto Found1;
+ if (value.Equals(Unsafe.Add(ref searchSpace, length)))
+ goto Found;
+ }
+
+ while (length > 0)
+ {
+ length--;
+
+ if (value.Equals(Unsafe.Add(ref searchSpace, length)))
+ goto Found;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return length;
+ Found1:
+ return length + 1;
+ Found2:
+ return length + 2;
+ Found3:
+ return length + 3;
+ Found4:
+ return length + 4;
+ Found5:
+ return length + 5;
+ Found6:
+ return length + 6;
+ Found7:
+ return length + 7;
+ }
+
+ public static int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ T lookUp;
+ while (length >= 8)
+ {
+ length -= 8;
+
+ lookUp = Unsafe.Add(ref searchSpace, length + 7);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found7;
+ lookUp = Unsafe.Add(ref searchSpace, length + 6);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, length + 5);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, length + 4);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, length + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, length + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, length + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ lookUp = Unsafe.Add(ref searchSpace, length + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, length + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, length + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ }
+
+ while (length > 0)
+ {
+ length--;
+
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp))
+ goto Found;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return length;
+ Found1:
+ return length + 1;
+ Found2:
+ return length + 2;
+ Found3:
+ return length + 3;
+ Found4:
+ return length + 4;
+ Found5:
+ return length + 5;
+ Found6:
+ return length + 6;
+ Found7:
+ return length + 7;
+ }
+
+ public static int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, T value2, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ T lookUp;
+ while (length >= 8)
+ {
+ length -= 8;
+
+ lookUp = Unsafe.Add(ref searchSpace, length + 7);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found7;
+ lookUp = Unsafe.Add(ref searchSpace, length + 6);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found6;
+ lookUp = Unsafe.Add(ref searchSpace, length + 5);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found5;
+ lookUp = Unsafe.Add(ref searchSpace, length + 4);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found4;
+ lookUp = Unsafe.Add(ref searchSpace, length + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, length + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, length + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ lookUp = Unsafe.Add(ref searchSpace, length + 3);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found3;
+ lookUp = Unsafe.Add(ref searchSpace, length + 2);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found2;
+ lookUp = Unsafe.Add(ref searchSpace, length + 1);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found1;
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ }
+
+ while (length > 0)
+ {
+ length--;
+
+ lookUp = Unsafe.Add(ref searchSpace, length);
+ if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
+ goto Found;
+ }
+ return -1;
+
+ Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return length;
+ Found1:
+ return length + 1;
+ Found2:
+ return length + 2;
+ Found3:
+ return length + 3;
+ Found4:
+ return length + 4;
+ Found5:
+ return length + 5;
+ Found6:
+ return length + 6;
+ Found7:
+ return length + 7;
+ }
+
+ public static int LastIndexOfAny<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(searchSpaceLength >= 0);
+ Debug.Assert(valueLength >= 0);
+
+ if (valueLength == 0)
+ return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
+
+ int index = -1;
+ for (int i = 0; i < valueLength; i++)
+ {
+ var tempIndex = LastIndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
+ if (tempIndex > index) index = tempIndex;
+ }
+ return index;
+ }
+
+ public static bool SequenceEqual<T>(ref T first, ref T second, int length)
+ where T : IEquatable<T>
+ {
+ Debug.Assert(length >= 0);
+
+ if (Unsafe.AreSame(ref first, ref second))
+ goto Equal;
+
+ IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
+ while (length >= 8)
+ {
+ length -= 8;
+
+ if (!Unsafe.Add(ref first, index).Equals(Unsafe.Add(ref second, index)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 1).Equals(Unsafe.Add(ref second, index + 1)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 2).Equals(Unsafe.Add(ref second, index + 2)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 3).Equals(Unsafe.Add(ref second, index + 3)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 4).Equals(Unsafe.Add(ref second, index + 4)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 5).Equals(Unsafe.Add(ref second, index + 5)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 6).Equals(Unsafe.Add(ref second, index + 6)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 7).Equals(Unsafe.Add(ref second, index + 7)))
+ goto NotEqual;
+
+ index += 8;
+ }
+
+ if (length >= 4)
+ {
+ length -= 4;
+
+ if (!Unsafe.Add(ref first, index).Equals(Unsafe.Add(ref second, index)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 1).Equals(Unsafe.Add(ref second, index + 1)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 2).Equals(Unsafe.Add(ref second, index + 2)))
+ goto NotEqual;
+ if (!Unsafe.Add(ref first, index + 3).Equals(Unsafe.Add(ref second, index + 3)))
+ goto NotEqual;
+
+ index += 4;
+ }
+
+ while (length > 0)
+ {
+ if (!Unsafe.Add(ref first, index).Equals(Unsafe.Add(ref second, index)))
+ goto NotEqual;
+ index += 1;
+ length--;
+ }
+
+ Equal:
+ return true;
+
+ NotEqual: // Workaround for https://github.com/dotnet/coreclr/issues/13549
+ return false;
+ }
+
+ public static int SequenceCompareTo<T>(ref T first, int firstLength, ref T second, int secondLength)
+ where T : IComparable<T>
+ {
+ Debug.Assert(firstLength >= 0);
+ Debug.Assert(secondLength >= 0);
+
+ var minLength = firstLength;
+ if (minLength > secondLength) minLength = secondLength;
+ for (int i = 0; i < minLength; i++)
+ {
+ int result = Unsafe.Add(ref first, i).CompareTo(Unsafe.Add(ref second, i));
+ if (result != 0) return result;
+ }
+ return firstLength.CompareTo(secondLength);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SpanHelpers.cs b/src/System.Private.CoreLib/shared/System/SpanHelpers.cs
new file mode 100644
index 0000000000..83cdfce71a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SpanHelpers.cs
@@ -0,0 +1,416 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime;
+using System.Runtime.InteropServices;
+
+using Internal.Runtime.CompilerServices;
+
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
+namespace System
+{
+ internal static partial class SpanHelpers
+ {
+ public static unsafe void ClearWithoutReferences(ref byte b, nuint byteLength)
+ {
+ if (byteLength == 0)
+ return;
+
+#if CORECLR && (AMD64 || ARM64)
+ if (byteLength > 4096)
+ goto PInvoke;
+ Unsafe.InitBlockUnaligned(ref b, 0, (uint)byteLength);
+ return;
+#else
+ // TODO: Optimize other platforms to be on par with AMD64 CoreCLR
+ // Note: It's important that this switch handles lengths at least up to 22.
+ // See notes below near the main loop for why.
+
+ // The switch will be very fast since it can be implemented using a jump
+ // table in assembly. See http://stackoverflow.com/a/449297/4077294 for more info.
+
+ switch (byteLength)
+ {
+ case 1:
+ b = 0;
+ return;
+ case 2:
+ Unsafe.As<byte, short>(ref b) = 0;
+ return;
+ case 3:
+ Unsafe.As<byte, short>(ref b) = 0;
+ Unsafe.Add<byte>(ref b, 2) = 0;
+ return;
+ case 4:
+ Unsafe.As<byte, int>(ref b) = 0;
+ return;
+ case 5:
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.Add<byte>(ref b, 4) = 0;
+ return;
+ case 6:
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ return;
+ case 7:
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.Add<byte>(ref b, 6) = 0;
+ return;
+ case 8:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ return;
+ case 9:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.Add<byte>(ref b, 8) = 0;
+ return;
+ case 10:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ return;
+ case 11:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.Add<byte>(ref b, 10) = 0;
+ return;
+ case 12:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ return;
+ case 13:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.Add<byte>(ref b, 12) = 0;
+ return;
+ case 14:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+ return;
+ case 15:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+ Unsafe.Add<byte>(ref b, 14) = 0;
+ return;
+ case 16:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ return;
+ case 17:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.Add<byte>(ref b, 16) = 0;
+ return;
+ case 18:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ return;
+ case 19:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ Unsafe.Add<byte>(ref b, 18) = 0;
+ return;
+ case 20:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ return;
+ case 21:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ Unsafe.Add<byte>(ref b, 20) = 0;
+ return;
+ case 22:
+#if BIT64
+ Unsafe.As<byte, long>(ref b) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref b) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
+#endif
+ Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
+ Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 20)) = 0;
+ return;
+ }
+
+ // P/Invoke into the native version for large lengths
+ if (byteLength >= 512) goto PInvoke;
+
+ nuint i = 0; // byte offset at which we're copying
+
+ if ((Unsafe.As<byte, int>(ref b) & 3) != 0)
+ {
+ if ((Unsafe.As<byte, int>(ref b) & 1) != 0)
+ {
+ Unsafe.AddByteOffset<byte>(ref b, i) = 0;
+ i += 1;
+ if ((Unsafe.As<byte, int>(ref b) & 2) != 0)
+ goto IntAligned;
+ }
+ Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ i += 2;
+ }
+
+ IntAligned:
+
+ // On 64-bit IntPtr.Size == 8, so we want to advance to the next 8-aligned address. If
+ // (int)b % 8 is 0, 5, 6, or 7, we will already have advanced by 0, 3, 2, or 1
+ // bytes to the next aligned address (respectively), so do nothing. On the other hand,
+ // if it is 1, 2, 3, or 4 we will want to copy-and-advance another 4 bytes until
+ // we're aligned.
+ // The thing 1, 2, 3, and 4 have in common that the others don't is that if you
+ // subtract one from them, their 3rd lsb will not be set. Hence, the below check.
+
+ if (((Unsafe.As<byte, int>(ref b) - 1) & 4) == 0)
+ {
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ i += 4;
+ }
+
+ nuint end = byteLength - 16;
+ byteLength -= i; // lower 4 bits of byteLength represent how many bytes are left *after* the unrolled loop
+
+ // We know due to the above switch-case that this loop will always run 1 iteration; max
+ // bytes we clear before checking is 23 (7 to align the pointers, 16 for 1 iteration) so
+ // the switch handles lengths 0-22.
+ Debug.Assert(end >= 7 && i <= end);
+
+ // This is separated out into a different variable, so the i + 16 addition can be
+ // performed at the start of the pipeline and the loop condition does not have
+ // a dependency on the writes.
+ nuint counter;
+
+ do
+ {
+ counter = i + 16;
+
+ // This loop looks very costly since there appear to be a bunch of temporary values
+ // being created with the adds, but the jit (for x86 anyways) will convert each of
+ // these to use memory addressing operands.
+
+ // So the only cost is a bit of code size, which is made up for by the fact that
+ // we save on writes to b.
+
+#if BIT64
+ Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0;
+#else
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 12)) = 0;
+#endif
+
+ i = counter;
+
+ // See notes above for why this wasn't used instead
+ // i += 16;
+ }
+ while (counter <= end);
+
+ if ((byteLength & 8) != 0)
+ {
+#if BIT64
+ Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+#else
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0;
+#endif
+ i += 8;
+ }
+ if ((byteLength & 4) != 0)
+ {
+ Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ i += 4;
+ }
+ if ((byteLength & 2) != 0)
+ {
+ Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
+ i += 2;
+ }
+ if ((byteLength & 1) != 0)
+ {
+ Unsafe.AddByteOffset<byte>(ref b, i) = 0;
+ // We're not using i after this, so not needed
+ // i += 1;
+ }
+
+ return;
+#endif
+
+ PInvoke:
+ RuntimeImports.RhZeroMemory(ref b, byteLength);
+ }
+
+ public static unsafe void ClearWithReferences(ref IntPtr ip, nuint pointerSizeLength)
+ {
+ Debug.Assert((int)Unsafe.AsPointer(ref ip) % sizeof(IntPtr) == 0, "Should've been aligned on natural word boundary.");
+
+ // First write backward 8 natural words at a time.
+ // Writing backward allows us to get away with only simple modifications to the
+ // mov instruction's base and index registers between loop iterations.
+
+ for (; pointerSizeLength >= 8; pointerSizeLength -= 8)
+ {
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -1) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -2) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -3) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -4) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -5) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -6) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -7) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -8) = default(IntPtr);
+ }
+
+ Debug.Assert(pointerSizeLength <= 7);
+
+ // The logic below works by trying to minimize the number of branches taken for any
+ // given range of lengths. For example, the lengths [ 4 .. 7 ] are handled by a single
+ // branch, [ 2 .. 3 ] are handled by a single branch, and [ 1 ] is handled by a single
+ // branch.
+ //
+ // We can write both forward and backward as a perf improvement. For example,
+ // the lengths [ 4 .. 7 ] can be handled by zeroing out the first four natural
+ // words and the last 3 natural words. In the best case (length = 7), there are
+ // no overlapping writes. In the worst case (length = 4), there are three
+ // overlapping writes near the middle of the buffer. In perf testing, the
+ // penalty for performing duplicate writes is less expensive than the penalty
+ // for complex branching.
+
+ if (pointerSizeLength >= 4)
+ {
+ goto Write4To7;
+ }
+ else if (pointerSizeLength >= 2)
+ {
+ goto Write2To3;
+ }
+ else if (pointerSizeLength > 0)
+ {
+ goto Write1;
+ }
+ else
+ {
+ return; // nothing to write
+ }
+
+ Write4To7:
+ Debug.Assert(pointerSizeLength >= 4);
+
+ // Write first four and last three.
+ Unsafe.Add(ref ip, 2) = default(IntPtr);
+ Unsafe.Add(ref ip, 3) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -3) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -2) = default(IntPtr);
+
+ Write2To3:
+ Debug.Assert(pointerSizeLength >= 2);
+
+ // Write first two and last one.
+ Unsafe.Add(ref ip, 1) = default(IntPtr);
+ Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -1) = default(IntPtr);
+
+ Write1:
+ Debug.Assert(pointerSizeLength >= 1);
+
+ // Write only element.
+ ip = default(IntPtr);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/StackOverflowException.cs b/src/System.Private.CoreLib/shared/System/StackOverflowException.cs
new file mode 100644
index 0000000000..6f954cc75a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/StackOverflowException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: The exception class for stack overflow.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class StackOverflowException : SystemException
+ {
+ public StackOverflowException()
+ : base(SR.Arg_StackOverflowException)
+ {
+ HResult = HResults.COR_E_STACKOVERFLOW;
+ }
+
+ public StackOverflowException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_STACKOVERFLOW;
+ }
+
+ public StackOverflowException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_STACKOVERFLOW;
+ }
+
+ internal StackOverflowException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/String.Comparison.cs b/src/System.Private.CoreLib/shared/System/String.Comparison.cs
new file mode 100644
index 0000000000..03c7cf6f3c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/String.Comparison.cs
@@ -0,0 +1,1007 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Internal.Runtime.CompilerServices;
+
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
+namespace System
+{
+ public partial class String
+ {
+ private static unsafe int CompareOrdinalIgnoreCaseHelper(string strA, string strB)
+ {
+ Debug.Assert(strA != null);
+ Debug.Assert(strB != null);
+ int length = Math.Min(strA.Length, strB.Length);
+
+ fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
+ {
+ char* a = ap;
+ char* b = bp;
+ int charA = 0, charB = 0;
+
+ while (length != 0)
+ {
+ charA = *a;
+ charB = *b;
+
+ Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
+ if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
+
+ //Return the (case-insensitive) difference between them.
+ if (charA != charB)
+ return charA - charB;
+
+ // Next char
+ a++; b++;
+ length--;
+ }
+
+ return strA.Length - strB.Length;
+ }
+ }
+
+ //
+ // Search/Query methods
+ //
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool EqualsHelper(string strA, string strB)
+ {
+ Debug.Assert(strA != null);
+ Debug.Assert(strB != null);
+ Debug.Assert(strA.Length == strB.Length);
+
+ return SpanHelpers.SequenceEqual(
+ ref Unsafe.As<char, byte>(ref strA.GetRawStringData()),
+ ref Unsafe.As<char, byte>(ref strB.GetRawStringData()),
+ ((nuint)strA.Length) * 2);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int CompareOrdinalHelper(string strA, int indexA, int countA, string strB, int indexB, int countB)
+ {
+ Debug.Assert(strA != null);
+ Debug.Assert(strB != null);
+ Debug.Assert(indexA >= 0 && indexB >= 0);
+ Debug.Assert(countA >= 0 && countB >= 0);
+ Debug.Assert(indexA + countA <= strA.Length && indexB + countB <= strB.Length);
+
+ return SpanHelpers.SequenceCompareTo(ref Unsafe.Add(ref strA.GetRawStringData(), indexA), countA, ref Unsafe.Add(ref strB.GetRawStringData(), indexB), countB);
+ }
+
+ private static unsafe bool EqualsIgnoreCaseAsciiHelper(string strA, string strB)
+ {
+ Debug.Assert(strA != null);
+ Debug.Assert(strB != null);
+ Debug.Assert(strA.Length == strB.Length);
+ int length = strA.Length;
+
+ fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
+ {
+ char* a = ap;
+ char* b = bp;
+
+ while (length != 0)
+ {
+ int charA = *a;
+ int charB = *b;
+
+ Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
+
+ // Ordinal equals or lowercase equals if the result ends up in the a-z range
+ if (charA == charB ||
+ ((charA | 0x20) == (charB | 0x20) &&
+ (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a')))
+ {
+ a++;
+ b++;
+ length--;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ private static unsafe int CompareOrdinalHelper(string strA, string strB)
+ {
+ Debug.Assert(strA != null);
+ Debug.Assert(strB != null);
+
+ // NOTE: This may be subject to change if eliminating the check
+ // in the callers makes them small enough to be inlined
+ Debug.Assert(strA._firstChar == strB._firstChar,
+ "For performance reasons, callers of this method should " +
+ "check/short-circuit beforehand if the first char is the same.");
+
+ int length = Math.Min(strA.Length, strB.Length);
+
+ fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
+ {
+ char* a = ap;
+ char* b = bp;
+
+ // Check if the second chars are different here
+ // The reason we check if _firstChar is different is because
+ // it's the most common case and allows us to avoid a method call
+ // to here.
+ // The reason we check if the second char is different is because
+ // if the first two chars the same we can increment by 4 bytes,
+ // leaving us word-aligned on both 32-bit (12 bytes into the string)
+ // and 64-bit (16 bytes) platforms.
+
+ // For empty strings, the second char will be null due to padding.
+ // The start of the string is the type pointer + string length, which
+ // takes up 8 bytes on 32-bit, 12 on x64. For empty strings the null
+ // terminator immediately follows, leaving us with an object
+ // 10/14 bytes in size. Since everything needs to be a multiple
+ // of 4/8, this will get padded and zeroed out.
+
+ // For one-char strings the second char will be the null terminator.
+
+ // NOTE: If in the future there is a way to read the second char
+ // without pinning the string (e.g. System.Runtime.CompilerServices.Unsafe
+ // is exposed to mscorlib, or a future version of C# allows inline IL),
+ // then do that and short-circuit before the fixed.
+
+ if (*(a + 1) != *(b + 1)) goto DiffOffset1;
+
+ // Since we know that the first two chars are the same,
+ // we can increment by 2 here and skip 4 bytes.
+ // This leaves us 8-byte aligned, which results
+ // on better perf for 64-bit platforms.
+ length -= 2; a += 2; b += 2;
+
+ // unroll the loop
+#if BIT64
+ while (length >= 12)
+ {
+ if (*(long*)a != *(long*)b) goto DiffOffset0;
+ if (*(long*)(a + 4) != *(long*)(b + 4)) goto DiffOffset4;
+ if (*(long*)(a + 8) != *(long*)(b + 8)) goto DiffOffset8;
+ length -= 12; a += 12; b += 12;
+ }
+#else // BIT64
+ while (length >= 10)
+ {
+ if (*(int*)a != *(int*)b) goto DiffOffset0;
+ if (*(int*)(a + 2) != *(int*)(b + 2)) goto DiffOffset2;
+ if (*(int*)(a + 4) != *(int*)(b + 4)) goto DiffOffset4;
+ if (*(int*)(a + 6) != *(int*)(b + 6)) goto DiffOffset6;
+ if (*(int*)(a + 8) != *(int*)(b + 8)) goto DiffOffset8;
+ length -= 10; a += 10; b += 10;
+ }
+#endif // BIT64
+
+ // Fallback loop:
+ // go back to slower code path and do comparison on 4 bytes at a time.
+ // This depends on the fact that the String objects are
+ // always zero terminated and that the terminating zero is not included
+ // in the length. For odd string sizes, the last compare will include
+ // the zero terminator.
+ while (length > 0)
+ {
+ if (*(int*)a != *(int*)b) goto DiffNextInt;
+ length -= 2;
+ a += 2;
+ b += 2;
+ }
+
+ // At this point, we have compared all the characters in at least one string.
+ // The longer string will be larger.
+ return strA.Length - strB.Length;
+
+#if BIT64
+ DiffOffset8: a += 4; b += 4;
+ DiffOffset4: a += 4; b += 4;
+#else // BIT64
+ // Use jumps instead of falling through, since
+ // otherwise going to DiffOffset8 will involve
+ // 8 add instructions before getting to DiffNextInt
+ DiffOffset8: a += 8; b += 8; goto DiffOffset0;
+ DiffOffset6: a += 6; b += 6; goto DiffOffset0;
+ DiffOffset4: a += 2; b += 2;
+ DiffOffset2: a += 2; b += 2;
+#endif // BIT64
+
+ DiffOffset0:
+ // If we reached here, we already see a difference in the unrolled loop above
+#if BIT64
+ if (*(int*)a == *(int*)b)
+ {
+ a += 2; b += 2;
+ }
+#endif // BIT64
+
+ DiffNextInt:
+ if (*a != *b) return *a - *b;
+
+ DiffOffset1:
+ Debug.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!");
+ return *(a + 1) - *(b + 1);
+ }
+ }
+
+ // Provides a culture-correct string comparison. StrA is compared to StrB
+ // to determine whether it is lexicographically less, equal, or greater, and then returns
+ // either a negative integer, 0, or a positive integer; respectively.
+ //
+ public static int Compare(string strA, string strB)
+ {
+ return Compare(strA, strB, StringComparison.CurrentCulture);
+ }
+
+
+ // Provides a culture-correct string comparison. strA is compared to strB
+ // to determine whether it is lexicographically less, equal, or greater, and then a
+ // negative integer, 0, or a positive integer is returned; respectively.
+ // The case-sensitive option is set by ignoreCase
+ //
+ public static int Compare(string strA, string strB, bool ignoreCase)
+ {
+ var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
+ return Compare(strA, strB, comparisonType);
+ }
+
+
+ // Provides a more flexible function for string comparison. See StringComparison
+ // for meaning of different comparisonType.
+ public static int Compare(string strA, string strB, StringComparison comparisonType)
+ {
+ if (object.ReferenceEquals(strA, strB))
+ {
+ CheckStringComparison(comparisonType);
+ return 0;
+ }
+
+ // They can't both be null at this point.
+ if (strA == null)
+ {
+ CheckStringComparison(comparisonType);
+ return -1;
+ }
+ if (strB == null)
+ {
+ CheckStringComparison(comparisonType);
+ return 1;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.Compare(strA, strB, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ // Most common case: first character is different.
+ // Returns false for empty strings.
+ if (strA._firstChar != strB._firstChar)
+ {
+ return strA._firstChar - strB._firstChar;
+ }
+
+ return CompareOrdinalHelper(strA, strB);
+
+ case StringComparison.OrdinalIgnoreCase:
+#if CORECLR
+ // If both strings are ASCII strings, we can take the fast path.
+ if (strA.IsAscii() && strB.IsAscii())
+ {
+ return CompareOrdinalIgnoreCaseHelper(strA, strB);
+ }
+#endif
+ return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+
+ // Provides a culture-correct string comparison. strA is compared to strB
+ // to determine whether it is lexicographically less, equal, or greater, and then a
+ // negative integer, 0, or a positive integer is returned; respectively.
+ //
+ public static int Compare(string strA, string strB, CultureInfo culture, CompareOptions options)
+ {
+ if (culture == null)
+ {
+ throw new ArgumentNullException(nameof(culture));
+ }
+
+ return culture.CompareInfo.Compare(strA, strB, options);
+ }
+
+
+
+ // Provides a culture-correct string comparison. strA is compared to strB
+ // to determine whether it is lexicographically less, equal, or greater, and then a
+ // negative integer, 0, or a positive integer is returned; respectively.
+ // The case-sensitive option is set by ignoreCase, and the culture is set
+ // by culture
+ //
+ public static int Compare(string strA, string strB, bool ignoreCase, CultureInfo culture)
+ {
+ var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
+ return Compare(strA, strB, culture, options);
+ }
+
+ // Determines whether two string regions match. The substring of strA beginning
+ // at indexA of given length is compared with the substring of strB
+ // beginning at indexB of the same length.
+ //
+ public static int Compare(string strA, int indexA, string strB, int indexB, int length)
+ {
+ // NOTE: It's important we call the boolean overload, and not the StringComparison
+ // one. The two have some subtly different behavior (see notes in the former).
+ return Compare(strA, indexA, strB, indexB, length, ignoreCase: false);
+ }
+
+ // Determines whether two string regions match. The substring of strA beginning
+ // at indexA of given length is compared with the substring of strB
+ // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean.
+ //
+ public static int Compare(string strA, int indexA, string strB, int indexB, int length, bool ignoreCase)
+ {
+ // Ideally we would just forward to the string.Compare overload that takes
+ // a StringComparison parameter, and just pass in CurrentCulture/CurrentCultureIgnoreCase.
+ // That function will return early if an optimization can be applied, e.g. if
+ // (object)strA == strB && indexA == indexB then it will return 0 straightaway.
+ // There are a couple of subtle behavior differences that prevent us from doing so
+ // however:
+ // - string.Compare(null, -1, null, -1, -1, StringComparison.CurrentCulture) works
+ // since that method also returns early for nulls before validation. It shouldn't
+ // for this overload.
+ // - Since we originally forwarded to CompareInfo.Compare for all of the argument
+ // validation logic, the ArgumentOutOfRangeExceptions thrown will contain different
+ // parameter names.
+ // Therefore, we have to duplicate some of the logic here.
+
+ int lengthA = length;
+ int lengthB = length;
+
+ if (strA != null)
+ {
+ lengthA = Math.Min(lengthA, strA.Length - indexA);
+ }
+
+ if (strB != null)
+ {
+ lengthB = Math.Min(lengthB, strB.Length - indexB);
+ }
+
+ var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
+ }
+
+ // Determines whether two string regions match. The substring of strA beginning
+ // at indexA of length length is compared with the substring of strB
+ // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean,
+ // and the culture is set by culture.
+ //
+ public static int Compare(string strA, int indexA, string strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
+ {
+ var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
+ return Compare(strA, indexA, strB, indexB, length, culture, options);
+ }
+
+
+ // Determines whether two string regions match. The substring of strA beginning
+ // at indexA of length length is compared with the substring of strB
+ // beginning at indexB of the same length.
+ //
+ public static int Compare(string strA, int indexA, string strB, int indexB, int length, CultureInfo culture, CompareOptions options)
+ {
+ if (culture == null)
+ {
+ throw new ArgumentNullException(nameof(culture));
+ }
+
+ int lengthA = length;
+ int lengthB = length;
+
+ if (strA != null)
+ {
+ lengthA = Math.Min(lengthA, strA.Length - indexA);
+ }
+
+ if (strB != null)
+ {
+ lengthB = Math.Min(lengthB, strB.Length - indexB);
+ }
+
+ return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
+ }
+
+ public static int Compare(string strA, int indexA, string strB, int indexB, int length, StringComparison comparisonType)
+ {
+ CheckStringComparison(comparisonType);
+
+ if (strA == null || strB == null)
+ {
+
+ if (object.ReferenceEquals(strA, strB))
+ {
+ // They're both null
+ return 0;
+ }
+
+ return strA == null ? -1 : 1;
+ }
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
+ }
+
+ if (indexA < 0 || indexB < 0)
+ {
+ string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
+ throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
+ }
+
+ if (strA.Length - indexA < 0 || strB.Length - indexB < 0)
+ {
+ string paramName = strA.Length - indexA < 0 ? nameof(indexA) : nameof(indexB);
+ throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
+ }
+
+ if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
+ {
+ return 0;
+ }
+
+ int lengthA = Math.Min(length, strA.Length - indexA);
+ int lengthB = Math.Min(length, strB.Length - indexB);
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ // Compares strA and strB using an ordinal (code-point) comparison.
+ //
+ public static int CompareOrdinal(string strA, string strB)
+ {
+ if (object.ReferenceEquals(strA, strB))
+ {
+ return 0;
+ }
+
+ // They can't both be null at this point.
+ if (strA == null)
+ {
+ return -1;
+ }
+ if (strB == null)
+ {
+ return 1;
+ }
+
+ // Most common case, first character is different.
+ // This will return false for empty strings.
+ if (strA._firstChar != strB._firstChar)
+ {
+ return strA._firstChar - strB._firstChar;
+ }
+
+ return CompareOrdinalHelper(strA, strB);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
+ => SpanHelpers.SequenceCompareTo(ref MemoryMarshal.GetReference(strA), strA.Length, ref MemoryMarshal.GetReference(strB), strB.Length);
+
+ // Compares strA and strB using an ordinal (code-point) comparison.
+ //
+ public static int CompareOrdinal(string strA, int indexA, string strB, int indexB, int length)
+ {
+ if (strA == null || strB == null)
+ {
+ if (object.ReferenceEquals(strA, strB))
+ {
+ // They're both null
+ return 0;
+ }
+
+ return strA == null ? -1 : 1;
+ }
+
+ // COMPAT: Checking for nulls should become before the arguments are validated,
+ // but other optimizations which allow us to return early should come after.
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeCount);
+ }
+
+ if (indexA < 0 || indexB < 0)
+ {
+ string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
+ throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
+ }
+
+ int lengthA = Math.Min(length, strA.Length - indexA);
+ int lengthB = Math.Min(length, strB.Length - indexB);
+
+ if (lengthA < 0 || lengthB < 0)
+ {
+ string paramName = lengthA < 0 ? nameof(indexA) : nameof(indexB);
+ throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
+ }
+
+ if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
+ {
+ return 0;
+ }
+
+ return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
+ }
+
+ // Compares this String to another String (cast as object), returning an integer that
+ // indicates the relationship. This method returns a value less than 0 if this is less than value, 0
+ // if this is equal to value, or a value greater than 0 if this is greater than value.
+ //
+ public int CompareTo(object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+
+ string other = value as string;
+
+ if (other == null)
+ {
+ throw new ArgumentException(SR.Arg_MustBeString);
+ }
+
+ return CompareTo(other); // will call the string-based overload
+ }
+
+ // Determines the sorting relation of StrB to the current instance.
+ //
+ public int CompareTo(string strB)
+ {
+ return string.Compare(this, strB, StringComparison.CurrentCulture);
+ }
+
+ // Determines whether a specified string is a suffix of the current instance.
+ //
+ // The case-sensitive and culture-sensitive option is set by options,
+ // and the default culture is used.
+ //
+ public bool EndsWith(string value)
+ {
+ return EndsWith(value, StringComparison.CurrentCulture);
+ }
+
+ public bool EndsWith(string value, StringComparison comparisonType)
+ {
+ if ((object)value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if ((object)this == (object)value)
+ {
+ CheckStringComparison(comparisonType);
+ return true;
+ }
+
+ if (value.Length == 0)
+ {
+ CheckStringComparison(comparisonType);
+ return true;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.IsSuffix(this, value, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return this.Length < value.Length ? false : (CompareInfo.CompareOrdinalIgnoreCase(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ public bool EndsWith(string value, bool ignoreCase, CultureInfo culture)
+ {
+ if (null == value)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if ((object)this == (object)value)
+ {
+ return true;
+ }
+
+ CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
+ return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
+ }
+
+ public bool EndsWith(char value)
+ {
+ int thisLen = Length;
+ return thisLen != 0 && this[thisLen - 1] == value;
+ }
+
+ // Determines whether two strings match.
+ public override bool Equals(object obj)
+ {
+ if (object.ReferenceEquals(this, obj))
+ return true;
+
+ string str = obj as string;
+ if (str == null)
+ return false;
+
+ if (this.Length != str.Length)
+ return false;
+
+ return EqualsHelper(this, str);
+ }
+
+ // Determines whether two strings match.
+ public bool Equals(string value)
+ {
+ if (object.ReferenceEquals(this, value))
+ return true;
+
+ // NOTE: No need to worry about casting to object here.
+ // If either side of an == comparison between strings
+ // is null, Roslyn generates a simple ceq instruction
+ // instead of calling string.op_Equality.
+ if (value == null)
+ return false;
+
+ if (this.Length != value.Length)
+ return false;
+
+ return EqualsHelper(this, value);
+ }
+
+ public bool Equals(string value, StringComparison comparisonType)
+ {
+ if ((object)this == (object)value)
+ {
+ CheckStringComparison(comparisonType);
+ return true;
+ }
+
+ if ((object)value == null)
+ {
+ CheckStringComparison(comparisonType);
+ return false;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, GetCaseCompareOfComparisonCulture(comparisonType)) == 0);
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return (CompareInfo.Invariant.Compare(this, value, GetCaseCompareOfComparisonCulture(comparisonType)) == 0);
+
+ case StringComparison.Ordinal:
+ if (this.Length != value.Length)
+ return false;
+ return EqualsHelper(this, value);
+
+ case StringComparison.OrdinalIgnoreCase:
+ if (this.Length != value.Length)
+ return false;
+#if CORECLR
+ // If both strings are ASCII strings, we can take the fast path.
+ if (this.IsAscii() && value.IsAscii())
+ {
+ return EqualsIgnoreCaseAsciiHelper(this, value);
+ }
+#endif
+ return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+
+ // Determines whether two Strings match.
+ public static bool Equals(string a, string b)
+ {
+ if ((object)a == (object)b)
+ {
+ return true;
+ }
+
+ if ((object)a == null || (object)b == null || a.Length != b.Length)
+ {
+ return false;
+ }
+
+ return EqualsHelper(a, b);
+ }
+
+ public static bool Equals(string a, string b, StringComparison comparisonType)
+ {
+ if ((object)a == (object)b)
+ {
+ CheckStringComparison(comparisonType);
+ return true;
+ }
+
+ if ((object)a == null || (object)b == null)
+ {
+ CheckStringComparison(comparisonType);
+ return false;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, GetCaseCompareOfComparisonCulture(comparisonType)) == 0);
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return (CompareInfo.Invariant.Compare(a, b, GetCaseCompareOfComparisonCulture(comparisonType)) == 0);
+
+ case StringComparison.Ordinal:
+ if (a.Length != b.Length)
+ return false;
+ return EqualsHelper(a, b);
+
+ case StringComparison.OrdinalIgnoreCase:
+ if (a.Length != b.Length)
+ return false;
+#if CORECLR
+ // If both strings are ASCII strings, we can take the fast path.
+ if (a.IsAscii() && b.IsAscii())
+ {
+ return EqualsIgnoreCaseAsciiHelper(a, b);
+ }
+#endif
+ return (CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ public static bool operator ==(string a, string b)
+ {
+ return string.Equals(a, b);
+ }
+
+ public static bool operator !=(string a, string b)
+ {
+ return !string.Equals(a, b);
+ }
+
+ // Gets a hash code for this string. If strings A and B are such that A.Equals(B), then
+ // they will return the same hash code.
+ public override int GetHashCode()
+ {
+ return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2, Marvin.DefaultSeed);
+ }
+
+ // Gets a hash code for this string and this comparison. If strings A and B and comparison C are such
+ // that string.Equals(A, B, C), then they will return the same hash code with this comparison C.
+ public int GetHashCode(StringComparison comparisonType) => StringComparer.FromComparison(comparisonType).GetHashCode(this);
+
+ // Use this if and only if you need the hashcode to not change across app domains (e.g. you have an app domain agile
+ // hash table).
+ internal int GetLegacyNonRandomizedHashCode()
+ {
+ unsafe
+ {
+ fixed (char* src = &_firstChar)
+ {
+ Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
+ Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary");
+#if BIT64
+ int hash1 = 5381;
+#else // !BIT64 (32)
+ int hash1 = (5381<<16) + 5381;
+#endif
+ int hash2 = hash1;
+
+#if BIT64
+ int c;
+ char* s = src;
+ while ((c = s[0]) != 0)
+ {
+ hash1 = ((hash1 << 5) + hash1) ^ c;
+ c = s[1];
+ if (c == 0)
+ break;
+ hash2 = ((hash2 << 5) + hash2) ^ c;
+ s += 2;
+ }
+#else // !BIT64 (32)
+ // 32 bit machines.
+ int* pint = (int *)src;
+ int len = this.Length;
+ while (len > 2)
+ {
+ hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
+ hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
+ pint += 2;
+ len -= 4;
+ }
+
+ if (len > 0)
+ {
+ hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
+ }
+#endif
+ return hash1 + (hash2 * 1566083941);
+ }
+ }
+ }
+
+ // Determines whether a specified string is a prefix of the current instance
+ //
+ public bool StartsWith(string value)
+ {
+ if ((object)value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+ return StartsWith(value, StringComparison.CurrentCulture);
+ }
+
+ public bool StartsWith(string value, StringComparison comparisonType)
+ {
+ if ((object)value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if ((object)this == (object)value)
+ {
+ CheckStringComparison(comparisonType);
+ return true;
+ }
+
+ if (value.Length == 0)
+ {
+ CheckStringComparison(comparisonType);
+ return true;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.IsPrefix(this, value, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ if (this.Length < value.Length || _firstChar != value._firstChar)
+ {
+ return false;
+ }
+ return (value.Length == 1) ?
+ true : // First char is the same and thats all there is to compare
+ SpanHelpers.SequenceEqual(
+ ref Unsafe.As<char, byte>(ref this.GetRawStringData()),
+ ref Unsafe.As<char, byte>(ref value.GetRawStringData()),
+ ((nuint)value.Length) * 2);
+
+ case StringComparison.OrdinalIgnoreCase:
+ if (this.Length < value.Length)
+ {
+ return false;
+ }
+ return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ public bool StartsWith(string value, bool ignoreCase, CultureInfo culture)
+ {
+ if (null == value)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if ((object)this == (object)value)
+ {
+ return true;
+ }
+
+ CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
+ return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
+ }
+
+ public bool StartsWith(char value) => Length != 0 && _firstChar == value;
+
+ internal static void CheckStringComparison(StringComparison comparisonType)
+ {
+ // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]
+ if ((uint)comparisonType > (uint)StringComparison.OrdinalIgnoreCase)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.NotSupported_StringComparison, ExceptionArgument.comparisonType);
+ }
+ }
+
+ internal static CompareOptions GetCaseCompareOfComparisonCulture(StringComparison comparisonType)
+ {
+ Debug.Assert((uint)comparisonType <= (uint)StringComparison.OrdinalIgnoreCase);
+
+ // Culture enums can be & with CompareOptions.IgnoreCase 0x01 to extract if IgnoreCase or CompareOptions.None 0x00
+ //
+ // CompareOptions.None 0x00
+ // CompareOptions.IgnoreCase 0x01
+ //
+ // StringComparison.CurrentCulture: 0x00
+ // StringComparison.InvariantCulture: 0x02
+ // StringComparison.Ordinal 0x04
+ //
+ // StringComparison.CurrentCultureIgnoreCase: 0x01
+ // StringComparison.InvariantCultureIgnoreCase: 0x03
+ // StringComparison.OrdinalIgnoreCase 0x05
+
+ return (CompareOptions)((int)comparisonType & (int)CompareOptions.IgnoreCase);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/String.Manipulation.cs b/src/System.Private.CoreLib/shared/System/String.Manipulation.cs
new file mode 100644
index 0000000000..5face0764a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/String.Manipulation.cs
@@ -0,0 +1,1848 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ public partial class String
+ {
+ private const int StackallocIntBufferSizeLimit = 128;
+
+ private static unsafe void FillStringChecked(string dest, int destPos, string src)
+ {
+ Debug.Assert(dest != null);
+ Debug.Assert(src != null);
+ if (src.Length > dest.Length - destPos)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ fixed (char* pDest = &dest._firstChar)
+ fixed (char* pSrc = &src._firstChar)
+ {
+ wstrcpy(pDest + destPos, pSrc, src.Length);
+ }
+ }
+
+ public static string Concat(object arg0)
+ {
+ if (arg0 == null)
+ {
+ return string.Empty;
+ }
+ return arg0.ToString();
+ }
+
+ public static string Concat(object arg0, object arg1)
+ {
+ if (arg0 == null)
+ {
+ arg0 = string.Empty;
+ }
+
+ if (arg1 == null)
+ {
+ arg1 = string.Empty;
+ }
+ return Concat(arg0.ToString(), arg1.ToString());
+ }
+
+ public static string Concat(object arg0, object arg1, object arg2)
+ {
+ if (arg0 == null)
+ {
+ arg0 = string.Empty;
+ }
+
+ if (arg1 == null)
+ {
+ arg1 = string.Empty;
+ }
+
+ if (arg2 == null)
+ {
+ arg2 = string.Empty;
+ }
+
+ return Concat(arg0.ToString(), arg1.ToString(), arg2.ToString());
+ }
+
+ public static string Concat(params object[] args)
+ {
+ if (args == null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ if (args.Length <= 1)
+ {
+ return args.Length == 0 ?
+ string.Empty :
+ args[0]?.ToString() ?? string.Empty;
+ }
+
+ // We need to get an intermediary string array
+ // to fill with each of the args' ToString(),
+ // and then just concat that in one operation.
+
+ // This way we avoid any intermediary string representations,
+ // or buffer resizing if we use StringBuilder (although the
+ // latter case is partially alleviated due to StringBuilder's
+ // linked-list style implementation)
+
+ var strings = new string[args.Length];
+
+ int totalLength = 0;
+
+ for (int i = 0; i < args.Length; i++)
+ {
+ object value = args[i];
+
+ string toString = value?.ToString() ?? string.Empty; // We need to handle both the cases when value or value.ToString() is null
+ strings[i] = toString;
+
+ totalLength += toString.Length;
+
+ if (totalLength < 0) // Check for a positive overflow
+ {
+ throw new OutOfMemoryException();
+ }
+ }
+
+ // If all of the ToStrings are null/empty, just return string.Empty
+ if (totalLength == 0)
+ {
+ return string.Empty;
+ }
+
+ string result = FastAllocateString(totalLength);
+ int position = 0; // How many characters we've copied so far
+
+ for (int i = 0; i < strings.Length; i++)
+ {
+ string s = strings[i];
+
+ Debug.Assert(s != null);
+ Debug.Assert(position <= totalLength - s.Length, "We didn't allocate enough space for the result string!");
+
+ FillStringChecked(result, position, s);
+ position += s.Length;
+ }
+
+ return result;
+ }
+
+ public static string Concat<T>(IEnumerable<T> values)
+ {
+ if (values == null)
+ throw new ArgumentNullException(nameof(values));
+
+ if (typeof(T) == typeof(char))
+ {
+ // Special-case T==char, as we can handle that case much more efficiently,
+ // and string.Concat(IEnumerable<char>) can be used as an efficient
+ // enumerable-based equivalent of new string(char[]).
+ using (IEnumerator<char> en = Unsafe.As<IEnumerable<char>>(values).GetEnumerator())
+ {
+ if (!en.MoveNext())
+ {
+ // There weren't any chars. Return the empty string.
+ return Empty;
+ }
+
+ char c = en.Current; // save the first char
+
+ if (!en.MoveNext())
+ {
+ // There was only one char. Return a string from it directly.
+ return CreateFromChar(c);
+ }
+
+ // Create the StringBuilder, add the chars we've already enumerated,
+ // add the rest, and then get the resulting string.
+ StringBuilder result = StringBuilderCache.Acquire();
+ result.Append(c); // first value
+ do
+ {
+ c = en.Current;
+ result.Append(c);
+ }
+ while (en.MoveNext());
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+ }
+ else
+ {
+ using (IEnumerator<T> en = values.GetEnumerator())
+ {
+ if (!en.MoveNext())
+ return string.Empty;
+
+ // We called MoveNext once, so this will be the first item
+ T currentValue = en.Current;
+
+ // Call ToString before calling MoveNext again, since
+ // we want to stay consistent with the below loop
+ // Everything should be called in the order
+ // MoveNext-Current-ToString, unless further optimizations
+ // can be made, to avoid breaking changes
+ string firstString = currentValue?.ToString();
+
+ // If there's only 1 item, simply call ToString on that
+ if (!en.MoveNext())
+ {
+ // We have to handle the case of either currentValue
+ // or its ToString being null
+ return firstString ?? string.Empty;
+ }
+
+ StringBuilder result = StringBuilderCache.Acquire();
+
+ result.Append(firstString);
+
+ do
+ {
+ currentValue = en.Current;
+
+ if (currentValue != null)
+ {
+ result.Append(currentValue.ToString());
+ }
+ }
+ while (en.MoveNext());
+
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+ }
+ }
+
+ public static string Concat(IEnumerable<string> values)
+ {
+ if (values == null)
+ throw new ArgumentNullException(nameof(values));
+
+ using (IEnumerator<string> en = values.GetEnumerator())
+ {
+ if (!en.MoveNext())
+ return string.Empty;
+
+ string firstValue = en.Current;
+
+ if (!en.MoveNext())
+ {
+ return firstValue ?? string.Empty;
+ }
+
+ StringBuilder result = StringBuilderCache.Acquire();
+ result.Append(firstValue);
+
+ do
+ {
+ result.Append(en.Current);
+ }
+ while (en.MoveNext());
+
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+ }
+
+ public static string Concat(string str0, string str1)
+ {
+ if (IsNullOrEmpty(str0))
+ {
+ if (IsNullOrEmpty(str1))
+ {
+ return string.Empty;
+ }
+ return str1;
+ }
+
+ if (IsNullOrEmpty(str1))
+ {
+ return str0;
+ }
+
+ int str0Length = str0.Length;
+
+ string result = FastAllocateString(str0Length + str1.Length);
+
+ FillStringChecked(result, 0, str0);
+ FillStringChecked(result, str0Length, str1);
+
+ return result;
+ }
+
+ public static string Concat(string str0, string str1, string str2)
+ {
+ if (IsNullOrEmpty(str0))
+ {
+ return Concat(str1, str2);
+ }
+
+ if (IsNullOrEmpty(str1))
+ {
+ return Concat(str0, str2);
+ }
+
+ if (IsNullOrEmpty(str2))
+ {
+ return Concat(str0, str1);
+ }
+
+ int totalLength = str0.Length + str1.Length + str2.Length;
+
+ string result = FastAllocateString(totalLength);
+ FillStringChecked(result, 0, str0);
+ FillStringChecked(result, str0.Length, str1);
+ FillStringChecked(result, str0.Length + str1.Length, str2);
+
+ return result;
+ }
+
+ public static string Concat(string str0, string str1, string str2, string str3)
+ {
+ if (IsNullOrEmpty(str0))
+ {
+ return Concat(str1, str2, str3);
+ }
+
+ if (IsNullOrEmpty(str1))
+ {
+ return Concat(str0, str2, str3);
+ }
+
+ if (IsNullOrEmpty(str2))
+ {
+ return Concat(str0, str1, str3);
+ }
+
+ if (IsNullOrEmpty(str3))
+ {
+ return Concat(str0, str1, str2);
+ }
+
+ int totalLength = str0.Length + str1.Length + str2.Length + str3.Length;
+
+ string result = FastAllocateString(totalLength);
+ FillStringChecked(result, 0, str0);
+ FillStringChecked(result, str0.Length, str1);
+ FillStringChecked(result, str0.Length + str1.Length, str2);
+ FillStringChecked(result, str0.Length + str1.Length + str2.Length, str3);
+
+ return result;
+ }
+
+ public static string Concat(params string[] values)
+ {
+ if (values == null)
+ throw new ArgumentNullException(nameof(values));
+
+ if (values.Length <= 1)
+ {
+ return values.Length == 0 ?
+ string.Empty :
+ values[0] ?? string.Empty;
+ }
+
+ // It's possible that the input values array could be changed concurrently on another
+ // thread, such that we can't trust that each read of values[i] will be equivalent.
+ // Worst case, we can make a defensive copy of the array and use that, but we first
+ // optimistically try the allocation and copies assuming that the array isn't changing,
+ // which represents the 99.999% case, in particular since string.Concat is used for
+ // string concatenation by the languages, with the input array being a params array.
+
+ // Sum the lengths of all input strings
+ long totalLengthLong = 0;
+ for (int i = 0; i < values.Length; i++)
+ {
+ string value = values[i];
+ if (value != null)
+ {
+ totalLengthLong += value.Length;
+ }
+ }
+
+ // If it's too long, fail, or if it's empty, return an empty string.
+ if (totalLengthLong > int.MaxValue)
+ {
+ throw new OutOfMemoryException();
+ }
+ int totalLength = (int)totalLengthLong;
+ if (totalLength == 0)
+ {
+ return string.Empty;
+ }
+
+ // Allocate a new string and copy each input string into it
+ string result = FastAllocateString(totalLength);
+ int copiedLength = 0;
+ for (int i = 0; i < values.Length; i++)
+ {
+ string value = values[i];
+ if (!string.IsNullOrEmpty(value))
+ {
+ int valueLen = value.Length;
+ if (valueLen > totalLength - copiedLength)
+ {
+ copiedLength = -1;
+ break;
+ }
+
+ FillStringChecked(result, copiedLength, value);
+ copiedLength += valueLen;
+ }
+ }
+
+ // If we copied exactly the right amount, return the new string. Otherwise,
+ // something changed concurrently to mutate the input array: fall back to
+ // doing the concatenation again, but this time with a defensive copy. This
+ // fall back should be extremely rare.
+ return copiedLength == totalLength ? result : Concat((string[])values.Clone());
+ }
+
+ public static string Format(string format, object arg0)
+ {
+ return FormatHelper(null, format, new ParamsArray(arg0));
+ }
+
+ public static string Format(string format, object arg0, object arg1)
+ {
+ return FormatHelper(null, format, new ParamsArray(arg0, arg1));
+ }
+
+ public static string Format(string format, object arg0, object arg1, object arg2)
+ {
+ return FormatHelper(null, format, new ParamsArray(arg0, arg1, arg2));
+ }
+
+ public static string Format(string format, params object[] args)
+ {
+ if (args == null)
+ {
+ // To preserve the original exception behavior, throw an exception about format if both
+ // args and format are null. The actual null check for format is in FormatHelper.
+ throw new ArgumentNullException((format == null) ? nameof(format) : nameof(args));
+ }
+
+ return FormatHelper(null, format, new ParamsArray(args));
+ }
+
+ public static string Format(IFormatProvider provider, string format, object arg0)
+ {
+ return FormatHelper(provider, format, new ParamsArray(arg0));
+ }
+
+ public static string Format(IFormatProvider provider, string format, object arg0, object arg1)
+ {
+ return FormatHelper(provider, format, new ParamsArray(arg0, arg1));
+ }
+
+ public static string Format(IFormatProvider provider, string format, object arg0, object arg1, object arg2)
+ {
+ return FormatHelper(provider, format, new ParamsArray(arg0, arg1, arg2));
+ }
+
+ public static string Format(IFormatProvider provider, string format, params object[] args)
+ {
+ if (args == null)
+ {
+ // To preserve the original exception behavior, throw an exception about format if both
+ // args and format are null. The actual null check for format is in FormatHelper.
+ throw new ArgumentNullException((format == null) ? nameof(format) : nameof(args));
+ }
+
+ return FormatHelper(provider, format, new ParamsArray(args));
+ }
+
+ private static string FormatHelper(IFormatProvider provider, string format, ParamsArray args)
+ {
+ if (format == null)
+ throw new ArgumentNullException(nameof(format));
+
+ return StringBuilderCache.GetStringAndRelease(
+ StringBuilderCache
+ .Acquire(format.Length + args.Length * 8)
+ .AppendFormatHelper(provider, format, args));
+ }
+
+ public string Insert(int startIndex, string value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+ if (startIndex < 0 || startIndex > this.Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+
+ int oldLength = Length;
+ int insertLength = value.Length;
+
+ if (oldLength == 0)
+ return value;
+ if (insertLength == 0)
+ return this;
+
+ // In case this computation overflows, newLength will be negative and FastAllocateString throws OutOfMemoryException
+ int newLength = oldLength + insertLength;
+ string result = FastAllocateString(newLength);
+ unsafe
+ {
+ fixed (char* srcThis = &_firstChar)
+ {
+ fixed (char* srcInsert = &value._firstChar)
+ {
+ fixed (char* dst = &result._firstChar)
+ {
+ wstrcpy(dst, srcThis, startIndex);
+ wstrcpy(dst + startIndex, srcInsert, insertLength);
+ wstrcpy(dst + startIndex + insertLength, srcThis + startIndex, oldLength - startIndex);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ public static string Join(char separator, params string[] value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ return Join(separator, value, 0, value.Length);
+ }
+
+ public static unsafe string Join(char separator, params object[] values)
+ {
+ // Defer argument validation to the internal function
+ return JoinCore(&separator, 1, values);
+ }
+
+ public static unsafe string Join<T>(char separator, IEnumerable<T> values)
+ {
+ // Defer argument validation to the internal function
+ return JoinCore(&separator, 1, values);
+ }
+
+ public static unsafe string Join(char separator, string[] value, int startIndex, int count)
+ {
+ // Defer argument validation to the internal function
+ return JoinCore(&separator, 1, value, startIndex, count);
+ }
+
+ // Joins an array of strings together as one string with a separator between each original string.
+ //
+ public static string Join(string separator, params string[] value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+ return Join(separator, value, 0, value.Length);
+ }
+
+ public static unsafe string Join(string separator, params object[] values)
+ {
+ separator = separator ?? string.Empty;
+ fixed (char* pSeparator = &separator._firstChar)
+ {
+ // Defer argument validation to the internal function
+ return JoinCore(pSeparator, separator.Length, values);
+ }
+ }
+
+ public static unsafe string Join<T>(string separator, IEnumerable<T> values)
+ {
+ separator = separator ?? string.Empty;
+ fixed (char* pSeparator = &separator._firstChar)
+ {
+ // Defer argument validation to the internal function
+ return JoinCore(pSeparator, separator.Length, values);
+ }
+ }
+
+ public static string Join(string separator, IEnumerable<string> values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ using (IEnumerator<string> en = values.GetEnumerator())
+ {
+ if (!en.MoveNext())
+ {
+ return string.Empty;
+ }
+
+ string firstValue = en.Current;
+
+ if (!en.MoveNext())
+ {
+ // Only one value available
+ return firstValue ?? string.Empty;
+ }
+
+ // Null separator and values are handled by the StringBuilder
+ StringBuilder result = StringBuilderCache.Acquire();
+ result.Append(firstValue);
+
+ do
+ {
+ result.Append(separator);
+ result.Append(en.Current);
+ }
+ while (en.MoveNext());
+
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+ }
+
+ // Joins an array of strings together as one string with a separator between each original string.
+ //
+ public static unsafe string Join(string separator, string[] value, int startIndex, int count)
+ {
+ separator = separator ?? string.Empty;
+ fixed (char* pSeparator = &separator._firstChar)
+ {
+ // Defer argument validation to the internal function
+ return JoinCore(pSeparator, separator.Length, value, startIndex, count);
+ }
+ }
+
+ private static unsafe string JoinCore(char* separator, int separatorLength, object[] values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ if (values.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ string firstString = values[0]?.ToString();
+
+ if (values.Length == 1)
+ {
+ return firstString ?? string.Empty;
+ }
+
+ StringBuilder result = StringBuilderCache.Acquire();
+ result.Append(firstString);
+
+ for (int i = 1; i < values.Length; i++)
+ {
+ result.Append(separator, separatorLength);
+ object value = values[i];
+ if (value != null)
+ {
+ result.Append(value.ToString());
+ }
+ }
+
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+
+ private static unsafe string JoinCore<T>(char* separator, int separatorLength, IEnumerable<T> values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ using (IEnumerator<T> en = values.GetEnumerator())
+ {
+ if (!en.MoveNext())
+ {
+ return string.Empty;
+ }
+
+ // We called MoveNext once, so this will be the first item
+ T currentValue = en.Current;
+
+ // Call ToString before calling MoveNext again, since
+ // we want to stay consistent with the below loop
+ // Everything should be called in the order
+ // MoveNext-Current-ToString, unless further optimizations
+ // can be made, to avoid breaking changes
+ string firstString = currentValue?.ToString();
+
+ // If there's only 1 item, simply call ToString on that
+ if (!en.MoveNext())
+ {
+ // We have to handle the case of either currentValue
+ // or its ToString being null
+ return firstString ?? string.Empty;
+ }
+
+ StringBuilder result = StringBuilderCache.Acquire();
+
+ result.Append(firstString);
+
+ do
+ {
+ currentValue = en.Current;
+
+ result.Append(separator, separatorLength);
+ if (currentValue != null)
+ {
+ result.Append(currentValue.ToString());
+ }
+ }
+ while (en.MoveNext());
+
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+ }
+
+ private static unsafe string JoinCore(char* separator, int separatorLength, string[] value, int startIndex, int count)
+ {
+ // If the separator is null, it is converted to an empty string before entering this function.
+ // Even for empty strings, fixed should never return null (it should return a pointer to a null char).
+ Debug.Assert(separator != null);
+ Debug.Assert(separatorLength >= 0);
+
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount);
+ }
+ if (startIndex > value.Length - count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_IndexCountBuffer);
+ }
+
+ if (count <= 1)
+ {
+ return count == 0 ?
+ string.Empty :
+ value[startIndex] ?? string.Empty;
+ }
+
+ long totalSeparatorsLength = (long)(count - 1) * separatorLength;
+ if (totalSeparatorsLength > int.MaxValue)
+ {
+ throw new OutOfMemoryException();
+ }
+ int totalLength = (int)totalSeparatorsLength;
+
+ // Calculate the length of the resultant string so we know how much space to allocate.
+ for (int i = startIndex, end = startIndex + count; i < end; i++)
+ {
+ string currentValue = value[i];
+ if (currentValue != null)
+ {
+ totalLength += currentValue.Length;
+ if (totalLength < 0) // Check for overflow
+ {
+ throw new OutOfMemoryException();
+ }
+ }
+ }
+
+ // Copy each of the strings into the resultant buffer, interleaving with the separator.
+ string result = FastAllocateString(totalLength);
+ int copiedLength = 0;
+
+ for (int i = startIndex, end = startIndex + count; i < end; i++)
+ {
+ // It's possible that another thread may have mutated the input array
+ // such that our second read of an index will not be the same string
+ // we got during the first read.
+
+ // We range check again to avoid buffer overflows if this happens.
+
+ string currentValue = value[i];
+ if (currentValue != null)
+ {
+ int valueLen = currentValue.Length;
+ if (valueLen > totalLength - copiedLength)
+ {
+ copiedLength = -1;
+ break;
+ }
+
+ // Fill in the value.
+ FillStringChecked(result, copiedLength, currentValue);
+ copiedLength += valueLen;
+ }
+
+ if (i < end - 1)
+ {
+ // Fill in the separator.
+ fixed (char* pResult = &result._firstChar)
+ {
+ // If we are called from the char-based overload, we will not
+ // want to call MemoryCopy each time we fill in the separator. So
+ // specialize for 1-length separators.
+ if (separatorLength == 1)
+ {
+ pResult[copiedLength] = *separator;
+ }
+ else
+ {
+ wstrcpy(pResult + copiedLength, separator, separatorLength);
+ }
+ }
+ copiedLength += separatorLength;
+ }
+ }
+
+ // If we copied exactly the right amount, return the new string. Otherwise,
+ // something changed concurrently to mutate the input array: fall back to
+ // doing the concatenation again, but this time with a defensive copy. This
+ // fall back should be extremely rare.
+ return copiedLength == totalLength ?
+ result :
+ JoinCore(separator, separatorLength, (string[])value.Clone(), startIndex, count);
+ }
+
+ public string PadLeft(int totalWidth) => PadLeft(totalWidth, ' ');
+
+ public string PadLeft(int totalWidth, char paddingChar)
+ {
+ if (totalWidth < 0)
+ throw new ArgumentOutOfRangeException(nameof(totalWidth), SR.ArgumentOutOfRange_NeedNonNegNum);
+ int oldLength = Length;
+ int count = totalWidth - oldLength;
+ if (count <= 0)
+ return this;
+ string result = FastAllocateString(totalWidth);
+ unsafe
+ {
+ fixed (char* dst = &result._firstChar)
+ {
+ for (int i = 0; i < count; i++)
+ dst[i] = paddingChar;
+ fixed (char* src = &_firstChar)
+ {
+ wstrcpy(dst + count, src, oldLength);
+ }
+ }
+ }
+ return result;
+ }
+
+ public string PadRight(int totalWidth) => PadRight(totalWidth, ' ');
+
+ public string PadRight(int totalWidth, char paddingChar)
+ {
+ if (totalWidth < 0)
+ throw new ArgumentOutOfRangeException(nameof(totalWidth), SR.ArgumentOutOfRange_NeedNonNegNum);
+ int oldLength = Length;
+ int count = totalWidth - oldLength;
+ if (count <= 0)
+ return this;
+ string result = FastAllocateString(totalWidth);
+ unsafe
+ {
+ fixed (char* dst = &result._firstChar)
+ {
+ fixed (char* src = &_firstChar)
+ {
+ wstrcpy(dst, src, oldLength);
+ }
+ for (int i = 0; i < count; i++)
+ dst[oldLength + i] = paddingChar;
+ }
+ }
+ return result;
+ }
+
+ public string Remove(int startIndex, int count)
+ {
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount);
+ int oldLength = this.Length;
+ if (count > oldLength - startIndex)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_IndexCount);
+
+ if (count == 0)
+ return this;
+ int newLength = oldLength - count;
+ if (newLength == 0)
+ return string.Empty;
+
+ string result = FastAllocateString(newLength);
+ unsafe
+ {
+ fixed (char* src = &_firstChar)
+ {
+ fixed (char* dst = &result._firstChar)
+ {
+ wstrcpy(dst, src, startIndex);
+ wstrcpy(dst + startIndex, src + startIndex + count, newLength - startIndex);
+ }
+ }
+ }
+ return result;
+ }
+
+ // a remove that just takes a startindex.
+ public string Remove(int startIndex)
+ {
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+
+ if (startIndex >= Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndexLessThanLength);
+
+ return Substring(0, startIndex);
+ }
+
+ public string Replace(string oldValue, string newValue, bool ignoreCase, CultureInfo culture)
+ {
+ return ReplaceCore(oldValue, newValue, culture, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
+ }
+
+ public string Replace(string oldValue, string newValue, StringComparison comparisonType)
+ {
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return ReplaceCore(oldValue, newValue, CultureInfo.CurrentCulture, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return ReplaceCore(oldValue, newValue, CultureInfo.InvariantCulture, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ return Replace(oldValue, newValue);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return ReplaceCore(oldValue, newValue, CultureInfo.InvariantCulture, CompareOptions.OrdinalIgnoreCase);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ private unsafe string ReplaceCore(string oldValue, string newValue, CultureInfo culture, CompareOptions options)
+ {
+ if (oldValue == null)
+ throw new ArgumentNullException(nameof(oldValue));
+ if (oldValue.Length == 0)
+ throw new ArgumentException(SR.Argument_StringZeroLength, nameof(oldValue));
+
+ // If they asked to replace oldValue with a null, replace all occurrences
+ // with the empty string.
+ if (newValue == null)
+ newValue = string.Empty;
+
+ CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
+ StringBuilder result = StringBuilderCache.Acquire();
+
+ int startIndex = 0;
+ int index = 0;
+
+ int matchLength = 0;
+
+ bool hasDoneAnyReplacements = false;
+ CompareInfo ci = referenceCulture.CompareInfo;
+
+ do
+ {
+ index = ci.IndexOf(this, oldValue, startIndex, this.Length - startIndex, options, &matchLength);
+ if (index >= 0)
+ {
+ // append the unmodified portion of string
+ result.Append(this, startIndex, index - startIndex);
+
+ // append the replacement
+ result.Append(newValue);
+
+ startIndex = index + matchLength;
+ hasDoneAnyReplacements = true;
+ }
+ else if (!hasDoneAnyReplacements)
+ {
+ // small optimization,
+ // if we have not done any replacements,
+ // we will return the original string
+ StringBuilderCache.Release(result);
+ return this;
+ }
+ else
+ {
+ result.Append(this, startIndex, this.Length - startIndex);
+ }
+ } while (index >= 0);
+
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+
+ // Replaces all instances of oldChar with newChar.
+ //
+ public string Replace(char oldChar, char newChar)
+ {
+ if (oldChar == newChar)
+ return this;
+
+ unsafe
+ {
+ int remainingLength = Length;
+
+ fixed (char* pChars = &_firstChar)
+ {
+ char* pSrc = pChars;
+
+ while (remainingLength > 0)
+ {
+ if (*pSrc == oldChar)
+ {
+ break;
+ }
+
+ remainingLength--;
+ pSrc++;
+ }
+ }
+
+ if (remainingLength == 0)
+ return this;
+
+ string result = FastAllocateString(Length);
+
+ fixed (char* pChars = &_firstChar)
+ {
+ fixed (char* pResult = &result._firstChar)
+ {
+ int copyLength = Length - remainingLength;
+
+ //Copy the characters already proven not to match.
+ if (copyLength > 0)
+ {
+ wstrcpy(pResult, pChars, copyLength);
+ }
+
+ //Copy the remaining characters, doing the replacement as we go.
+ char* pSrc = pChars + copyLength;
+ char* pDst = pResult + copyLength;
+
+ do
+ {
+ char currentChar = *pSrc;
+ if (currentChar == oldChar)
+ currentChar = newChar;
+ *pDst = currentChar;
+
+ remainingLength--;
+ pSrc++;
+ pDst++;
+ } while (remainingLength > 0);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ public string Replace(string oldValue, string newValue)
+ {
+ if (oldValue == null)
+ throw new ArgumentNullException(nameof(oldValue));
+ if (oldValue.Length == 0)
+ throw new ArgumentException(SR.Argument_StringZeroLength, nameof(oldValue));
+
+ // Api behavior: if newValue is null, instances of oldValue are to be removed.
+ if (newValue == null)
+ newValue = string.Empty;
+
+ Span<int> initialSpan = stackalloc int[StackallocIntBufferSizeLimit];
+ var replacementIndices = new ValueListBuilder<int>(initialSpan);
+
+ unsafe
+ {
+ fixed (char* pThis = &_firstChar)
+ {
+ int matchIdx = 0;
+ int lastPossibleMatchIdx = this.Length - oldValue.Length;
+ while (matchIdx <= lastPossibleMatchIdx)
+ {
+ char* pMatch = pThis + matchIdx;
+ for (int probeIdx = 0; probeIdx < oldValue.Length; probeIdx++)
+ {
+ if (pMatch[probeIdx] != oldValue[probeIdx])
+ {
+ goto Next;
+ }
+ }
+ // Found a match for the string. Record the location of the match and skip over the "oldValue."
+ replacementIndices.Append(matchIdx);
+ matchIdx += oldValue.Length;
+ continue;
+
+ Next:
+ matchIdx++;
+ }
+ }
+ }
+
+ if (replacementIndices.Length == 0)
+ return this;
+
+ // String allocation and copying is in separate method to make this method faster for the case where
+ // nothing needs replacing.
+ string dst = ReplaceHelper(oldValue.Length, newValue, replacementIndices.AsSpan());
+
+ replacementIndices.Dispose();
+
+ return dst;
+ }
+
+ private string ReplaceHelper(int oldValueLength, string newValue, ReadOnlySpan<int> indices)
+ {
+ Debug.Assert(indices.Length > 0);
+
+ long dstLength = this.Length + ((long)(newValue.Length - oldValueLength)) * indices.Length;
+ if (dstLength > int.MaxValue)
+ throw new OutOfMemoryException();
+ string dst = FastAllocateString((int)dstLength);
+
+ Span<char> dstSpan = new Span<char>(ref dst.GetRawStringData(), dst.Length);
+
+ int thisIdx = 0;
+ int dstIdx = 0;
+
+ for (int r = 0; r < indices.Length; r++)
+ {
+ int replacementIdx = indices[r];
+
+ // Copy over the non-matching portion of the original that precedes this occurrence of oldValue.
+ int count = replacementIdx - thisIdx;
+ if (count != 0)
+ {
+ this.AsSpan(thisIdx, count).CopyTo(dstSpan.Slice(dstIdx));
+ dstIdx += count;
+ }
+ thisIdx = replacementIdx + oldValueLength;
+
+ // Copy over newValue to replace the oldValue.
+ newValue.AsSpan().CopyTo(dstSpan.Slice(dstIdx));
+ dstIdx += newValue.Length;
+ }
+
+ // Copy over the final non-matching portion at the end of the string.
+ Debug.Assert(this.Length - thisIdx == dstSpan.Length - dstIdx);
+ this.AsSpan(thisIdx).CopyTo(dstSpan.Slice(dstIdx));
+
+ return dst;
+ }
+
+ public string[] Split(char separator, StringSplitOptions options = StringSplitOptions.None)
+ {
+ return SplitInternal(new ReadOnlySpan<char>(ref separator, 1), int.MaxValue, options);
+ }
+
+ public string[] Split(char separator, int count, StringSplitOptions options = StringSplitOptions.None)
+ {
+ return SplitInternal(new ReadOnlySpan<char>(ref separator, 1), count, options);
+ }
+
+ // Creates an array of strings by splitting this string at each
+ // occurrence of a separator. The separator is searched for, and if found,
+ // the substring preceding the occurrence is stored as the first element in
+ // the array of strings. We then continue in this manner by searching
+ // the substring that follows the occurrence. On the other hand, if the separator
+ // is not found, the array of strings will contain this instance as its only element.
+ // If the separator is null
+ // whitespace (i.e., Character.IsWhitespace) is used as the separator.
+ //
+ public string[] Split(params char[] separator)
+ {
+ return SplitInternal(separator, int.MaxValue, StringSplitOptions.None);
+ }
+
+ // Creates an array of strings by splitting this string at each
+ // occurrence of a separator. The separator is searched for, and if found,
+ // the substring preceding the occurrence is stored as the first element in
+ // the array of strings. We then continue in this manner by searching
+ // the substring that follows the occurrence. On the other hand, if the separator
+ // is not found, the array of strings will contain this instance as its only element.
+ // If the separator is the empty string (i.e., string.Empty), then
+ // whitespace (i.e., Character.IsWhitespace) is used as the separator.
+ // If there are more than count different strings, the last n-(count-1)
+ // elements are concatenated and added as the last string.
+ //
+ public string[] Split(char[] separator, int count)
+ {
+ return SplitInternal(separator, count, StringSplitOptions.None);
+ }
+
+ public string[] Split(char[] separator, StringSplitOptions options)
+ {
+ return SplitInternal(separator, int.MaxValue, options);
+ }
+
+ public string[] Split(char[] separator, int count, StringSplitOptions options)
+ {
+ return SplitInternal(separator, count, options);
+ }
+
+ private string[] SplitInternal(ReadOnlySpan<char> separators, int count, StringSplitOptions options)
+ {
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NegativeCount);
+
+ if (options < StringSplitOptions.None || options > StringSplitOptions.RemoveEmptyEntries)
+ throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, options));
+
+ bool omitEmptyEntries = (options == StringSplitOptions.RemoveEmptyEntries);
+
+ if ((count == 0) || (omitEmptyEntries && Length == 0))
+ {
+ return Array.Empty<string>();
+ }
+
+ if (count == 1)
+ {
+ return new string[] { this };
+ }
+
+ Span<int> initialSpan = stackalloc int[StackallocIntBufferSizeLimit];
+ var sepListBuilder = new ValueListBuilder<int>(initialSpan);
+
+ MakeSeparatorList(separators, ref sepListBuilder);
+ ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
+
+ // Handle the special case of no replaces.
+ if (sepList.Length == 0)
+ {
+ return new string[] { this };
+ }
+
+ string[] result = omitEmptyEntries
+ ? SplitOmitEmptyEntries(sepList, default, 1, count)
+ : SplitKeepEmptyEntries(sepList, default, 1, count);
+
+ sepListBuilder.Dispose();
+
+ return result;
+ }
+
+ public string[] Split(string separator, StringSplitOptions options = StringSplitOptions.None)
+ {
+ return SplitInternal(separator ?? string.Empty, null, int.MaxValue, options);
+ }
+
+ public string[] Split(string separator, Int32 count, StringSplitOptions options = StringSplitOptions.None)
+ {
+ return SplitInternal(separator ?? string.Empty, null, count, options);
+ }
+
+ public string[] Split(string[] separator, StringSplitOptions options)
+ {
+ return SplitInternal(null, separator, int.MaxValue, options);
+ }
+
+ public string[] Split(string[] separator, Int32 count, StringSplitOptions options)
+ {
+ return SplitInternal(null, separator, count, options);
+ }
+
+ private string[] SplitInternal(string separator, string[] separators, int count, StringSplitOptions options)
+ {
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NegativeCount);
+ }
+
+ if (options < StringSplitOptions.None || options > StringSplitOptions.RemoveEmptyEntries)
+ {
+ throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)options));
+ }
+
+ bool omitEmptyEntries = (options == StringSplitOptions.RemoveEmptyEntries);
+
+ bool singleSeparator = separator != null;
+
+ if (!singleSeparator && (separators == null || separators.Length == 0))
+ {
+ return SplitInternal((char[])null, count, options);
+ }
+
+ if ((count == 0) || (omitEmptyEntries && Length == 0))
+ {
+ return Array.Empty<string>();
+ }
+
+ if (count == 1 || (singleSeparator && separator.Length == 0))
+ {
+ return new string[] { this };
+ }
+
+ if (singleSeparator)
+ {
+ return SplitInternal(separator, count, options);
+ }
+
+ Span<int> sepListInitialSpan = stackalloc int[StackallocIntBufferSizeLimit];
+ var sepListBuilder = new ValueListBuilder<int>(sepListInitialSpan);
+
+ Span<int> lengthListInitialSpan = stackalloc int[StackallocIntBufferSizeLimit];
+ var lengthListBuilder = new ValueListBuilder<int>(lengthListInitialSpan);
+
+ MakeSeparatorList(separators, ref sepListBuilder, ref lengthListBuilder);
+ ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
+ ReadOnlySpan<int> lengthList = lengthListBuilder.AsSpan();
+
+ // Handle the special case of no replaces.
+ if (sepList.Length == 0)
+ {
+ return new string[] { this };
+ }
+
+ string[] result = omitEmptyEntries
+ ? SplitOmitEmptyEntries(sepList, lengthList, 0, count)
+ : SplitKeepEmptyEntries(sepList, lengthList, 0, count);
+
+ sepListBuilder.Dispose();
+ lengthListBuilder.Dispose();
+
+ return result;
+ }
+
+ private string[] SplitInternal(string separator, int count, StringSplitOptions options)
+ {
+ Span<int> sepListInitialSpan = stackalloc int[StackallocIntBufferSizeLimit];
+ var sepListBuilder = new ValueListBuilder<int>(sepListInitialSpan);
+
+ MakeSeparatorList(separator, ref sepListBuilder);
+ ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
+ if (sepList.Length == 0)
+ {
+ // there are no separators so sepListBuilder did not rent an array from pool and there is no need to dispose it
+ return new string[] { this };
+ }
+
+ string[] result = options == StringSplitOptions.RemoveEmptyEntries
+ ? SplitOmitEmptyEntries(sepList, default, separator.Length, count)
+ : SplitKeepEmptyEntries(sepList, default, separator.Length, count);
+
+ sepListBuilder.Dispose();
+
+ return result;
+ }
+
+ private string[] SplitKeepEmptyEntries(ReadOnlySpan<int> sepList, ReadOnlySpan<int> lengthList, int defaultLength, int count)
+ {
+ Debug.Assert(count >= 2);
+
+ int currIndex = 0;
+ int arrIndex = 0;
+
+ count--;
+ int numActualReplaces = (sepList.Length < count) ? sepList.Length : count;
+
+ //Allocate space for the new array.
+ //+1 for the string from the end of the last replace to the end of the string.
+ string[] splitStrings = new string[numActualReplaces + 1];
+
+ for (int i = 0; i < numActualReplaces && currIndex < Length; i++)
+ {
+ splitStrings[arrIndex++] = Substring(currIndex, sepList[i] - currIndex);
+ currIndex = sepList[i] + (lengthList.IsEmpty ? defaultLength : lengthList[i]);
+ }
+
+ //Handle the last string at the end of the array if there is one.
+ if (currIndex < Length && numActualReplaces >= 0)
+ {
+ splitStrings[arrIndex] = Substring(currIndex);
+ }
+ else if (arrIndex == numActualReplaces)
+ {
+ //We had a separator character at the end of a string. Rather than just allowing
+ //a null character, we'll replace the last element in the array with an empty string.
+ splitStrings[arrIndex] = string.Empty;
+ }
+
+ return splitStrings;
+ }
+
+
+ // This function will not keep the Empty string
+ private string[] SplitOmitEmptyEntries(ReadOnlySpan<int> sepList, ReadOnlySpan<int> lengthList, int defaultLength, int count)
+ {
+ Debug.Assert(count >= 2);
+
+ int numReplaces = sepList.Length;
+
+ // Allocate array to hold items. This array may not be
+ // filled completely in this function, we will create a
+ // new array and copy string references to that new array.
+ int maxItems = (numReplaces < count) ? (numReplaces + 1) : count;
+ string[] splitStrings = new string[maxItems];
+
+ int currIndex = 0;
+ int arrIndex = 0;
+
+ for (int i = 0; i < numReplaces && currIndex < Length; i++)
+ {
+ if (sepList[i] - currIndex > 0)
+ {
+ splitStrings[arrIndex++] = Substring(currIndex, sepList[i] - currIndex);
+ }
+ currIndex = sepList[i] + (lengthList.IsEmpty ? defaultLength : lengthList[i]);
+ if (arrIndex == count - 1)
+ {
+ // If all the remaining entries at the end are empty, skip them
+ while (i < numReplaces - 1 && currIndex == sepList[++i])
+ {
+ currIndex += (lengthList.IsEmpty ? defaultLength : lengthList[i]);
+ }
+ break;
+ }
+ }
+
+ // we must have at least one slot left to fill in the last string.
+ Debug.Assert(arrIndex < maxItems);
+
+ //Handle the last string at the end of the array if there is one.
+ if (currIndex < Length)
+ {
+ splitStrings[arrIndex++] = Substring(currIndex);
+ }
+
+ string[] stringArray = splitStrings;
+ if (arrIndex != maxItems)
+ {
+ stringArray = new string[arrIndex];
+ for (int j = 0; j < arrIndex; j++)
+ {
+ stringArray[j] = splitStrings[j];
+ }
+ }
+ return stringArray;
+ }
+
+ /// <summary>
+ /// Uses ValueListBuilder to create list that holds indexes of separators in string.
+ /// </summary>
+ /// <param name="separators"><see cref="ReadOnlySpan{T}"/> of separator chars</param>
+ /// <param name="sepListBuilder"><see cref="ValueListBuilder{T}"/> to store indexes</param>
+ /// <returns></returns>
+ private void MakeSeparatorList(ReadOnlySpan<char> separators, ref ValueListBuilder<int> sepListBuilder)
+ {
+ char sep0, sep1, sep2;
+
+ switch (separators.Length)
+ {
+ // Special-case no separators to mean any whitespace is a separator.
+ case 0:
+ for (int i = 0; i < Length; i++)
+ {
+ if (char.IsWhiteSpace(this[i]))
+ {
+ sepListBuilder.Append(i);
+ }
+ }
+ break;
+
+ // Special-case the common cases of 1, 2, and 3 separators, with manual comparisons against each separator.
+ case 1:
+ sep0 = separators[0];
+ for (int i = 0; i < Length; i++)
+ {
+ if (this[i] == sep0)
+ {
+ sepListBuilder.Append(i);
+ }
+ }
+ break;
+ case 2:
+ sep0 = separators[0];
+ sep1 = separators[1];
+ for (int i = 0; i < Length; i++)
+ {
+ char c = this[i];
+ if (c == sep0 || c == sep1)
+ {
+ sepListBuilder.Append(i);
+ }
+ }
+ break;
+ case 3:
+ sep0 = separators[0];
+ sep1 = separators[1];
+ sep2 = separators[2];
+ for (int i = 0; i < Length; i++)
+ {
+ char c = this[i];
+ if (c == sep0 || c == sep1 || c == sep2)
+ {
+ sepListBuilder.Append(i);
+ }
+ }
+ break;
+
+ // Handle > 3 separators with a probabilistic map, ala IndexOfAny.
+ // This optimizes for chars being unlikely to match a separator.
+ default:
+ unsafe
+ {
+ ProbabilisticMap map = default;
+ uint* charMap = (uint*)&map;
+ InitializeProbabilisticMap(charMap, separators);
+
+ for (int i = 0; i < Length; i++)
+ {
+ char c = this[i];
+ if (IsCharBitSet(charMap, (byte)c) && IsCharBitSet(charMap, (byte)(c >> 8)) &&
+ separators.Contains(c))
+ {
+ sepListBuilder.Append(i);
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Uses ValueListBuilder to create list that holds indexes of separators in string.
+ /// </summary>
+ /// <param name="separator">separator string</param>
+ /// <param name="sepListBuilder"><see cref="ValueListBuilder{T}"/> to store indexes</param>
+ /// <returns></returns>
+ private void MakeSeparatorList(string separator, ref ValueListBuilder<int> sepListBuilder)
+ {
+ Debug.Assert(!IsNullOrEmpty(separator), "!string.IsNullOrEmpty(separator)");
+
+ int currentSepLength = separator.Length;
+
+ for (int i = 0; i < Length; i++)
+ {
+ if (this[i] == separator[0] && currentSepLength <= Length - i)
+ {
+ if (currentSepLength == 1
+ || this.AsSpan(i, currentSepLength).SequenceEqual(separator))
+ {
+ sepListBuilder.Append(i);
+ i += currentSepLength - 1;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Uses ValueListBuilder to create list that holds indexes of separators in string and list that holds length of separator strings.
+ /// </summary>
+ /// <param name="separators">separator strngs</param>
+ /// <param name="sepListBuilder"><see cref="ValueListBuilder{T}"/> for separator indexes</param>
+ /// <param name="lengthListBuilder"><see cref="ValueListBuilder{T}"/> for separator length values</param>
+ private void MakeSeparatorList(string[] separators, ref ValueListBuilder<int> sepListBuilder, ref ValueListBuilder<int> lengthListBuilder)
+ {
+ Debug.Assert(separators != null && separators.Length > 0, "separators != null && separators.Length > 0");
+
+ int sepCount = separators.Length;
+
+ for (int i = 0; i < Length; i++)
+ {
+ for (int j = 0; j < separators.Length; j++)
+ {
+ string separator = separators[j];
+ if (IsNullOrEmpty(separator))
+ {
+ continue;
+ }
+ int currentSepLength = separator.Length;
+ if (this[i] == separator[0] && currentSepLength <= Length - i)
+ {
+ if (currentSepLength == 1
+ || this.AsSpan(i, currentSepLength).SequenceEqual(separator))
+ {
+ sepListBuilder.Append(i);
+ lengthListBuilder.Append(currentSepLength);
+ i += currentSepLength - 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Returns a substring of this string.
+ //
+ public string Substring(int startIndex) => Substring(startIndex, Length - startIndex);
+
+ public string Substring(int startIndex, int length)
+ {
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+ }
+
+ if (startIndex > Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndexLargerThanLength);
+ }
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
+ }
+
+ if (startIndex > Length - length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
+ }
+
+ if (length == 0)
+ {
+ return string.Empty;
+ }
+
+ if (startIndex == 0 && length == this.Length)
+ {
+ return this;
+ }
+
+ return InternalSubString(startIndex, length);
+ }
+
+ private unsafe string InternalSubString(int startIndex, int length)
+ {
+ Debug.Assert(startIndex >= 0 && startIndex <= this.Length, "StartIndex is out of range!");
+ Debug.Assert(length >= 0 && startIndex <= this.Length - length, "length is out of range!");
+
+ string result = FastAllocateString(length);
+
+ fixed (char* dest = &result._firstChar)
+ fixed (char* src = &_firstChar)
+ {
+ wstrcpy(dest, src + startIndex, length);
+ }
+
+ return result;
+ }
+
+ // Creates a copy of this string in lower case. The culture is set by culture.
+ public string ToLower()
+ {
+ return CultureInfo.CurrentCulture.TextInfo.ToLower(this);
+ }
+
+ // Creates a copy of this string in lower case. The culture is set by culture.
+ public string ToLower(CultureInfo culture)
+ {
+ if (culture == null)
+ {
+ throw new ArgumentNullException(nameof(culture));
+ }
+ return culture.TextInfo.ToLower(this);
+ }
+
+ // Creates a copy of this string in lower case based on invariant culture.
+ public string ToLowerInvariant()
+ {
+ return CultureInfo.InvariantCulture.TextInfo.ToLower(this);
+ }
+
+ public string ToUpper()
+ {
+ return CultureInfo.CurrentCulture.TextInfo.ToUpper(this);
+ }
+
+ // Creates a copy of this string in upper case. The culture is set by culture.
+ public string ToUpper(CultureInfo culture)
+ {
+ if (culture == null)
+ {
+ throw new ArgumentNullException(nameof(culture));
+ }
+ return culture.TextInfo.ToUpper(this);
+ }
+
+ //Creates a copy of this string in upper case based on invariant culture.
+ public string ToUpperInvariant()
+ {
+ return CultureInfo.InvariantCulture.TextInfo.ToUpper(this);
+ }
+
+ // Trims the whitespace from both ends of the string. Whitespace is defined by
+ // Char.IsWhiteSpace.
+ //
+ public string Trim() => TrimWhiteSpaceHelper(TrimType.Both);
+
+ // Removes a set of characters from the beginning and end of this string.
+ public unsafe string Trim(char trimChar) => TrimHelper(&trimChar, 1, TrimType.Both);
+
+ // Removes a set of characters from the beginning and end of this string.
+ public unsafe string Trim(params char[] trimChars)
+ {
+ if (trimChars == null || trimChars.Length == 0)
+ {
+ return TrimWhiteSpaceHelper(TrimType.Both);
+ }
+ fixed (char* pTrimChars = &trimChars[0])
+ {
+ return TrimHelper(pTrimChars, trimChars.Length, TrimType.Both);
+ }
+ }
+
+ // Removes a set of characters from the beginning of this string.
+ public string TrimStart() => TrimWhiteSpaceHelper(TrimType.Head);
+
+ // Removes a set of characters from the beginning of this string.
+ public unsafe string TrimStart(char trimChar) => TrimHelper(&trimChar, 1, TrimType.Head);
+
+ // Removes a set of characters from the beginning of this string.
+ public unsafe string TrimStart(params char[] trimChars)
+ {
+ if (trimChars == null || trimChars.Length == 0)
+ {
+ return TrimWhiteSpaceHelper(TrimType.Head);
+ }
+ fixed (char* pTrimChars = &trimChars[0])
+ {
+ return TrimHelper(pTrimChars, trimChars.Length, TrimType.Head);
+ }
+ }
+
+ // Removes a set of characters from the end of this string.
+ public string TrimEnd() => TrimWhiteSpaceHelper(TrimType.Tail);
+
+ // Removes a set of characters from the end of this string.
+ public unsafe string TrimEnd(char trimChar) => TrimHelper(&trimChar, 1, TrimType.Tail);
+
+ // Removes a set of characters from the end of this string.
+ public unsafe string TrimEnd(params char[] trimChars)
+ {
+ if (trimChars == null || trimChars.Length == 0)
+ {
+ return TrimWhiteSpaceHelper(TrimType.Tail);
+ }
+ fixed (char* pTrimChars = &trimChars[0])
+ {
+ return TrimHelper(pTrimChars, trimChars.Length, TrimType.Tail);
+ }
+ }
+
+ private string TrimWhiteSpaceHelper(TrimType trimType)
+ {
+ // end will point to the first non-trimmed character on the right.
+ // start will point to the first non-trimmed character on the left.
+ int end = Length - 1;
+ int start = 0;
+
+ // Trim specified characters.
+ if (trimType != TrimType.Tail)
+ {
+ for (start = 0; start < Length; start++)
+ {
+ if (!char.IsWhiteSpace(this[start]))
+ {
+ break;
+ }
+ }
+ }
+
+ if (trimType != TrimType.Head)
+ {
+ for (end = Length - 1; end >= start; end--)
+ {
+ if (!char.IsWhiteSpace(this[end]))
+ {
+ break;
+ }
+ }
+ }
+
+ return CreateTrimmedString(start, end);
+ }
+
+ private unsafe string TrimHelper(char* trimChars, int trimCharsLength, TrimType trimType)
+ {
+ Debug.Assert(trimChars != null);
+ Debug.Assert(trimCharsLength > 0);
+
+ // end will point to the first non-trimmed character on the right.
+ // start will point to the first non-trimmed character on the left.
+ int end = Length - 1;
+ int start = 0;
+
+ // Trim specified characters.
+ if (trimType != TrimType.Tail)
+ {
+ for (start = 0; start < Length; start++)
+ {
+ int i = 0;
+ char ch = this[start];
+ for (i = 0; i < trimCharsLength; i++)
+ {
+ if (trimChars[i] == ch)
+ {
+ break;
+ }
+ }
+ if (i == trimCharsLength)
+ {
+ // The character is not in trimChars, so stop trimming.
+ break;
+ }
+ }
+ }
+
+ if (trimType != TrimType.Head)
+ {
+ for (end = Length - 1; end >= start; end--)
+ {
+ int i = 0;
+ char ch = this[end];
+ for (i = 0; i < trimCharsLength; i++)
+ {
+ if (trimChars[i] == ch)
+ {
+ break;
+ }
+ }
+ if (i == trimCharsLength)
+ {
+ // The character is not in trimChars, so stop trimming.
+ break;
+ }
+ }
+ }
+
+ return CreateTrimmedString(start, end);
+ }
+
+ private string CreateTrimmedString(int start, int end)
+ {
+ int len = end - start + 1;
+ return
+ len == Length ? this :
+ len == 0 ? string.Empty :
+ InternalSubString(start, len);
+ }
+
+ private enum TrimType
+ {
+ Head = 0,
+ Tail = 1,
+ Both = 2
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/String.Searching.cs b/src/System.Private.CoreLib/shared/System/String.Searching.cs
new file mode 100644
index 0000000000..3a6ffc3093
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/String.Searching.cs
@@ -0,0 +1,534 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Internal.Runtime.CompilerServices;
+
+namespace System
+{
+ public partial class String
+ {
+ public bool Contains(string value)
+ {
+ return (IndexOf(value, StringComparison.Ordinal) >= 0);
+ }
+
+ public bool Contains(string value, StringComparison comparisonType)
+ {
+ return (IndexOf(value, comparisonType) >= 0);
+ }
+
+ public bool Contains(char value)
+ {
+ return IndexOf(value) != -1;
+ }
+
+ public bool Contains(char value, StringComparison comparisonType)
+ {
+ return IndexOf(value, comparisonType) != -1;
+ }
+
+ // Returns the index of the first occurrence of a specified character in the current instance.
+ // The search starts at startIndex and runs thorough the next count characters.
+ //
+ public int IndexOf(char value) => SpanHelpers.IndexOf(ref _firstChar, value, Length);
+
+ public int IndexOf(char value, int startIndex)
+ {
+ return IndexOf(value, startIndex, this.Length - startIndex);
+ }
+
+ public int IndexOf(char value, StringComparison comparisonType)
+ {
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.IndexOf(this, value, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.IndexOf(this, value, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ return CompareInfo.Invariant.IndexOf(this, value, CompareOptions.Ordinal);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return CompareInfo.Invariant.IndexOf(this, value, CompareOptions.OrdinalIgnoreCase);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ public unsafe int IndexOf(char value, int startIndex, int count)
+ {
+ if ((uint)startIndex > (uint)Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ if ((uint)count > (uint)(Length - startIndex))
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ int result = SpanHelpers.IndexOf(ref Unsafe.Add(ref _firstChar, startIndex), value, count);
+
+ return result == -1 ? result : result + startIndex;
+ }
+
+ // Returns the index of the first occurrence of any specified character in the current instance.
+ // The search starts at startIndex and runs to startIndex + count - 1.
+ //
+ public int IndexOfAny(char[] anyOf)
+ {
+ return IndexOfAny(anyOf, 0, this.Length);
+ }
+
+ public int IndexOfAny(char[] anyOf, int startIndex)
+ {
+ return IndexOfAny(anyOf, startIndex, this.Length - startIndex);
+ }
+
+ public int IndexOfAny(char[] anyOf, int startIndex, int count)
+ {
+ if (anyOf == null)
+ throw new ArgumentNullException(nameof(anyOf));
+
+ if ((uint)startIndex > (uint)Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ if ((uint)count > (uint)(Length - startIndex))
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ if (anyOf.Length == 2)
+ {
+ // Very common optimization for directory separators (/, \), quotes (", '), brackets, etc
+ return IndexOfAny(anyOf[0], anyOf[1], startIndex, count);
+ }
+ else if (anyOf.Length == 3)
+ {
+ return IndexOfAny(anyOf[0], anyOf[1], anyOf[2], startIndex, count);
+ }
+ else if (anyOf.Length > 3)
+ {
+ return IndexOfCharArray(anyOf, startIndex, count);
+ }
+ else if (anyOf.Length == 1)
+ {
+ return IndexOf(anyOf[0], startIndex, count);
+ }
+ else // anyOf.Length == 0
+ {
+ return -1;
+ }
+ }
+
+ private unsafe int IndexOfAny(char value1, char value2, int startIndex, int count)
+ {
+ fixed (char* pChars = &_firstChar)
+ {
+ char* pCh = pChars + startIndex;
+
+ while (count > 0)
+ {
+ char c = *pCh;
+
+ if (c == value1 || c == value2)
+ return (int)(pCh - pChars);
+
+ // Possibly reads outside of count and can include null terminator
+ // Handled in the return logic
+ c = *(pCh + 1);
+
+ if (c == value1 || c == value2)
+ return (count == 1 ? -1 : (int)(pCh - pChars) + 1);
+
+ pCh += 2;
+ count -= 2;
+ }
+
+ return -1;
+ }
+ }
+
+ private unsafe int IndexOfAny(char value1, char value2, char value3, int startIndex, int count)
+ {
+ fixed (char* pChars = &_firstChar)
+ {
+ char* pCh = pChars + startIndex;
+
+ while (count > 0)
+ {
+ char c = *pCh;
+
+ if (c == value1 || c == value2 || c == value3)
+ return (int)(pCh - pChars);
+
+ pCh++;
+ count--;
+ }
+
+ return -1;
+ }
+ }
+
+ private unsafe int IndexOfCharArray(char[] anyOf, int startIndex, int count)
+ {
+ // use probabilistic map, see InitializeProbabilisticMap
+ ProbabilisticMap map = default(ProbabilisticMap);
+ uint* charMap = (uint*)&map;
+
+ InitializeProbabilisticMap(charMap, anyOf);
+
+ fixed (char* pChars = &_firstChar)
+ {
+ char* pCh = pChars + startIndex;
+
+ while (count > 0)
+ {
+ int thisChar = *pCh;
+
+ if (IsCharBitSet(charMap, (byte)thisChar) &&
+ IsCharBitSet(charMap, (byte)(thisChar >> 8)) &&
+ ArrayContains((char)thisChar, anyOf))
+ {
+ return (int)(pCh - pChars);
+ }
+
+ count--;
+ pCh++;
+ }
+
+ return -1;
+ }
+ }
+
+ private const int PROBABILISTICMAP_BLOCK_INDEX_MASK = 0x7;
+ private const int PROBABILISTICMAP_BLOCK_INDEX_SHIFT = 0x3;
+ private const int PROBABILISTICMAP_SIZE = 0x8;
+
+ // A probabilistic map is an optimization that is used in IndexOfAny/
+ // LastIndexOfAny methods. The idea is to create a bit map of the characters we
+ // are searching for and use this map as a "cheap" check to decide if the
+ // current character in the string exists in the array of input characters.
+ // There are 256 bits in the map, with each character mapped to 2 bits. Every
+ // character is divided into 2 bytes, and then every byte is mapped to 1 bit.
+ // The character map is an array of 8 integers acting as map blocks. The 3 lsb
+ // in each byte in the character is used to index into this map to get the
+ // right block, the value of the remaining 5 msb are used as the bit position
+ // inside this block.
+ private static unsafe void InitializeProbabilisticMap(uint* charMap, ReadOnlySpan<char> anyOf)
+ {
+ bool hasAscii = false;
+ uint* charMapLocal = charMap; // https://github.com/dotnet/coreclr/issues/14264
+
+ for (int i = 0; i < anyOf.Length; ++i)
+ {
+ int c = anyOf[i];
+
+ // Map low bit
+ SetCharBit(charMapLocal, (byte)c);
+
+ // Map high bit
+ c >>= 8;
+
+ if (c == 0)
+ {
+ hasAscii = true;
+ }
+ else
+ {
+ SetCharBit(charMapLocal, (byte)c);
+ }
+ }
+
+ if (hasAscii)
+ {
+ // Common to search for ASCII symbols. Just set the high value once.
+ charMapLocal[0] |= 1u;
+ }
+ }
+
+ private static bool ArrayContains(char searchChar, char[] anyOf)
+ {
+ for (int i = 0; i < anyOf.Length; i++)
+ {
+ if (anyOf[i] == searchChar)
+ return true;
+ }
+
+ return false;
+ }
+
+ private static unsafe bool IsCharBitSet(uint* charMap, byte value)
+ {
+ return (charMap[value & PROBABILISTICMAP_BLOCK_INDEX_MASK] & (1u << (value >> PROBABILISTICMAP_BLOCK_INDEX_SHIFT))) != 0;
+ }
+
+ private static unsafe void SetCharBit(uint* charMap, byte value)
+ {
+ charMap[value & PROBABILISTICMAP_BLOCK_INDEX_MASK] |= 1u << (value >> PROBABILISTICMAP_BLOCK_INDEX_SHIFT);
+ }
+
+ public int IndexOf(string value)
+ {
+ return IndexOf(value, StringComparison.CurrentCulture);
+ }
+
+ public int IndexOf(string value, int startIndex)
+ {
+ return IndexOf(value, startIndex, StringComparison.CurrentCulture);
+ }
+
+ public int IndexOf(string value, int startIndex, int count)
+ {
+ if (startIndex < 0 || startIndex > this.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (count < 0 || count > this.Length - startIndex)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+ }
+
+ return IndexOf(value, startIndex, count, StringComparison.CurrentCulture);
+ }
+
+ public int IndexOf(string value, StringComparison comparisonType)
+ {
+ return IndexOf(value, 0, this.Length, comparisonType);
+ }
+
+ public int IndexOf(string value, int startIndex, StringComparison comparisonType)
+ {
+ return IndexOf(value, startIndex, this.Length - startIndex, comparisonType);
+ }
+
+ public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType)
+ {
+ // Validate inputs
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (startIndex < 0 || startIndex > this.Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ if (count < 0 || startIndex > this.Length - count)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.IndexOf(this, value, startIndex, count, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.IndexOf(this, value, startIndex, count, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ case StringComparison.OrdinalIgnoreCase:
+ return CompareInfo.Invariant.IndexOfOrdinal(this, value, startIndex, count, GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ // Returns the index of the last occurrence of a specified character in the current instance.
+ // The search starts at startIndex and runs backwards to startIndex - count + 1.
+ // The character at position startIndex is included in the search. startIndex is the larger
+ // index within the string.
+ //
+ public int LastIndexOf(char value) => SpanHelpers.LastIndexOf(ref _firstChar, value, Length);
+
+ public int LastIndexOf(char value, int startIndex)
+ {
+ return LastIndexOf(value, startIndex, startIndex + 1);
+ }
+
+ public unsafe int LastIndexOf(char value, int startIndex, int count)
+ {
+ if (Length == 0)
+ return -1;
+
+ if ((uint)startIndex >= (uint)Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ if ((uint)count > (uint)startIndex + 1)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ int startSearchAt = startIndex + 1 - count;
+ int result = SpanHelpers.LastIndexOf(ref Unsafe.Add(ref _firstChar, startSearchAt), value, count);
+
+ return result == -1 ? result : result + startSearchAt;
+ }
+
+ // Returns the index of the last occurrence of any specified character in the current instance.
+ // The search starts at startIndex and runs backwards to startIndex - count + 1.
+ // The character at position startIndex is included in the search. startIndex is the larger
+ // index within the string.
+ //
+ public int LastIndexOfAny(char[] anyOf)
+ {
+ return LastIndexOfAny(anyOf, this.Length - 1, this.Length);
+ }
+
+ public int LastIndexOfAny(char[] anyOf, int startIndex)
+ {
+ return LastIndexOfAny(anyOf, startIndex, startIndex + 1);
+ }
+
+ public unsafe int LastIndexOfAny(char[] anyOf, int startIndex, int count)
+ {
+ if (anyOf == null)
+ throw new ArgumentNullException(nameof(anyOf));
+
+ if (Length == 0)
+ return -1;
+
+ if ((uint)startIndex >= (uint)Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ if ((count < 0) || ((count - 1) > startIndex))
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+ }
+
+ if (anyOf.Length > 1)
+ {
+ return LastIndexOfCharArray(anyOf, startIndex, count);
+ }
+ else if (anyOf.Length == 1)
+ {
+ return LastIndexOf(anyOf[0], startIndex, count);
+ }
+ else // anyOf.Length == 0
+ {
+ return -1;
+ }
+ }
+
+ private unsafe int LastIndexOfCharArray(char[] anyOf, int startIndex, int count)
+ {
+ // use probabilistic map, see InitializeProbabilisticMap
+ ProbabilisticMap map = default(ProbabilisticMap);
+ uint* charMap = (uint*)&map;
+
+ InitializeProbabilisticMap(charMap, anyOf);
+
+ fixed (char* pChars = &_firstChar)
+ {
+ char* pCh = pChars + startIndex;
+
+ while (count > 0)
+ {
+ int thisChar = *pCh;
+
+ if (IsCharBitSet(charMap, (byte)thisChar) &&
+ IsCharBitSet(charMap, (byte)(thisChar >> 8)) &&
+ ArrayContains((char)thisChar, anyOf))
+ {
+ return (int)(pCh - pChars);
+ }
+
+ count--;
+ pCh--;
+ }
+
+ return -1;
+ }
+ }
+
+ // Returns the index of the last occurrence of any character in value in the current instance.
+ // The search starts at startIndex and runs backwards to startIndex - count + 1.
+ // The character at position startIndex is included in the search. startIndex is the larger
+ // index within the string.
+ //
+ public int LastIndexOf(string value)
+ {
+ return LastIndexOf(value, this.Length - 1, this.Length, StringComparison.CurrentCulture);
+ }
+
+ public int LastIndexOf(string value, int startIndex)
+ {
+ return LastIndexOf(value, startIndex, startIndex + 1, StringComparison.CurrentCulture);
+ }
+
+ public int LastIndexOf(string value, int startIndex, int count)
+ {
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+ }
+
+ return LastIndexOf(value, startIndex, count, StringComparison.CurrentCulture);
+ }
+
+ public int LastIndexOf(string value, StringComparison comparisonType)
+ {
+ return LastIndexOf(value, this.Length - 1, this.Length, comparisonType);
+ }
+
+ public int LastIndexOf(string value, int startIndex, StringComparison comparisonType)
+ {
+ return LastIndexOf(value, startIndex, startIndex + 1, comparisonType);
+ }
+
+ public int LastIndexOf(string value, int startIndex, int count, StringComparison comparisonType)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ // Special case for 0 length input strings
+ if (this.Length == 0 && (startIndex == -1 || startIndex == 0))
+ return (value.Length == 0) ? 0 : -1;
+
+ // Now after handling empty strings, make sure we're not out of range
+ if (startIndex < 0 || startIndex > this.Length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ // Make sure that we allow startIndex == this.Length
+ if (startIndex == this.Length)
+ {
+ startIndex--;
+ if (count > 0)
+ count--;
+ }
+
+ // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
+ if (count < 0 || startIndex - count + 1 < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
+
+ // If we are looking for nothing, just return startIndex
+ if (value.Length == 0)
+ return startIndex;
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(this, value, startIndex, count, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.InvariantCulture:
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.LastIndexOf(this, value, startIndex, count, GetCaseCompareOfComparisonCulture(comparisonType));
+
+ case StringComparison.Ordinal:
+ case StringComparison.OrdinalIgnoreCase:
+ return CompareInfo.Invariant.LastIndexOfOrdinal(this, value, startIndex, count, GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);
+
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ [StructLayout(LayoutKind.Explicit, Size = PROBABILISTICMAP_SIZE * sizeof(uint))]
+ private struct ProbabilisticMap { }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/String.cs b/src/System.Private.CoreLib/shared/System/String.cs
new file mode 100644
index 0000000000..8c095ab899
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/String.cs
@@ -0,0 +1,766 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Text;
+
+namespace System
+{
+ // The String class represents a static string of characters. Many of
+ // the String methods perform some type of transformation on the current
+ // instance and return the result as a new String. As with arrays, character
+ // positions (indices) are zero-based.
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed partial class String : IComparable, IEnumerable, IConvertible, IEnumerable<char>, IComparable<String>, IEquatable<String>, ICloneable
+ {
+ // String constructors
+ // These are special. The implementation methods for these have a different signature from the
+ // declared constructors.
+
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ public extern String(char[] value);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private string Ctor(char[] value)
+ {
+ if (value == null || value.Length == 0)
+ return Empty;
+
+ string result = FastAllocateString(value.Length);
+ unsafe
+ {
+ fixed (char* dest = &result._firstChar, source = value)
+ wstrcpy(dest, source, value.Length);
+ }
+ return result;
+ }
+
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ public extern String(char[] value, int startIndex, int length);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private string Ctor(char[] value, int startIndex, int length)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
+
+ if (startIndex > value.Length - length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ if (length == 0)
+ return Empty;
+
+ string result = FastAllocateString(length);
+ unsafe
+ {
+ fixed (char* dest = &result._firstChar, source = value)
+ wstrcpy(dest, source + startIndex, length);
+ }
+ return result;
+ }
+
+ [CLSCompliant(false)]
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ public extern unsafe String(char* value);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private unsafe string Ctor(char* ptr)
+ {
+ if (ptr == null)
+ return Empty;
+
+ int count = wcslen(ptr);
+ if (count == 0)
+ return Empty;
+
+ string result = FastAllocateString(count);
+ fixed (char* dest = &result._firstChar)
+ wstrcpy(dest, ptr, count);
+ return result;
+ }
+
+ [CLSCompliant(false)]
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ public extern unsafe String(char* value, int startIndex, int length);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private unsafe string Ctor(char* ptr, int startIndex, int length)
+ {
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
+
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+
+ char* pStart = ptr + startIndex;
+
+ // overflow check
+ if (pStart < ptr)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_PartialWCHAR);
+
+ if (length == 0)
+ return Empty;
+
+ if (ptr == null)
+ throw new ArgumentOutOfRangeException(nameof(ptr), SR.ArgumentOutOfRange_PartialWCHAR);
+
+ string result = FastAllocateString(length);
+ fixed (char* dest = &result._firstChar)
+ wstrcpy(dest, pStart, length);
+ return result;
+ }
+
+ [CLSCompliant(false)]
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ public extern unsafe String(sbyte* value);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private unsafe string Ctor(sbyte* value)
+ {
+ byte* pb = (byte*)value;
+ if (pb == null)
+ return Empty;
+
+ int numBytes = new ReadOnlySpan<byte>((byte*)value, int.MaxValue).IndexOf<byte>(0);
+
+ // Check for overflow
+ if (numBytes < 0)
+ throw new ArgumentException(SR.Arg_MustBeNullTerminatedString);
+
+ return CreateStringForSByteConstructor(pb, numBytes);
+ }
+
+ [CLSCompliant(false)]
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ public extern unsafe String(sbyte* value, int startIndex, int length);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private unsafe string Ctor(sbyte* value, int startIndex, int length)
+ {
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
+
+ if (value == null)
+ {
+ if (length == 0)
+ return Empty;
+
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ byte* pStart = (byte*)(value + startIndex);
+
+ // overflow check
+ if (pStart < value)
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_PartialWCHAR);
+
+ return CreateStringForSByteConstructor(pStart, length);
+ }
+
+ // Encoder for String..ctor(sbyte*) and String..ctor(sbyte*, int, int)
+ private static unsafe string CreateStringForSByteConstructor(byte *pb, int numBytes)
+ {
+ Debug.Assert(numBytes >= 0);
+ Debug.Assert(pb <= (pb + numBytes));
+
+ if (numBytes == 0)
+ return Empty;
+
+#if PLATFORM_UNIX
+ return Encoding.UTF8.GetString(pb, numBytes);
+#else
+ int numCharsRequired = Interop.Kernel32.MultiByteToWideChar(Interop.Kernel32.CP_ACP, Interop.Kernel32.MB_PRECOMPOSED, pb, numBytes, (char*)null, 0);
+ if (numCharsRequired == 0)
+ throw new ArgumentException(SR.Arg_InvalidANSIString);
+
+ string newString = FastAllocateString(numCharsRequired);
+ fixed (char *pFirstChar = &newString._firstChar)
+ {
+ numCharsRequired = Interop.Kernel32.MultiByteToWideChar(Interop.Kernel32.CP_ACP, Interop.Kernel32.MB_PRECOMPOSED, pb, numBytes, pFirstChar, numCharsRequired);
+ }
+ if (numCharsRequired == 0)
+ throw new ArgumentException(SR.Arg_InvalidANSIString);
+ return newString;
+#endif
+ }
+
+ [CLSCompliant(false)]
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ public extern unsafe String(sbyte* value, int startIndex, int length, Encoding enc);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private unsafe string Ctor(sbyte* value, int startIndex, int length, Encoding enc)
+ {
+ if (enc == null)
+ return new string(value, startIndex, length);
+
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+
+ if (value == null)
+ {
+ if (length == 0)
+ return Empty;
+
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ byte* pStart = (byte*)(value + startIndex);
+
+ // overflow check
+ if (pStart < value)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_PartialWCHAR);
+
+ return enc.GetString(new ReadOnlySpan<byte>(pStart, length));
+ }
+
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ public extern String(char c, int count);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private string Ctor(char c, int count)
+ {
+ if (count <= 0)
+ {
+ if (count == 0)
+ return Empty;
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount);
+ }
+
+ string result = FastAllocateString(count);
+
+ if (c != '\0') // Fast path null char string
+ {
+ unsafe
+ {
+ fixed (char* dest = &result._firstChar)
+ {
+ uint cc = (uint)((c << 16) | c);
+ uint* dmem = (uint*)dest;
+ if (count >= 4)
+ {
+ count -= 4;
+ do
+ {
+ dmem[0] = cc;
+ dmem[1] = cc;
+ dmem += 2;
+ count -= 4;
+ } while (count >= 0);
+ }
+ if ((count & 2) != 0)
+ {
+ *dmem = cc;
+ dmem++;
+ }
+ if ((count & 1) != 0)
+ ((char*)dmem)[0] = c;
+ }
+ }
+ }
+ return result;
+ }
+
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ public extern String(ReadOnlySpan<char> value);
+
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+#if !CORECLR
+ static
+#endif
+ private unsafe string Ctor(ReadOnlySpan<char> value)
+ {
+ if (value.Length == 0)
+ return Empty;
+
+ string result = FastAllocateString(value.Length);
+ fixed (char* dest = &result._firstChar, src = &MemoryMarshal.GetReference(value))
+ wstrcpy(dest, src, value.Length);
+ return result;
+ }
+
+ public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
+ {
+ if (action == null)
+ throw new ArgumentNullException(nameof(action));
+
+ if (length <= 0)
+ {
+ if (length == 0)
+ return Empty;
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ string result = FastAllocateString(length);
+ action(new Span<char>(ref result.GetRawStringData(), length), state);
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator ReadOnlySpan<char>(string value) =>
+ value != null ? new ReadOnlySpan<char>(ref value.GetRawStringData(), value.Length) : default;
+
+ public object Clone()
+ {
+ return this;
+ }
+
+ public static unsafe string Copy(string str)
+ {
+ if (str == null)
+ throw new ArgumentNullException(nameof(str));
+
+ string result = FastAllocateString(str.Length);
+ fixed (char* dest = &result._firstChar, src = &str._firstChar)
+ wstrcpy(dest, src, str.Length);
+ return result;
+ }
+
+ // Converts a substring of this string to an array of characters. Copies the
+ // characters of this string beginning at position sourceIndex and ending at
+ // sourceIndex + count - 1 to the character array buffer, beginning
+ // at destinationIndex.
+ //
+ public unsafe void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
+ {
+ if (destination == null)
+ throw new ArgumentNullException(nameof(destination));
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount);
+ if (sourceIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_Index);
+ if (count > Length - sourceIndex)
+ throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_IndexCount);
+ if (destinationIndex > destination.Length - count || destinationIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_IndexCount);
+
+ fixed (char* src = &_firstChar, dest = destination)
+ wstrcpy(dest + destinationIndex, src + sourceIndex, count);
+ }
+
+ // Returns the entire string as an array of characters.
+ public unsafe char[] ToCharArray()
+ {
+ if (Length == 0)
+ return Array.Empty<char>();
+
+ char[] chars = new char[Length];
+ fixed (char* src = &_firstChar, dest = &chars[0])
+ wstrcpy(dest, src, Length);
+ return chars;
+ }
+
+ // Returns a substring of this string as an array of characters.
+ //
+ public unsafe char[] ToCharArray(int startIndex, int length)
+ {
+ // Range check everything.
+ if (startIndex < 0 || startIndex > Length || startIndex > Length - length)
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+
+ if (length <= 0)
+ {
+ if (length == 0)
+ return Array.Empty<char>();
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Index);
+ }
+
+ char[] chars = new char[length];
+ fixed (char* src = &_firstChar, dest = &chars[0])
+ wstrcpy(dest, src + startIndex, length);
+ return chars;
+ }
+
+ [NonVersionable]
+ public static bool IsNullOrEmpty(string value)
+ {
+ // Using 0u >= (uint)value.Length rather than
+ // value.Length == 0 as it will elide the bounds check to
+ // the first char: value[0] if that is performed following the test
+ // for the same test cost.
+ // Ternary operator returning true/false prevents redundant asm generation:
+ // https://github.com/dotnet/coreclr/issues/914
+ return (value == null || 0u >= (uint)value.Length) ? true : false;
+ }
+
+ public static bool IsNullOrWhiteSpace(string value)
+ {
+ if (value == null) return true;
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ if (!Char.IsWhiteSpace(value[i])) return false;
+ }
+
+ return true;
+ }
+
+ internal ref char GetRawStringData() => ref _firstChar;
+
+ // Helper for encodings so they can talk to our buffer directly
+ // stringLength must be the exact size we'll expect
+ internal static unsafe string CreateStringFromEncoding(
+ byte* bytes, int byteLength, Encoding encoding)
+ {
+ Debug.Assert(bytes != null);
+ Debug.Assert(byteLength >= 0);
+
+ // Get our string length
+ int stringLength = encoding.GetCharCount(bytes, byteLength, null);
+ Debug.Assert(stringLength >= 0, "stringLength >= 0");
+
+ // They gave us an empty string if they needed one
+ // 0 bytelength might be possible if there's something in an encoder
+ if (stringLength == 0)
+ return Empty;
+
+ string s = FastAllocateString(stringLength);
+ fixed (char* pTempChars = &s._firstChar)
+ {
+ int doubleCheck = encoding.GetChars(bytes, byteLength, pTempChars, stringLength, null);
+ Debug.Assert(stringLength == doubleCheck,
+ "Expected encoding.GetChars to return same length as encoding.GetCharCount");
+ }
+
+ return s;
+ }
+
+ // This is only intended to be used by char.ToString.
+ // It is necessary to put the code in this class instead of Char, since _firstChar is a private member.
+ // Making _firstChar internal would be dangerous since it would make it much easier to break String's immutability.
+ internal static string CreateFromChar(char c)
+ {
+ string result = FastAllocateString(1);
+ result._firstChar = c;
+ return result;
+ }
+
+ internal static unsafe void wstrcpy(char* dmem, char* smem, int charCount)
+ {
+ Buffer.Memmove((byte*)dmem, (byte*)smem, ((uint)charCount) * 2);
+ }
+
+
+ // Returns this string.
+ public override string ToString()
+ {
+ return this;
+ }
+
+ // Returns this string.
+ public string ToString(IFormatProvider provider)
+ {
+ return this;
+ }
+
+ public CharEnumerator GetEnumerator()
+ {
+ return new CharEnumerator(this);
+ }
+
+ IEnumerator<char> IEnumerable<char>.GetEnumerator()
+ {
+ return new CharEnumerator(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return new CharEnumerator(this);
+ }
+
+ internal static unsafe int wcslen(char* ptr)
+ {
+ char* end = ptr;
+
+ // First make sure our pointer is aligned on a word boundary
+ int alignment = IntPtr.Size - 1;
+
+ // If ptr is at an odd address (e.g. 0x5), this loop will simply iterate all the way
+ while (((uint)end & (uint)alignment) != 0)
+ {
+ if (*end == 0) goto FoundZero;
+ end++;
+ }
+
+#if !BIT64
+ // The following code is (somewhat surprisingly!) significantly faster than a naive loop,
+ // at least on x86 and the current jit.
+
+ // The loop condition below works because if "end[0] & end[1]" is non-zero, that means
+ // neither operand can have been zero. If is zero, we have to look at the operands individually,
+ // but we hope this going to fairly rare.
+
+ // In general, it would be incorrect to access end[1] if we haven't made sure
+ // end[0] is non-zero. However, we know the ptr has been aligned by the loop above
+ // so end[0] and end[1] must be in the same word (and therefore page), so they're either both accessible, or both not.
+
+ while ((end[0] & end[1]) != 0 || (end[0] != 0 && end[1] != 0))
+ {
+ end += 2;
+ }
+
+ Debug.Assert(end[0] == 0 || end[1] == 0);
+ if (end[0] != 0) end++;
+#else // !BIT64
+ // Based on https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
+
+ // 64-bit implementation: process 1 ulong (word) at a time
+
+ // What we do here is add 0x7fff from each of the
+ // 4 individual chars within the ulong, using MagicMask.
+ // If the char > 0 and < 0x8001, it will have its high bit set.
+ // We then OR with MagicMask, to set all the other bits.
+ // This will result in all bits set (ulong.MaxValue) for any
+ // char that fits the above criteria, and something else otherwise.
+
+ // Note that for any char > 0x8000, this will be a false
+ // positive and we will fallback to the slow path and
+ // check each char individually. This is OK though, since
+ // we optimize for the common case (ASCII chars, which are < 0x80).
+
+ // NOTE: We can access a ulong a time since the ptr is aligned,
+ // and therefore we're only accessing the same word/page. (See notes
+ // for the 32-bit version above.)
+
+ const ulong MagicMask = 0x7fff7fff7fff7fff;
+
+ while (true)
+ {
+ ulong word = *(ulong*)end;
+ word += MagicMask; // cause high bit to be set if not zero, and <= 0x8000
+ word |= MagicMask; // set everything besides the high bits
+
+ if (word == ulong.MaxValue) // 0xffff...
+ {
+ // all of the chars have their bits set (and therefore none can be 0)
+ end += 4;
+ continue;
+ }
+
+ // at least one of them didn't have their high bit set!
+ // go through each char and check for 0.
+
+ if (end[0] == 0) goto EndAt0;
+ if (end[1] == 0) goto EndAt1;
+ if (end[2] == 0) goto EndAt2;
+ if (end[3] == 0) goto EndAt3;
+
+ // if we reached here, it was a false positive-- just continue
+ end += 4;
+ }
+
+ EndAt3: end++;
+ EndAt2: end++;
+ EndAt1: end++;
+ EndAt0:
+#endif // !BIT64
+
+ FoundZero:
+ Debug.Assert(*end == 0);
+
+ int count = (int)(end - ptr);
+
+#if BIT64
+ // Check for overflow
+ if (ptr + count != end)
+ throw new ArgumentException(SR.Arg_MustBeNullTerminatedString);
+#else
+ Debug.Assert(ptr + count == end);
+#endif
+
+ return count;
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.String;
+ }
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(this, provider);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(this, provider);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(this, provider);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(this, provider);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(this, provider);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(this, provider);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(this, provider);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(this, provider);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(this, provider);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(this, provider);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(this, provider);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(this, provider);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(this, provider);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ return Convert.ToDateTime(this, provider);
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+
+ // Normalization Methods
+ // These just wrap calls to Normalization class
+ public bool IsNormalized()
+ {
+ return IsNormalized(NormalizationForm.FormC);
+ }
+
+ public bool IsNormalized(NormalizationForm normalizationForm)
+ {
+#if CORECLR
+ if (this.IsFastSort())
+ {
+ // If its FastSort && one of the 4 main forms, then its already normalized
+ if (normalizationForm == NormalizationForm.FormC ||
+ normalizationForm == NormalizationForm.FormKC ||
+ normalizationForm == NormalizationForm.FormD ||
+ normalizationForm == NormalizationForm.FormKD)
+ return true;
+ }
+#endif
+ return Normalization.IsNormalized(this, normalizationForm);
+ }
+
+ public string Normalize()
+ {
+ return Normalize(NormalizationForm.FormC);
+ }
+
+ public string Normalize(NormalizationForm normalizationForm)
+ {
+#if CORECLR
+ if (this.IsAscii())
+ {
+ // If its FastSort && one of the 4 main forms, then its already normalized
+ if (normalizationForm == NormalizationForm.FormC ||
+ normalizationForm == NormalizationForm.FormKC ||
+ normalizationForm == NormalizationForm.FormD ||
+ normalizationForm == NormalizationForm.FormKD)
+ return this;
+ }
+#endif
+ return Normalization.Normalize(this, normalizationForm);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/StringComparer.cs b/src/System.Private.CoreLib/shared/System/StringComparer.cs
new file mode 100644
index 0000000000..cb2d32fccb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/StringComparer.cs
@@ -0,0 +1,387 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public abstract class StringComparer : IComparer, IEqualityComparer, IComparer<string>, IEqualityComparer<string>
+ {
+ private static readonly CultureAwareComparer s_invariantCulture = new CultureAwareComparer(CultureInfo.InvariantCulture, CompareOptions.None);
+ private static readonly CultureAwareComparer s_invariantCultureIgnoreCase = new CultureAwareComparer(CultureInfo.InvariantCulture, CompareOptions.IgnoreCase);
+ private static readonly OrdinalCaseSensitiveComparer s_ordinal = new OrdinalCaseSensitiveComparer();
+ private static readonly OrdinalIgnoreCaseComparer s_ordinalIgnoreCase = new OrdinalIgnoreCaseComparer();
+
+ public static StringComparer InvariantCulture
+ {
+ get
+ {
+ return s_invariantCulture;
+ }
+ }
+
+ public static StringComparer InvariantCultureIgnoreCase
+ {
+ get
+ {
+ return s_invariantCultureIgnoreCase;
+ }
+ }
+
+ public static StringComparer CurrentCulture
+ {
+ get
+ {
+ return new CultureAwareComparer(CultureInfo.CurrentCulture, CompareOptions.None);
+ }
+ }
+
+ public static StringComparer CurrentCultureIgnoreCase
+ {
+ get
+ {
+ return new CultureAwareComparer(CultureInfo.CurrentCulture, CompareOptions.IgnoreCase);
+ }
+ }
+
+ public static StringComparer Ordinal
+ {
+ get
+ {
+ return s_ordinal;
+ }
+ }
+
+ public static StringComparer OrdinalIgnoreCase
+ {
+ get
+ {
+ return s_ordinalIgnoreCase;
+ }
+ }
+
+ // Convert a StringComparison to a StringComparer
+ public static StringComparer FromComparison(StringComparison comparisonType)
+ {
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return CurrentCulture;
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CurrentCultureIgnoreCase;
+ case StringComparison.InvariantCulture:
+ return InvariantCulture;
+ case StringComparison.InvariantCultureIgnoreCase:
+ return InvariantCultureIgnoreCase;
+ case StringComparison.Ordinal:
+ return Ordinal;
+ case StringComparison.OrdinalIgnoreCase:
+ return OrdinalIgnoreCase;
+ default:
+ throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
+ }
+ }
+
+ public static StringComparer Create(CultureInfo culture, bool ignoreCase)
+ {
+ if (culture == null)
+ {
+ throw new ArgumentNullException(nameof(culture));
+ }
+
+ return new CultureAwareComparer(culture, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
+ }
+
+ public static StringComparer Create(CultureInfo culture, CompareOptions options)
+ {
+ if (culture == null)
+ {
+ throw new ArgumentException(nameof(culture));
+ }
+
+ return new CultureAwareComparer(culture, options);
+ }
+
+ public int Compare(object x, object y)
+ {
+ if (x == y) return 0;
+ if (x == null) return -1;
+ if (y == null) return 1;
+
+ String sa = x as String;
+ if (sa != null)
+ {
+ String sb = y as String;
+ if (sb != null)
+ {
+ return Compare(sa, sb);
+ }
+ }
+
+ IComparable ia = x as IComparable;
+ if (ia != null)
+ {
+ return ia.CompareTo(y);
+ }
+
+ throw new ArgumentException(SR.Argument_ImplementIComparable);
+ }
+
+ public new bool Equals(Object x, Object y)
+ {
+ if (x == y) return true;
+ if (x == null || y == null) return false;
+
+ String sa = x as String;
+ if (sa != null)
+ {
+ String sb = y as String;
+ if (sb != null)
+ {
+ return Equals(sa, sb);
+ }
+ }
+ return x.Equals(y);
+ }
+
+ public int GetHashCode(object obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+
+ string s = obj as string;
+ if (s != null)
+ {
+ return GetHashCode(s);
+ }
+ return obj.GetHashCode();
+ }
+
+ public abstract int Compare(String x, String y);
+ public abstract bool Equals(String x, String y);
+ public abstract int GetHashCode(string obj);
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class CultureAwareComparer : StringComparer, ISerializable
+ {
+ private const CompareOptions ValidCompareMaskOffFlags = ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
+
+ private readonly CompareInfo _compareInfo; // Do not rename (binary serialization)
+ private CompareOptions _options;
+
+ internal CultureAwareComparer(CultureInfo culture, CompareOptions options) : this(culture.CompareInfo, options) { }
+
+ internal CultureAwareComparer(CompareInfo compareInfo, CompareOptions options)
+ {
+ _compareInfo = compareInfo;
+
+ if ((options & ValidCompareMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
+ }
+ _options = options;
+ }
+
+ private CultureAwareComparer(SerializationInfo info, StreamingContext context)
+ {
+ _compareInfo = (CompareInfo)info.GetValue("_compareInfo", typeof(CompareInfo));
+ bool ignoreCase = info.GetBoolean("_ignoreCase");
+
+ var obj = info.GetValueNoThrow("_options", typeof(CompareOptions));
+ if (obj != null)
+ _options = (CompareOptions)obj;
+
+ // fix up the _options value in case we are getting old serialized object not having _options
+ _options |= ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
+ }
+
+ public override int Compare(string x, string y)
+ {
+ if (object.ReferenceEquals(x, y)) return 0;
+ if (x == null) return -1;
+ if (y == null) return 1;
+ return _compareInfo.Compare(x, y, _options);
+ }
+
+ public override bool Equals(string x, string y)
+ {
+ if (object.ReferenceEquals(x, y)) return true;
+ if (x == null || y == null) return false;
+ return _compareInfo.Compare(x, y, _options) == 0;
+ }
+
+ public override int GetHashCode(string obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ return _compareInfo.GetHashCodeOfString(obj, _options);
+ }
+
+ // Equals method for the comparer itself.
+ public override bool Equals(object obj)
+ {
+ CultureAwareComparer comparer = obj as CultureAwareComparer;
+ return
+ comparer != null &&
+ _options == comparer._options &&
+ _compareInfo.Equals(comparer._compareInfo);
+ }
+
+ public override int GetHashCode()
+ {
+ return _compareInfo.GetHashCode() ^ ((int)_options & 0x7FFFFFFF);
+ }
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ info.AddValue("_compareInfo", _compareInfo);
+ info.AddValue("_options", _options);
+ info.AddValue("_ignoreCase", (_options & CompareOptions.IgnoreCase) != 0);
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class OrdinalComparer : StringComparer
+ {
+ private readonly bool _ignoreCase; // Do not rename (binary serialization)
+
+ internal OrdinalComparer(bool ignoreCase)
+ {
+ _ignoreCase = ignoreCase;
+ }
+
+ public override int Compare(string x, string y)
+ {
+ if (ReferenceEquals(x, y))
+ return 0;
+ if (x == null)
+ return -1;
+ if (y == null)
+ return 1;
+
+ if (_ignoreCase)
+ {
+ return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
+ }
+
+ return string.CompareOrdinal(x, y);
+ }
+
+ public override bool Equals(string x, string y)
+ {
+ if (ReferenceEquals(x, y))
+ return true;
+ if (x == null || y == null)
+ return false;
+
+ if (_ignoreCase)
+ {
+ if (x.Length != y.Length)
+ {
+ return false;
+ }
+ return (string.Compare(x, y, StringComparison.OrdinalIgnoreCase) == 0);
+ }
+ return x.Equals(y);
+ }
+
+ public override int GetHashCode(string obj)
+ {
+ if (obj == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
+ }
+
+ if (_ignoreCase)
+ {
+ return CompareInfo.GetIgnoreCaseHash(obj);
+ }
+
+ return obj.GetHashCode();
+ }
+
+ // Equals method for the comparer itself.
+ public override bool Equals(object obj)
+ {
+ OrdinalComparer comparer = obj as OrdinalComparer;
+ if (comparer == null)
+ {
+ return false;
+ }
+ return (this._ignoreCase == comparer._ignoreCase);
+ }
+
+ public override int GetHashCode()
+ {
+ int hashCode = nameof(OrdinalComparer).GetHashCode();
+ return _ignoreCase ? (~hashCode) : hashCode;
+ }
+ }
+
+ [Serializable]
+ internal sealed class OrdinalCaseSensitiveComparer : OrdinalComparer, ISerializable
+ {
+ public OrdinalCaseSensitiveComparer() : base(false)
+ {
+ }
+
+ public override int Compare(string x, string y) => string.CompareOrdinal(x, y);
+
+ public override bool Equals(string x, string y) => string.Equals(x, y);
+
+ public override int GetHashCode(string obj)
+ {
+ if (obj == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
+ }
+ return obj.GetHashCode();
+ }
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ info.SetType(typeof(OrdinalComparer));
+ info.AddValue("_ignoreCase", false);
+ }
+ }
+
+ [Serializable]
+ internal sealed class OrdinalIgnoreCaseComparer : OrdinalComparer, ISerializable
+ {
+ public OrdinalIgnoreCaseComparer() : base(true)
+ {
+ }
+
+ public override int Compare(string x, string y) => string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
+
+ public override bool Equals(string x, string y) => string.Equals(x, y, StringComparison.OrdinalIgnoreCase);
+
+ public override int GetHashCode(string obj)
+ {
+ if (obj == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
+ }
+ return CompareInfo.GetIgnoreCaseHash(obj);
+ }
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ info.SetType(typeof(OrdinalComparer));
+ info.AddValue("_ignoreCase", true);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/StringComparison.cs b/src/System.Private.CoreLib/shared/System/StringComparison.cs
new file mode 100644
index 0000000000..d5c18c8021
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/StringComparison.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public enum StringComparison
+ {
+ CurrentCulture = 0,
+ CurrentCultureIgnoreCase = 1,
+ InvariantCulture = 2,
+ InvariantCultureIgnoreCase = 3,
+ Ordinal = 4,
+ OrdinalIgnoreCase = 5,
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/StringSplitOptions.cs b/src/System.Private.CoreLib/shared/System/StringSplitOptions.cs
new file mode 100644
index 0000000000..d7020559a1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/StringSplitOptions.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ [Flags]
+ public enum StringSplitOptions
+ {
+ None = 0,
+ RemoveEmptyEntries = 1
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/SystemException.cs b/src/System.Private.CoreLib/shared/System/SystemException.cs
new file mode 100644
index 0000000000..b7e8e42175
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/SystemException.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class SystemException : Exception
+ {
+ public SystemException()
+ : base(SR.Arg_SystemException)
+ {
+ HResult = HResults.COR_E_SYSTEM;
+ }
+
+ public SystemException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_SYSTEM;
+ }
+
+ public SystemException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_SYSTEM;
+ }
+
+ protected SystemException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/ASCIIEncoding.cs b/src/System.Private.CoreLib/shared/System/Text/ASCIIEncoding.cs
new file mode 100644
index 0000000000..e89943a192
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/ASCIIEncoding.cs
@@ -0,0 +1,945 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ // ASCIIEncoding
+ //
+ // Note that ASCIIEncoding is optimized with no best fit and ? for fallback.
+ // It doesn't come in other flavors.
+ //
+ // Note: ASCIIEncoding is the only encoding that doesn't do best fit (windows has best fit).
+ //
+ // Note: IsAlwaysNormalized remains false because 1/2 the code points are unassigned, so they'd
+ // use fallbacks, and we cannot guarantee that fallbacks are normalized.
+
+ public class ASCIIEncoding : Encoding
+ {
+ // Allow for devirtualization (see https://github.com/dotnet/coreclr/pull/9230)
+ internal sealed class ASCIIEncodingSealed : ASCIIEncoding { }
+
+ // Used by Encoding.ASCII for lazy initialization
+ // The initialization code will not be run until a static member of the class is referenced
+ internal static readonly ASCIIEncodingSealed s_default = new ASCIIEncodingSealed();
+
+ public ASCIIEncoding() : base(Encoding.CodePageASCII)
+ {
+ }
+
+ internal override void SetDefaultFallbacks()
+ {
+ // For ASCIIEncoding we just use default replacement fallback
+ this.encoderFallback = EncoderFallback.ReplacementFallback;
+ this.decoderFallback = DecoderFallback.ReplacementFallback;
+ }
+
+ // WARNING: GetByteCount(string chars), GetBytes(string chars,...), and GetString(byte[] byteIndex...)
+ // WARNING: have different variable names than EncodingNLS.cs, so this can't just be cut & pasted,
+ // WARNING: or it'll break VB's way of calling these.
+ //
+ // The following methods are copied from EncodingNLS.cs.
+ // Unfortunately EncodingNLS.cs is internal and we're public, so we have to re-implement them here.
+ // These should be kept in sync for the following classes:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ // Returns the number of bytes required to encode a range of characters in
+ // a character array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(char[] chars, int index, int count)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - index < count)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input, return 0, avoid fixed empty array problem
+ if (count == 0)
+ return 0;
+
+ // Just call the pointer version
+ fixed (char* pChars = chars)
+ return GetByteCount(pChars + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(String chars)
+ {
+ // Validate input
+ if (chars==null)
+ throw new ArgumentNullException("chars");
+
+ fixed (char* pChars = chars)
+ return GetByteCount(pChars, chars.Length, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetByteCount(char* chars, int count)
+ {
+ // Validate Parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Call it with empty encoder
+ return GetByteCount(chars, count, null);
+ }
+
+ // Parent method is safe.
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ public override unsafe int GetBytes(String chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? "chars" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCount);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = chars) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. An exception occurs if the byte array is not large
+ // enough to hold the complete encoding of the characters. The
+ // GetByteCount method can be used to determine the exact number of
+ // bytes that will be produced for a given range of characters.
+ // Alternatively, the GetMaxByteCount method can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? "chars" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ // If nothing to encode return 0
+ if (charCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = chars) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ // Remember that byteCount is # to decode, not size of array.
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetBytes(chars, charCount, bytes, byteCount, null);
+ }
+
+ // Returns the number of characters produced by decoding a range of bytes
+ // in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetCharCount(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input just return 0, fixed doesn't like 0 length arrays
+ if (count == 0)
+ return 0;
+
+ // Just call pointer version
+ fixed (byte* pBytes = bytes)
+ return GetCharCount(pBytes + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetCharCount(byte* bytes, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetCharCount(bytes, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? "byteIndex" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if ( bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (charIndex < 0 || charIndex > chars.Length)
+ throw new ArgumentOutOfRangeException("charIndex", SR.ArgumentOutOfRange_Index);
+
+ // If no input, return 0 & avoid fixed problem
+ if (byteCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int charCount = chars.Length - charIndex;
+
+ fixed (byte* pBytes = bytes) fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ // Remember that charCount is # to decode, not size of array
+ return GetChars(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public unsafe override int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetChars(bytes, byteCount, chars, charCount, null);
+ }
+
+ // Returns a string containing the decoded representation of a range of
+ // bytes in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe String GetString(byte[] bytes, int byteIndex, int byteCount)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? "byteIndex" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+
+ if (bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Avoid problems with empty input buffer
+ if (byteCount == 0) return String.Empty;
+
+ fixed (byte* pBytes = bytes)
+ return String.CreateStringFromEncoding(
+ pBytes + byteIndex, byteCount, this);
+ }
+
+ //
+ // End of standard methods copied from EncodingNLS.cs
+ //
+
+ // GetByteCount
+ // Note: We start by assuming that the output will be the same as count. Having
+ // an encoder or fallback may change that assumption
+ internal override unsafe int GetByteCount(char* chars, int charCount, EncoderNLS encoder)
+ {
+ // Just need to ASSERT, this is called by something else internal that checked parameters already
+ Debug.Assert(charCount >= 0, "[ASCIIEncoding.GetByteCount]count is negative");
+ Debug.Assert(chars != null, "[ASCIIEncoding.GetByteCount]chars is null");
+
+ // Assert because we shouldn't be able to have a null encoder.
+ Debug.Assert(encoderFallback != null, "[ASCIIEncoding.GetByteCount]Attempting to use null fallback encoder");
+
+ char charLeftOver = (char)0;
+ EncoderReplacementFallback fallback = null;
+
+ // Start by assuming default count, then +/- for fallback characters
+ char* charEnd = chars + charCount;
+
+ // For fallback we may need a fallback buffer, we know we aren't default fallback.
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ if (encoder != null)
+ {
+ charLeftOver = encoder._charLeftOver;
+ Debug.Assert(charLeftOver == 0 || Char.IsHighSurrogate(charLeftOver),
+ "[ASCIIEncoding.GetByteCount]leftover character should be high surrogate");
+
+ fallback = encoder.Fallback as EncoderReplacementFallback;
+
+ // We mustn't have left over fallback data when counting
+ if (encoder.InternalHasFallbackBuffer)
+ {
+ // We always need the fallback buffer in get bytes so we can flush any remaining ones if necessary
+ fallbackBuffer = encoder.FallbackBuffer;
+ if (fallbackBuffer.Remaining > 0 && encoder._throwOnOverflow)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType()));
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(chars, charEnd, encoder, false);
+ }
+
+ // Verify that we have no fallbackbuffer, for ASCII its always empty, so just assert
+ Debug.Assert(!encoder._throwOnOverflow || !encoder.InternalHasFallbackBuffer ||
+ encoder.FallbackBuffer.Remaining == 0,
+ "[ASCIICodePageEncoding.GetByteCount]Expected empty fallback buffer");
+ }
+ else
+ {
+ fallback = this.EncoderFallback as EncoderReplacementFallback;
+ }
+
+ // If we have an encoder AND we aren't using default fallback,
+ // then we may have a complicated count.
+ if (fallback != null && fallback.MaxCharCount == 1)
+ {
+ // Replacement fallback encodes surrogate pairs as two ?? (or two whatever), so return size is always
+ // same as input size.
+ // Note that no existing SBCS code pages map code points to supplimentary characters, so this is easy.
+
+ // We could however have 1 extra byte if the last call had an encoder and a funky fallback and
+ // if we don't use the funky fallback this time.
+
+ // Do we have an extra char left over from last time?
+ if (charLeftOver > 0)
+ charCount++;
+
+ return (charCount);
+ }
+
+ // Count is more complicated if you have a funky fallback
+ // For fallback we may need a fallback buffer, we know we're not default fallback
+ int byteCount = 0;
+
+ // We may have a left over character from last time, try and process it.
+ if (charLeftOver > 0)
+ {
+ Debug.Assert(Char.IsHighSurrogate(charLeftOver), "[ASCIIEncoding.GetByteCount]leftover character should be high surrogate");
+ Debug.Assert(encoder != null, "[ASCIIEncoding.GetByteCount]Expected encoder");
+
+ // Since left over char was a surrogate, it'll have to be fallen back.
+ // Get Fallback
+ fallbackBuffer = encoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(chars, charEnd, encoder, false);
+
+ // This will fallback a pair if *chars is a low surrogate
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be enregistered
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+ }
+
+ // Now we may have fallback char[] already from the encoder
+
+ // Go ahead and do it, including the fallback.
+ char ch;
+ while ((ch = (fallbackBuffer == null) ? '\0' : fallbackBuffer.InternalGetNextChar()) != 0 ||
+ chars < charEnd)
+ {
+ // First unwind any fallback
+ if (ch == 0)
+ {
+ // No fallback, just get next char
+ ch = *chars;
+ chars++;
+ }
+
+ // Check for fallback, this'll catch surrogate pairs too.
+ // no chars >= 0x80 are allowed.
+ if (ch > 0x7f)
+ {
+ if (fallbackBuffer == null)
+ {
+ // Initialize the buffer
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(charEnd - charCount, charEnd, encoder, false);
+ }
+
+ // Get Fallback
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be enregistered
+ fallbackBuffer.InternalFallback(ch, ref charsForFallback);
+ chars = charsForFallback;
+ continue;
+ }
+
+ // We'll use this one
+ byteCount++;
+ }
+
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[ASCIIEncoding.GetByteCount]Expected Empty fallback buffer");
+
+ return byteCount;
+ }
+
+ internal override unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, EncoderNLS encoder)
+ {
+ // Just need to ASSERT, this is called by something else internal that checked parameters already
+ Debug.Assert(bytes != null, "[ASCIIEncoding.GetBytes]bytes is null");
+ Debug.Assert(byteCount >= 0, "[ASCIIEncoding.GetBytes]byteCount is negative");
+ Debug.Assert(chars != null, "[ASCIIEncoding.GetBytes]chars is null");
+ Debug.Assert(charCount >= 0, "[ASCIIEncoding.GetBytes]charCount is negative");
+
+ // Assert because we shouldn't be able to have a null encoder.
+ Debug.Assert(encoderFallback != null, "[ASCIIEncoding.GetBytes]Attempting to use null encoder fallback");
+
+ // Get any left over characters
+ char charLeftOver = (char)0;
+ EncoderReplacementFallback fallback = null;
+
+ // For fallback we may need a fallback buffer, we know we aren't default fallback.
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ // prepare our end
+ char* charEnd = chars + charCount;
+ byte* byteStart = bytes;
+ char* charStart = chars;
+
+ if (encoder != null)
+ {
+ charLeftOver = encoder._charLeftOver;
+ fallback = encoder.Fallback as EncoderReplacementFallback;
+
+ // We mustn't have left over fallback data when counting
+ if (encoder.InternalHasFallbackBuffer)
+ {
+ // We always need the fallback buffer in get bytes so we can flush any remaining ones if necessary
+ fallbackBuffer = encoder.FallbackBuffer;
+ if (fallbackBuffer.Remaining > 0 && encoder._throwOnOverflow)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType()));
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, true);
+ }
+
+ Debug.Assert(charLeftOver == 0 || Char.IsHighSurrogate(charLeftOver),
+ "[ASCIIEncoding.GetBytes]leftover character should be high surrogate");
+
+ // Verify that we have no fallbackbuffer, for ASCII its always empty, so just assert
+ Debug.Assert(!encoder._throwOnOverflow || !encoder.InternalHasFallbackBuffer ||
+ encoder.FallbackBuffer.Remaining == 0,
+ "[ASCIICodePageEncoding.GetBytes]Expected empty fallback buffer");
+ }
+ else
+ {
+ fallback = this.EncoderFallback as EncoderReplacementFallback;
+ }
+
+
+ // See if we do the fast default or slightly slower fallback
+ if (fallback != null && fallback.MaxCharCount == 1)
+ {
+ // Fast version
+ char cReplacement = fallback.DefaultString[0];
+
+ // Check for replacements in range, otherwise fall back to slow version.
+ if (cReplacement <= (char)0x7f)
+ {
+ // We should have exactly as many output bytes as input bytes, unless there's a left
+ // over character, in which case we may need one more.
+ // If we had a left over character will have to add a ? (This happens if they had a funky
+ // fallback last time, but not this time.) (We can't spit any out though
+ // because with fallback encoder each surrogate is treated as a seperate code point)
+ if (charLeftOver > 0)
+ {
+ // Have to have room
+ // Throw even if doing no throw version because this is just 1 char,
+ // so buffer will never be big enough
+ if (byteCount == 0)
+ ThrowBytesOverflow(encoder, true);
+
+ // This'll make sure we still have more room and also make sure our return value is correct.
+ *(bytes++) = (byte)cReplacement;
+ byteCount--; // We used one of the ones we were counting.
+ }
+
+ // This keeps us from overrunning our output buffer
+ if (byteCount < charCount)
+ {
+ // Throw or make buffer smaller?
+ ThrowBytesOverflow(encoder, byteCount < 1);
+
+ // Just use what we can
+ charEnd = chars + byteCount;
+ }
+
+ // We just do a quick copy
+ while (chars < charEnd)
+ {
+ char ch2 = *(chars++);
+ if (ch2 >= 0x0080) *(bytes++) = (byte)cReplacement;
+ else *(bytes++) = unchecked((byte)(ch2));
+ }
+
+ // Clear encoder
+ if (encoder != null)
+ {
+ encoder._charLeftOver = (char)0;
+ encoder._charsUsed = (int)(chars - charStart);
+ }
+
+ return (int)(bytes - byteStart);
+ }
+ }
+
+ // Slower version, have to do real fallback.
+
+ // prepare our end
+ byte* byteEnd = bytes + byteCount;
+
+ // We may have a left over character from last time, try and process it.
+ if (charLeftOver > 0)
+ {
+ // Initialize the buffer
+ Debug.Assert(encoder != null,
+ "[ASCIIEncoding.GetBytes]Expected non null encoder if we have surrogate left over");
+ fallbackBuffer = encoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(chars, charEnd, encoder, true);
+
+ // Since left over char was a surrogate, it'll have to be fallen back.
+ // Get Fallback
+ // This will fallback a pair if *chars is a low surrogate
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be enregistered
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+ }
+
+ // Now we may have fallback char[] already from the encoder
+
+ // Go ahead and do it, including the fallback.
+ char ch;
+ while ((ch = (fallbackBuffer == null) ? '\0' : fallbackBuffer.InternalGetNextChar()) != 0 ||
+ chars < charEnd)
+ {
+ // First unwind any fallback
+ if (ch == 0)
+ {
+ // No fallback, just get next char
+ ch = *chars;
+ chars++;
+ }
+
+ // Check for fallback, this'll catch surrogate pairs too.
+ // All characters >= 0x80 must fall back.
+ if (ch > 0x7f)
+ {
+ // Initialize the buffer
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(charEnd - charCount, charEnd, encoder, true);
+ }
+
+ // Get Fallback
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be enregistered
+ fallbackBuffer.InternalFallback(ch, ref charsForFallback);
+ chars = charsForFallback;
+
+ // Go ahead & continue (& do the fallback)
+ continue;
+ }
+
+ // We'll use this one
+ // Bounds check
+ if (bytes >= byteEnd)
+ {
+ // didn't use this char, we'll throw or use buffer
+ if (fallbackBuffer == null || fallbackBuffer.bFallingBack == false)
+ {
+ Debug.Assert(chars > charStart || bytes == byteStart,
+ "[ASCIIEncoding.GetBytes]Expected chars to have advanced already.");
+ chars--; // don't use last char
+ }
+ else
+ fallbackBuffer.MovePrevious();
+
+ // Are we throwing or using buffer?
+ ThrowBytesOverflow(encoder, bytes == byteStart); // throw?
+ break; // don't throw, stop
+ }
+
+ // Go ahead and add it
+ *bytes = unchecked((byte)ch);
+ bytes++;
+ }
+
+ // Need to do encoder stuff
+ if (encoder != null)
+ {
+ // Fallback stuck it in encoder if necessary, but we have to clear MustFlush cases
+ if (fallbackBuffer != null && !fallbackBuffer.bUsedEncoder)
+ // Clear it in case of MustFlush
+ encoder._charLeftOver = (char)0;
+
+ // Set our chars used count
+ encoder._charsUsed = (int)(chars - charStart);
+ }
+
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0 ||
+ (encoder != null && !encoder._throwOnOverflow),
+ "[ASCIIEncoding.GetBytes]Expected Empty fallback buffer at end");
+
+ return (int)(bytes - byteStart);
+ }
+
+ // This is internal and called by something else,
+ internal override unsafe int GetCharCount(byte* bytes, int count, DecoderNLS decoder)
+ {
+ // Just assert, we're called internally so these should be safe, checked already
+ Debug.Assert(bytes != null, "[ASCIIEncoding.GetCharCount]bytes is null");
+ Debug.Assert(count >= 0, "[ASCIIEncoding.GetCharCount]byteCount is negative");
+
+ // ASCII doesn't do best fit, so don't have to check for it, find out which decoder fallback we're using
+ DecoderReplacementFallback fallback = null;
+
+ if (decoder == null)
+ fallback = this.DecoderFallback as DecoderReplacementFallback;
+ else
+ {
+ fallback = decoder.Fallback as DecoderReplacementFallback;
+ Debug.Assert(!decoder._throwOnOverflow || !decoder.InternalHasFallbackBuffer ||
+ decoder.FallbackBuffer.Remaining == 0,
+ "[ASCIICodePageEncoding.GetCharCount]Expected empty fallback buffer");
+ }
+
+ if (fallback != null && fallback.MaxCharCount == 1)
+ {
+ // Just return length, SBCS stay the same length because they don't map to surrogate
+ // pairs and we don't have a decoder fallback.
+
+ return count;
+ }
+
+ // Only need decoder fallback buffer if not using default replacement fallback, no best fit for ASCII
+ DecoderFallbackBuffer fallbackBuffer = null;
+
+ // Have to do it the hard way.
+ // Assume charCount will be == count
+ int charCount = count;
+ byte[] byteBuffer = new byte[1];
+
+ // Do it our fast way
+ byte* byteEnd = bytes + count;
+
+ // Quick loop
+ while (bytes < byteEnd)
+ {
+ // Faster if don't use *bytes++;
+ byte b = *bytes;
+ bytes++;
+
+ // If unknown we have to do fallback count
+ if (b >= 0x80)
+ {
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.DecoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(byteEnd - count, null);
+ }
+
+ // Use fallback buffer
+ byteBuffer[0] = b;
+ charCount--; // Have to unreserve the one we already allocated for b
+ charCount += fallbackBuffer.InternalFallback(byteBuffer, bytes);
+ }
+ }
+
+ // Fallback buffer must be empty
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[ASCIIEncoding.GetCharCount]Expected Empty fallback buffer");
+
+ // Converted sequence is same length as input
+ return charCount;
+ }
+
+ internal override unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, DecoderNLS decoder)
+ {
+ // Just need to ASSERT, this is called by something else internal that checked parameters already
+ Debug.Assert(bytes != null, "[ASCIIEncoding.GetChars]bytes is null");
+ Debug.Assert(byteCount >= 0, "[ASCIIEncoding.GetChars]byteCount is negative");
+ Debug.Assert(chars != null, "[ASCIIEncoding.GetChars]chars is null");
+ Debug.Assert(charCount >= 0, "[ASCIIEncoding.GetChars]charCount is negative");
+
+ // Do it fast way if using ? replacement fallback
+ byte* byteEnd = bytes + byteCount;
+ byte* byteStart = bytes;
+ char* charStart = chars;
+
+ // Note: ASCII doesn't do best fit, but we have to fallback if they use something > 0x7f
+ // Only need decoder fallback buffer if not using ? fallback.
+ // ASCII doesn't do best fit, so don't have to check for it, find out which decoder fallback we're using
+ DecoderReplacementFallback fallback = null;
+ char* charsForFallback;
+
+ if (decoder == null)
+ fallback = this.DecoderFallback as DecoderReplacementFallback;
+ else
+ {
+ fallback = decoder.Fallback as DecoderReplacementFallback;
+ Debug.Assert(!decoder._throwOnOverflow || !decoder.InternalHasFallbackBuffer ||
+ decoder.FallbackBuffer.Remaining == 0,
+ "[ASCIICodePageEncoding.GetChars]Expected empty fallback buffer");
+ }
+
+ if (fallback != null && fallback.MaxCharCount == 1)
+ {
+ // Try it the fast way
+ char replacementChar = fallback.DefaultString[0];
+
+ // Need byteCount chars, otherwise too small buffer
+ if (charCount < byteCount)
+ {
+ // Need at least 1 output byte, throw if must throw
+ ThrowCharsOverflow(decoder, charCount < 1);
+
+ // Not throwing, use what we can
+ byteEnd = bytes + charCount;
+ }
+
+ // Quick loop, just do '?' replacement because we don't have fallbacks for decodings.
+ while (bytes < byteEnd)
+ {
+ byte b = *(bytes++);
+ if (b >= 0x80)
+ // This is an invalid byte in the ASCII encoding.
+ *(chars++) = replacementChar;
+ else
+ *(chars++) = unchecked((char)b);
+ }
+
+ // bytes & chars used are the same
+ if (decoder != null)
+ decoder._bytesUsed = (int)(bytes - byteStart);
+ return (int)(chars - charStart);
+ }
+
+ // Slower way's going to need a fallback buffer
+ DecoderFallbackBuffer fallbackBuffer = null;
+ byte[] byteBuffer = new byte[1];
+ char* charEnd = chars + charCount;
+
+ // Not quite so fast loop
+ while (bytes < byteEnd)
+ {
+ // Faster if don't use *bytes++;
+ byte b = *(bytes);
+ bytes++;
+
+ if (b >= 0x80)
+ {
+ // This is an invalid byte in the ASCII encoding.
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.DecoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(byteEnd - byteCount, charEnd);
+ }
+
+ // Use fallback buffer
+ byteBuffer[0] = b;
+
+ // Note that chars won't get updated unless this succeeds
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be enregistered
+ bool fallbackResult = fallbackBuffer.InternalFallback(byteBuffer, bytes, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (!fallbackResult)
+ {
+ // May or may not throw, but we didn't get this byte
+ Debug.Assert(bytes > byteStart || chars == charStart,
+ "[ASCIIEncoding.GetChars]Expected bytes to have advanced already (fallback case)");
+ bytes--; // unused byte
+ fallbackBuffer.InternalReset(); // Didn't fall this back
+ ThrowCharsOverflow(decoder, chars == charStart); // throw?
+ break; // don't throw, but stop loop
+ }
+ }
+ else
+ {
+ // Make sure we have buffer space
+ if (chars >= charEnd)
+ {
+ Debug.Assert(bytes > byteStart || chars == charStart,
+ "[ASCIIEncoding.GetChars]Expected bytes to have advanced already (normal case)");
+ bytes--; // unused byte
+ ThrowCharsOverflow(decoder, chars == charStart); // throw?
+ break; // don't throw, but stop loop
+ }
+
+ *(chars) = unchecked((char)b);
+ chars++;
+ }
+ }
+
+ // Might have had decoder fallback stuff.
+ if (decoder != null)
+ decoder._bytesUsed = (int)(bytes - byteStart);
+
+ // Expect Empty fallback buffer for GetChars
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[ASCIIEncoding.GetChars]Expected Empty fallback buffer");
+
+ return (int)(chars - charStart);
+ }
+
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ if (charCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(charCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Characters would be # of characters + 1 in case high surrogate is ? * max fallback
+ long byteCount = (long)charCount + 1;
+
+ if (EncoderFallback.MaxCharCount > 1)
+ byteCount *= EncoderFallback.MaxCharCount;
+
+ // 1 to 1 for most characters. Only surrogates with fallbacks have less.
+
+ if (byteCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GetByteCountOverflow);
+ return (int)byteCount;
+ }
+
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(byteCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Just return length, SBCS stay the same length because they don't map to surrogate
+ long charCount = (long)byteCount;
+
+ // 1 to 1 for most characters. Only surrogates with fallbacks have less, unknown fallbacks could be longer.
+ if (DecoderFallback.MaxCharCount > 1)
+ charCount *= DecoderFallback.MaxCharCount;
+
+ if (charCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(byteCount), SR.ArgumentOutOfRange_GetCharCountOverflow);
+
+ return (int)charCount;
+ }
+
+ // True if and only if the encoding only uses single byte code points. (Ie, ASCII, 1252, etc)
+
+ public override bool IsSingleByte
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public override Decoder GetDecoder()
+ {
+ return new DecoderNLS(this);
+ }
+
+
+ public override Encoder GetEncoder()
+ {
+ return new EncoderNLS(this);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/Decoder.cs b/src/System.Private.CoreLib/shared/System/Text/Decoder.cs
new file mode 100644
index 0000000000..b4a7575ba6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/Decoder.cs
@@ -0,0 +1,351 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ // A Decoder is used to decode a sequence of blocks of bytes into a
+ // sequence of blocks of characters. Following instantiation of a decoder,
+ // sequential blocks of bytes are converted into blocks of characters through
+ // calls to the GetChars method. The decoder maintains state between the
+ // conversions, allowing it to correctly decode byte sequences that span
+ // adjacent blocks.
+ //
+ // Instances of specific implementations of the Decoder abstract base
+ // class are typically obtained through calls to the GetDecoder method
+ // of Encoding objects.
+ //
+ public abstract class Decoder
+ {
+ internal DecoderFallback _fallback = null;
+
+ internal DecoderFallbackBuffer _fallbackBuffer = null;
+
+ protected Decoder()
+ {
+ // We don't call default reset because default reset probably isn't good if we aren't initialized.
+ }
+
+ public DecoderFallback Fallback
+ {
+ get
+ {
+ return _fallback;
+ }
+
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ // Can't change fallback if buffer is wrong
+ if (_fallbackBuffer != null && _fallbackBuffer.Remaining > 0)
+ throw new ArgumentException(
+ SR.Argument_FallbackBufferNotEmpty, nameof(value));
+
+ _fallback = value;
+ _fallbackBuffer = null;
+ }
+ }
+
+ // Note: we don't test for threading here because async access to Encoders and Decoders
+ // doesn't work anyway.
+ public DecoderFallbackBuffer FallbackBuffer
+ {
+ get
+ {
+ if (_fallbackBuffer == null)
+ {
+ if (_fallback != null)
+ _fallbackBuffer = _fallback.CreateFallbackBuffer();
+ else
+ _fallbackBuffer = DecoderFallback.ReplacementFallback.CreateFallbackBuffer();
+ }
+
+ return _fallbackBuffer;
+ }
+ }
+
+ internal bool InternalHasFallbackBuffer
+ {
+ get
+ {
+ return _fallbackBuffer != null;
+ }
+ }
+
+ // Reset the Decoder
+ //
+ // Normally if we call GetChars() and an error is thrown we don't change the state of the Decoder. This
+ // would allow the caller to correct the error condition and try again (such as if they need a bigger buffer.)
+ //
+ // If the caller doesn't want to try again after GetChars() throws an error, then they need to call Reset().
+ //
+ // Virtual implementation has to call GetChars with flush and a big enough buffer to clear a 0 byte string
+ // We avoid GetMaxCharCount() because a) we can't call the base encoder and b) it might be really big.
+ public virtual void Reset()
+ {
+ byte[] byteTemp = Array.Empty<byte>();
+ char[] charTemp = new char[GetCharCount(byteTemp, 0, 0, true)];
+ GetChars(byteTemp, 0, 0, charTemp, 0, true);
+ _fallbackBuffer?.Reset();
+ }
+
+ // Returns the number of characters the next call to GetChars will
+ // produce if presented with the given range of bytes. The returned value
+ // takes into account the state in which the decoder was left following the
+ // last call to GetChars. The state of the decoder is not affected
+ // by a call to this method.
+ //
+ public abstract int GetCharCount(byte[] bytes, int index, int count);
+
+ public virtual int GetCharCount(byte[] bytes, int index, int count, bool flush)
+ {
+ return GetCharCount(bytes, index, count);
+ }
+
+ // We expect this to be the workhorse for NLS Encodings, but for existing
+ // ones we need a working (if slow) default implementation)
+ [CLSCompliant(false)]
+ public virtual unsafe int GetCharCount(byte* bytes, int count, bool flush)
+ {
+ // Validate input parameters
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ byte[] arrbyte = new byte[count];
+ int index;
+
+ for (index = 0; index < count; index++)
+ arrbyte[index] = bytes[index];
+
+ return GetCharCount(arrbyte, 0, count);
+ }
+
+ public virtual unsafe int GetCharCount(ReadOnlySpan<byte> bytes, bool flush)
+ {
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ {
+ return GetCharCount(bytesPtr, bytes.Length, flush);
+ }
+ }
+
+ // Decodes a range of bytes in a byte array into a range of characters
+ // in a character array. The method decodes byteCount bytes from
+ // bytes starting at index byteIndex, storing the resulting
+ // characters in chars starting at index charIndex. The
+ // decoding takes into account the state in which the decoder was left
+ // following the last call to this method.
+ //
+ // An exception occurs if the character array is not large enough to
+ // hold the complete decoding of the bytes. The GetCharCount method
+ // can be used to determine the exact number of characters that will be
+ // produced for a given range of bytes. Alternatively, the
+ // GetMaxCharCount method of the Encoding that produced this
+ // decoder can be used to determine the maximum number of characters that
+ // will be produced for a given number of bytes, regardless of the actual
+ // byte values.
+ //
+ public abstract int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex);
+
+ public virtual int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex, bool flush)
+ {
+ return GetChars(bytes, byteIndex, byteCount, chars, charIndex);
+ }
+
+ // We expect this to be the workhorse for NLS Encodings, but for existing
+ // ones we need a working (if slow) default implementation)
+ //
+ // WARNING WARNING WARNING
+ //
+ // WARNING: If this breaks it could be a security threat. Obviously we
+ // call this internally, so you need to make sure that your pointers, counts
+ // and indexes are correct when you call this method.
+ //
+ // In addition, we have internal code, which will be marked as "safe" calling
+ // this code. However this code is dependent upon the implementation of an
+ // external GetChars() method, which could be overridden by a third party and
+ // the results of which cannot be guaranteed. We use that result to copy
+ // the char[] to our char* output buffer. If the result count was wrong, we
+ // could easily overflow our output buffer. Therefore we do an extra test
+ // when we copy the buffer so that we don't overflow charCount either.
+ [CLSCompliant(false)]
+ public virtual unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, bool flush)
+ {
+ // Validate input parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException(chars == null ? nameof(chars) : nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ if (byteCount < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((byteCount < 0 ? nameof(byteCount) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Get the byte array to convert
+ byte[] arrByte = new byte[byteCount];
+
+ int index;
+ for (index = 0; index < byteCount; index++)
+ arrByte[index] = bytes[index];
+
+ // Get the char array to fill
+ char[] arrChar = new char[charCount];
+
+ // Do the work
+ int result = GetChars(arrByte, 0, byteCount, arrChar, 0, flush);
+
+ Debug.Assert(result <= charCount, "Returned more chars than we have space for");
+
+ // Copy the char array
+ // WARNING: We MUST make sure that we don't copy too many chars. We can't
+ // rely on result because it could be a 3rd party implementation. We need
+ // to make sure we never copy more than charCount chars no matter the value
+ // of result
+ if (result < charCount)
+ charCount = result;
+
+ // We check both result and charCount so that we don't accidentally overrun
+ // our pointer buffer just because of an issue in GetChars
+ for (index = 0; index < charCount; index++)
+ chars[index] = arrChar[index];
+
+ return charCount;
+ }
+
+ public virtual unsafe int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool flush)
+ {
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars))
+ {
+ return GetChars(bytesPtr, bytes.Length, charsPtr, chars.Length, flush);
+ }
+ }
+
+ // This method is used when the output buffer might not be large enough.
+ // It will decode until it runs out of bytes, and then it will return
+ // true if it the entire input was converted. In either case it
+ // will also return the number of converted bytes and output characters used.
+ // It will only throw a buffer overflow exception if the entire lenght of chars[] is
+ // too small to store the next char. (like 0 or maybe 1 or 4 for some encodings)
+ // We're done processing this buffer only if completed returns true.
+ //
+ // Might consider checking Max...Count to avoid the extra counting step.
+ //
+ // Note that if all of the input bytes are not consumed, then we'll do a /2, which means
+ // that its likely that we didn't consume as many bytes as we could have. For some
+ // applications this could be slow. (Like trying to exactly fill an output buffer from a bigger stream)
+ public virtual void Convert(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex, int charCount, bool flush,
+ out int bytesUsed, out int charsUsed, out bool completed)
+ {
+ // Validate parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException((bytes == null ? nameof(bytes) : nameof(chars)),
+ SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? nameof(byteIndex) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? nameof(charIndex) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException(nameof(bytes),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException(nameof(chars),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ bytesUsed = byteCount;
+
+ // Its easy to do if it won't overrun our buffer.
+ while (bytesUsed > 0)
+ {
+ if (GetCharCount(bytes, byteIndex, bytesUsed, flush) <= charCount)
+ {
+ charsUsed = GetChars(bytes, byteIndex, bytesUsed, chars, charIndex, flush);
+ completed = (bytesUsed == byteCount &&
+ (_fallbackBuffer == null || _fallbackBuffer.Remaining == 0));
+ return;
+ }
+
+ // Try again with 1/2 the count, won't flush then 'cause won't read it all
+ flush = false;
+ bytesUsed /= 2;
+ }
+
+ // Oops, we didn't have anything, we'll have to throw an overflow
+ throw new ArgumentException(SR.Argument_ConversionOverflow);
+ }
+
+ // This is the version that uses *.
+ // We're done processing this buffer only if completed returns true.
+ //
+ // Might consider checking Max...Count to avoid the extra counting step.
+ //
+ // Note that if all of the input bytes are not consumed, then we'll do a /2, which means
+ // that its likely that we didn't consume as many bytes as we could have. For some
+ // applications this could be slow. (Like trying to exactly fill an output buffer from a bigger stream)
+ [CLSCompliant(false)]
+ public virtual unsafe void Convert(byte* bytes, int byteCount,
+ char* chars, int charCount, bool flush,
+ out int bytesUsed, out int charsUsed, out bool completed)
+ {
+ // Validate input parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException(chars == null ? nameof(chars) : nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ if (byteCount < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((byteCount < 0 ? nameof(byteCount) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Get ready to do it
+ bytesUsed = byteCount;
+
+ // Its easy to do if it won't overrun our buffer.
+ while (bytesUsed > 0)
+ {
+ if (GetCharCount(bytes, bytesUsed, flush) <= charCount)
+ {
+ charsUsed = GetChars(bytes, bytesUsed, chars, charCount, flush);
+ completed = (bytesUsed == byteCount &&
+ (_fallbackBuffer == null || _fallbackBuffer.Remaining == 0));
+ return;
+ }
+
+ // Try again with 1/2 the count, won't flush then 'cause won't read it all
+ flush = false;
+ bytesUsed /= 2;
+ }
+
+ // Oops, we didn't have anything, we'll have to throw an overflow
+ throw new ArgumentException(SR.Argument_ConversionOverflow);
+ }
+
+ public virtual unsafe void Convert(ReadOnlySpan<byte> bytes, Span<char> chars, bool flush, out int bytesUsed, out int charsUsed, out bool completed)
+ {
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars))
+ {
+ Convert(bytesPtr, bytes.Length, charsPtr, chars.Length, flush, out bytesUsed, out charsUsed, out completed);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/DecoderBestFitFallback.cs b/src/System.Private.CoreLib/shared/System/Text/DecoderBestFitFallback.cs
new file mode 100644
index 0000000000..30c817c91a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/DecoderBestFitFallback.cs
@@ -0,0 +1,242 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// This is used internally to create best fit behavior as per the original windows best fit behavior.
+//
+
+using System.Diagnostics;
+using System.Threading;
+
+namespace System.Text
+{
+ internal sealed class InternalDecoderBestFitFallback : DecoderFallback
+ {
+ // Our variables
+ internal Encoding _encoding = null;
+ internal char[] _arrayBestFit = null;
+ internal char _cReplacement = '?';
+
+ internal InternalDecoderBestFitFallback(Encoding encoding)
+ {
+ // Need to load our replacement characters table.
+ _encoding = encoding;
+ }
+
+ public override DecoderFallbackBuffer CreateFallbackBuffer()
+ {
+ return new InternalDecoderBestFitFallbackBuffer(this);
+ }
+
+ // Maximum number of characters that this instance of this fallback could return
+ public override int MaxCharCount
+ {
+ get
+ {
+ return 1;
+ }
+ }
+
+ public override bool Equals(Object value)
+ {
+ InternalDecoderBestFitFallback that = value as InternalDecoderBestFitFallback;
+ if (that != null)
+ {
+ return (_encoding.CodePage == that._encoding.CodePage);
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return _encoding.CodePage;
+ }
+ }
+
+ internal sealed class InternalDecoderBestFitFallbackBuffer : DecoderFallbackBuffer
+ {
+ // Our variables
+ private char _cBestFit = '\0';
+ private int _iCount = -1;
+ private int _iSize;
+ private InternalDecoderBestFitFallback _oFallback;
+
+ // Private object for locking instead of locking on a public type for SQL reliability work.
+ private static Object s_InternalSyncObject;
+ private static Object InternalSyncObject
+ {
+ get
+ {
+ if (s_InternalSyncObject == null)
+ {
+ Object o = new Object();
+ Interlocked.CompareExchange<Object>(ref s_InternalSyncObject, o, null);
+ }
+ return s_InternalSyncObject;
+ }
+ }
+
+ // Constructor
+ public InternalDecoderBestFitFallbackBuffer(InternalDecoderBestFitFallback fallback)
+ {
+ _oFallback = fallback;
+
+ if (_oFallback._arrayBestFit == null)
+ {
+ // Lock so we don't confuse ourselves.
+ lock (InternalSyncObject)
+ {
+ // Double check before we do it again.
+ if (_oFallback._arrayBestFit == null)
+ _oFallback._arrayBestFit = fallback._encoding.GetBestFitBytesToUnicodeData();
+ }
+ }
+ }
+
+ // Fallback methods
+ public override bool Fallback(byte[] bytesUnknown, int index)
+ {
+ // We expect no previous fallback in our buffer
+ Debug.Assert(_iCount < 1, "[DecoderReplacementFallbackBuffer.Fallback] Calling fallback without a previously empty buffer");
+
+ _cBestFit = TryBestFit(bytesUnknown);
+ if (_cBestFit == '\0')
+ _cBestFit = _oFallback._cReplacement;
+
+ _iCount = _iSize = 1;
+
+ return true;
+ }
+
+ // Default version is overridden in DecoderReplacementFallback.cs
+ public override char GetNextChar()
+ {
+ // We want it to get < 0 because == 0 means that the current/last character is a fallback
+ // and we need to detect recursion. We could have a flag but we already have this counter.
+ _iCount--;
+
+ // Do we have anything left? 0 is now last fallback char, negative is nothing left
+ if (_iCount < 0)
+ return '\0';
+
+ // Need to get it out of the buffer.
+ // Make sure it didn't wrap from the fast count-- path
+ if (_iCount == int.MaxValue)
+ {
+ _iCount = -1;
+ return '\0';
+ }
+
+ // Return the best fit character
+ return _cBestFit;
+ }
+
+ public override bool MovePrevious()
+ {
+ // Exception fallback doesn't have anywhere to back up to.
+ if (_iCount >= 0)
+ _iCount++;
+
+ // Return true if we could do it.
+ return (_iCount >= 0 && _iCount <= _iSize);
+ }
+
+ // How many characters left to output?
+ public override int Remaining
+ {
+ get
+ {
+ return (_iCount > 0) ? _iCount : 0;
+ }
+ }
+
+ // Clear the buffer
+ public override unsafe void Reset()
+ {
+ _iCount = -1;
+ byteStart = null;
+ }
+
+ // This version just counts the fallback and doesn't actually copy anything.
+ internal unsafe override int InternalFallback(byte[] bytes, byte* pBytes)
+ // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
+ // array, and we might need the index, hence the byte*
+ {
+ // return our replacement string Length (always 1 for InternalDecoderBestFitFallback, either
+ // a best fit char or ?
+ return 1;
+ }
+
+ // private helper methods
+ private char TryBestFit(byte[] bytesCheck)
+ {
+ // Need to figure out our best fit character, low is beginning of array, high is 1 AFTER end of array
+ int lowBound = 0;
+ int highBound = _oFallback._arrayBestFit.Length;
+ int index;
+ char cCheck;
+
+ // Check trivial case first (no best fit)
+ if (highBound == 0)
+ return '\0';
+
+ // If our array is too small or too big we can't check
+ if (bytesCheck.Length == 0 || bytesCheck.Length > 2)
+ return '\0';
+
+ if (bytesCheck.Length == 1)
+ cCheck = unchecked((char)bytesCheck[0]);
+ else
+ cCheck = unchecked((char)((bytesCheck[0] << 8) + bytesCheck[1]));
+
+ // Check trivial out of range case
+ if (cCheck < _oFallback._arrayBestFit[0] || cCheck > _oFallback._arrayBestFit[highBound - 2])
+ return '\0';
+
+ // Binary search the array
+ int iDiff;
+ while ((iDiff = (highBound - lowBound)) > 6)
+ {
+ // Look in the middle, which is complicated by the fact that we have 2 #s for each pair,
+ // so we don't want index to be odd because it must be word aligned.
+ // Also note that index can never == highBound (because diff is rounded down)
+ index = ((iDiff / 2) + lowBound) & 0xFFFE;
+
+ char cTest = _oFallback._arrayBestFit[index];
+ if (cTest == cCheck)
+ {
+ // We found it
+ Debug.Assert(index + 1 < _oFallback._arrayBestFit.Length,
+ "[InternalDecoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
+ return _oFallback._arrayBestFit[index + 1];
+ }
+ else if (cTest < cCheck)
+ {
+ // We weren't high enough
+ lowBound = index;
+ }
+ else
+ {
+ // We weren't low enough
+ highBound = index;
+ }
+ }
+
+ for (index = lowBound; index < highBound; index += 2)
+ {
+ if (_oFallback._arrayBestFit[index] == cCheck)
+ {
+ // We found it
+ Debug.Assert(index + 1 < _oFallback._arrayBestFit.Length,
+ "[InternalDecoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
+ return _oFallback._arrayBestFit[index + 1];
+ }
+ }
+
+ // Char wasn't in our table
+ return '\0';
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Text/DecoderExceptionFallback.cs b/src/System.Private.CoreLib/shared/System/Text/DecoderExceptionFallback.cs
new file mode 100644
index 0000000000..8bfc1f32d3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/DecoderExceptionFallback.cs
@@ -0,0 +1,155 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System.Text
+{
+ public sealed class DecoderExceptionFallback : DecoderFallback
+ {
+ // Construction
+ public DecoderExceptionFallback()
+ {
+ }
+
+ public override DecoderFallbackBuffer CreateFallbackBuffer()
+ {
+ return new DecoderExceptionFallbackBuffer();
+ }
+
+ // Maximum number of characters that this instance of this fallback could return
+ public override int MaxCharCount
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ public override bool Equals(Object value)
+ {
+ DecoderExceptionFallback that = value as DecoderExceptionFallback;
+ if (that != null)
+ {
+ return (true);
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return 879;
+ }
+ }
+
+
+ public sealed class DecoderExceptionFallbackBuffer : DecoderFallbackBuffer
+ {
+ public override bool Fallback(byte[] bytesUnknown, int index)
+ {
+ Throw(bytesUnknown, index);
+ return true;
+ }
+
+ public override char GetNextChar()
+ {
+ return (char)0;
+ }
+
+ public override bool MovePrevious()
+ {
+ // Exception fallback doesn't have anywhere to back up to.
+ return false;
+ }
+
+ // Exceptions are always empty
+ public override int Remaining
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ private void Throw(byte[] bytesUnknown, int index)
+ {
+ // Create a string representation of our bytes.
+ StringBuilder strBytes = new StringBuilder(bytesUnknown.Length * 3);
+
+ int i;
+ for (i = 0; i < bytesUnknown.Length && i < 20; i++)
+ {
+ strBytes.Append('[');
+ strBytes.Append(bytesUnknown[i].ToString("X2", CultureInfo.InvariantCulture));
+ strBytes.Append(']');
+ }
+
+ // In case the string's really long
+ if (i == 20)
+ strBytes.Append(" ...");
+
+ // Known index
+ throw new DecoderFallbackException(
+ SR.Format(SR.Argument_InvalidCodePageBytesIndex,
+ strBytes, index), bytesUnknown, index);
+ }
+ }
+
+ // Exception for decoding unknown byte sequences.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class DecoderFallbackException : ArgumentException
+ {
+ private byte[] _bytesUnknown = null;
+ private int _index = 0;
+
+ public DecoderFallbackException()
+ : base(SR.Arg_ArgumentException)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ public DecoderFallbackException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ public DecoderFallbackException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ public DecoderFallbackException(String message, byte[] bytesUnknown, int index)
+ : base(message)
+ {
+ _bytesUnknown = bytesUnknown;
+ _index = index;
+ }
+
+ private DecoderFallbackException(SerializationInfo serializationInfo, StreamingContext streamingContext)
+ : base(serializationInfo, streamingContext)
+ {
+ }
+
+ public byte[] BytesUnknown
+ {
+ get
+ {
+ return (_bytesUnknown);
+ }
+ }
+
+ public int Index
+ {
+ get
+ {
+ return _index;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/DecoderFallback.cs b/src/System.Private.CoreLib/shared/System/Text/DecoderFallback.cs
new file mode 100644
index 0000000000..11b9539b5c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/DecoderFallback.cs
@@ -0,0 +1,216 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Threading;
+
+namespace System.Text
+{
+ public abstract class DecoderFallback
+ {
+ private static DecoderFallback s_replacementFallback; // Default fallback, uses no best fit & "?"
+ private static DecoderFallback s_exceptionFallback;
+
+ public static DecoderFallback ReplacementFallback =>
+ s_replacementFallback ?? Interlocked.CompareExchange(ref s_replacementFallback, new DecoderReplacementFallback(), null) ?? s_replacementFallback;
+
+
+ public static DecoderFallback ExceptionFallback =>
+ s_exceptionFallback ?? Interlocked.CompareExchange<DecoderFallback>(ref s_exceptionFallback, new DecoderExceptionFallback(), null) ?? s_exceptionFallback;
+
+ // Fallback
+ //
+ // Return the appropriate unicode string alternative to the character that need to fall back.
+ // Most implementations will be:
+ // return new MyCustomDecoderFallbackBuffer(this);
+
+ public abstract DecoderFallbackBuffer CreateFallbackBuffer();
+
+ // Maximum number of characters that this instance of this fallback could return
+
+ public abstract int MaxCharCount { get; }
+ }
+
+
+ public abstract class DecoderFallbackBuffer
+ {
+ // Most implementations will probably need an implementation-specific constructor
+
+ // internal methods that cannot be overridden that let us do our fallback thing
+ // These wrap the internal methods so that we can check for people doing stuff that's incorrect
+
+ public abstract bool Fallback(byte[] bytesUnknown, int index);
+
+ // Get next character
+
+ public abstract char GetNextChar();
+
+ // Back up a character
+
+ public abstract bool MovePrevious();
+
+ // How many chars left in this fallback?
+
+ public abstract int Remaining { get; }
+
+ // Clear the buffer
+
+ public virtual void Reset()
+ {
+ while (GetNextChar() != (char)0) ;
+ }
+
+ // Internal items to help us figure out what we're doing as far as error messages, etc.
+ // These help us with our performance and messages internally
+ internal unsafe byte* byteStart;
+ internal unsafe char* charEnd;
+
+ // Internal Reset
+ internal unsafe void InternalReset()
+ {
+ byteStart = null;
+ Reset();
+ }
+
+ // Set the above values
+ // This can't be part of the constructor because DecoderFallbacks would have to know how to implement these.
+ internal unsafe void InternalInitialize(byte* byteStart, char* charEnd)
+ {
+ this.byteStart = byteStart;
+ this.charEnd = charEnd;
+ }
+
+ // Fallback the current byte by sticking it into the remaining char buffer.
+ // This can only be called by our encodings (other have to use the public fallback methods), so
+ // we can use our DecoderNLS here too (except we don't).
+ // Returns true if we are successful, false if we can't fallback the character (no buffer space)
+ // So caller needs to throw buffer space if return false.
+ // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
+ // array, and we might need the index, hence the byte*
+ // Don't touch ref chars unless we succeed
+ internal unsafe virtual bool InternalFallback(byte[] bytes, byte* pBytes, ref char* chars)
+ {
+ Debug.Assert(byteStart != null, "[DecoderFallback.InternalFallback]Used InternalFallback without calling InternalInitialize");
+
+ // See if there's a fallback character and we have an output buffer then copy our string.
+ if (this.Fallback(bytes, (int)(pBytes - byteStart - bytes.Length)))
+ {
+ // Copy the chars to our output
+ char ch;
+ char* charTemp = chars;
+ bool bHighSurrogate = false;
+ while ((ch = GetNextChar()) != 0)
+ {
+ // Make sure no mixed up surrogates
+ if (Char.IsSurrogate(ch))
+ {
+ if (Char.IsHighSurrogate(ch))
+ {
+ // High Surrogate
+ if (bHighSurrogate)
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
+ bHighSurrogate = true;
+ }
+ else
+ {
+ // Low surrogate
+ if (bHighSurrogate == false)
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
+ bHighSurrogate = false;
+ }
+ }
+
+ if (charTemp >= charEnd)
+ {
+ // No buffer space
+ return false;
+ }
+
+ *(charTemp++) = ch;
+ }
+
+ // Need to make sure that bHighSurrogate isn't true
+ if (bHighSurrogate)
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
+
+ // Now we aren't going to be false, so its OK to update chars
+ chars = charTemp;
+ }
+
+ return true;
+ }
+
+ // This version just counts the fallback and doesn't actually copy anything.
+ internal unsafe virtual int InternalFallback(byte[] bytes, byte* pBytes)
+ // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
+ // array, and we might need the index, hence the byte*
+ {
+ Debug.Assert(byteStart != null, "[DecoderFallback.InternalFallback]Used InternalFallback without calling InternalInitialize");
+
+ // See if there's a fallback character and we have an output buffer then copy our string.
+ if (this.Fallback(bytes, (int)(pBytes - byteStart - bytes.Length)))
+ {
+ int count = 0;
+
+ char ch;
+ bool bHighSurrogate = false;
+ while ((ch = GetNextChar()) != 0)
+ {
+ // Make sure no mixed up surrogates
+ if (Char.IsSurrogate(ch))
+ {
+ if (Char.IsHighSurrogate(ch))
+ {
+ // High Surrogate
+ if (bHighSurrogate)
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
+ bHighSurrogate = true;
+ }
+ else
+ {
+ // Low surrogate
+ if (bHighSurrogate == false)
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
+ bHighSurrogate = false;
+ }
+ }
+
+ count++;
+ }
+
+ // Need to make sure that bHighSurrogate isn't true
+ if (bHighSurrogate)
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
+
+ return count;
+ }
+
+ // If no fallback return 0
+ return 0;
+ }
+
+ // private helper methods
+ internal void ThrowLastBytesRecursive(byte[] bytesUnknown)
+ {
+ // Create a string representation of our bytes.
+ StringBuilder strBytes = new StringBuilder(bytesUnknown.Length * 3);
+ int i;
+ for (i = 0; i < bytesUnknown.Length && i < 20; i++)
+ {
+ if (strBytes.Length > 0)
+ strBytes.Append(' ');
+ strBytes.AppendFormat(CultureInfo.InvariantCulture, "\\x{0:X2}", bytesUnknown[i]);
+ }
+ // In case the string's really long
+ if (i == 20)
+ strBytes.Append(" ...");
+
+ // Throw it, using our complete bytes
+ throw new ArgumentException(
+ SR.Format(SR.Argument_RecursiveFallbackBytes,
+ strBytes.ToString()), nameof(bytesUnknown));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/DecoderNLS.cs b/src/System.Private.CoreLib/shared/System/Text/DecoderNLS.cs
new file mode 100644
index 0000000000..ee88c22f9d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/DecoderNLS.cs
@@ -0,0 +1,242 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+using System.Text;
+using System;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ // A Decoder is used to decode a sequence of blocks of bytes into a
+ // sequence of blocks of characters. Following instantiation of a decoder,
+ // sequential blocks of bytes are converted into blocks of characters through
+ // calls to the GetChars method. The decoder maintains state between the
+ // conversions, allowing it to correctly decode byte sequences that span
+ // adjacent blocks.
+ //
+ // Instances of specific implementations of the Decoder abstract base
+ // class are typically obtained through calls to the GetDecoder method
+ // of Encoding objects.
+
+ internal class DecoderNLS : Decoder
+ {
+ // Remember our encoding
+ private Encoding _encoding;
+ private bool _mustFlush;
+ internal bool _throwOnOverflow;
+ internal int _bytesUsed;
+
+ internal DecoderNLS(Encoding encoding)
+ {
+ _encoding = encoding;
+ _fallback = this._encoding.DecoderFallback;
+ this.Reset();
+ }
+
+ // This is used by our child deserializers
+ internal DecoderNLS()
+ {
+ _encoding = null;
+ this.Reset();
+ }
+
+ public override void Reset()
+ {
+ _fallbackBuffer?.Reset();
+ }
+
+ public override unsafe int GetCharCount(byte[] bytes, int index, int count)
+ {
+ return GetCharCount(bytes, index, count, false);
+ }
+
+ public override unsafe int GetCharCount(byte[] bytes, int index, int count, bool flush)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException(nameof(bytes),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Just call pointer version
+ fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ return GetCharCount(pBytes + index, count, flush);
+ }
+
+ public unsafe override int GetCharCount(byte* bytes, int count, bool flush)
+ {
+ // Validate parameters
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Remember the flush
+ _mustFlush = flush;
+ _throwOnOverflow = true;
+
+ // By default just call the encoding version, no flush by default
+ return _encoding.GetCharCount(bytes, count, this);
+ }
+
+ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex)
+ {
+ return GetChars(bytes, byteIndex, byteCount, chars, charIndex, false);
+ }
+
+ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex, bool flush)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? nameof(bytes) : nameof(chars),
+ SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? nameof(byteIndex) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException(nameof(bytes),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (charIndex < 0 || charIndex > chars.Length)
+ throw new ArgumentOutOfRangeException(nameof(charIndex),
+ SR.ArgumentOutOfRange_Index);
+
+ int charCount = chars.Length - charIndex;
+
+ // Just call pointer version
+ fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ // Remember that charCount is # to decode, not size of array
+ return GetChars(pBytes + byteIndex, byteCount,
+ pChars + charIndex, charCount, flush);
+ }
+
+ public unsafe override int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, bool flush)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? nameof(chars) : nameof(bytes)),
+ SR.ArgumentNull_Array);
+
+ if (byteCount < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((byteCount < 0 ? nameof(byteCount) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Remember our flush
+ _mustFlush = flush;
+ _throwOnOverflow = true;
+
+ // By default just call the encodings version
+ return _encoding.GetChars(bytes, byteCount, chars, charCount, this);
+ }
+
+ // This method is used when the output buffer might not be big enough.
+ // Just call the pointer version. (This gets chars)
+ public override unsafe void Convert(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex, int charCount, bool flush,
+ out int bytesUsed, out int charsUsed, out bool completed)
+ {
+ // Validate parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException((bytes == null ? nameof(bytes) : nameof(chars)),
+ SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? nameof(byteIndex) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? nameof(charIndex) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException(nameof(bytes),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException(nameof(chars),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Just call the pointer version (public overrides can't do this)
+ fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ {
+ fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ {
+ Convert(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, flush,
+ out bytesUsed, out charsUsed, out completed);
+ }
+ }
+ }
+
+ // This is the version that used pointers. We call the base encoding worker function
+ // after setting our appropriate internal variables. This is getting chars
+ public unsafe override void Convert(byte* bytes, int byteCount,
+ char* chars, int charCount, bool flush,
+ out int bytesUsed, out int charsUsed, out bool completed)
+ {
+ // Validate input parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException(chars == null ? nameof(chars) : nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ if (byteCount < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((byteCount < 0 ? nameof(byteCount) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // We don't want to throw
+ _mustFlush = flush;
+ _throwOnOverflow = false;
+ _bytesUsed = 0;
+
+ // Do conversion
+ charsUsed = _encoding.GetChars(bytes, byteCount, chars, charCount, this);
+ bytesUsed = _bytesUsed;
+
+ // Its completed if they've used what they wanted AND if they didn't want flush or if we are flushed
+ completed = (bytesUsed == byteCount) && (!flush || !this.HasState) &&
+ (_fallbackBuffer == null || _fallbackBuffer.Remaining == 0);
+
+ // Our data thingy are now full, we can return
+ }
+
+ public bool MustFlush
+ {
+ get
+ {
+ return _mustFlush;
+ }
+ }
+
+ // Anything left in our decoder?
+ internal virtual bool HasState
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ // Allow encoding to clear our must flush instead of throwing (in ThrowCharsOverflow)
+ internal void ClearMustFlush()
+ {
+ _mustFlush = false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/DecoderReplacementFallback.cs b/src/System.Private.CoreLib/shared/System/Text/DecoderReplacementFallback.cs
new file mode 100644
index 0000000000..422b80bb2f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/DecoderReplacementFallback.cs
@@ -0,0 +1,204 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Text
+{
+ public sealed class DecoderReplacementFallback : DecoderFallback
+ {
+ // Our variables
+ private String _strDefault;
+
+ // Construction. Default replacement fallback uses no best fit and ? replacement string
+ public DecoderReplacementFallback() : this("?")
+ {
+ }
+
+ public DecoderReplacementFallback(String replacement)
+ {
+ if (replacement == null)
+ throw new ArgumentNullException(nameof(replacement));
+
+ // Make sure it doesn't have bad surrogate pairs
+ bool bFoundHigh = false;
+ for (int i = 0; i < replacement.Length; i++)
+ {
+ // Found a surrogate?
+ if (Char.IsSurrogate(replacement, i))
+ {
+ // High or Low?
+ if (Char.IsHighSurrogate(replacement, i))
+ {
+ // if already had a high one, stop
+ if (bFoundHigh)
+ break; // break & throw at the bFoundHIgh below
+ bFoundHigh = true;
+ }
+ else
+ {
+ // Low, did we have a high?
+ if (!bFoundHigh)
+ {
+ // Didn't have one, make if fail when we stop
+ bFoundHigh = true;
+ break;
+ }
+
+ // Clear flag
+ bFoundHigh = false;
+ }
+ }
+ // If last was high we're in trouble (not surrogate so not low surrogate, so break)
+ else if (bFoundHigh)
+ break;
+ }
+ if (bFoundHigh)
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidCharSequenceNoIndex, nameof(replacement)));
+
+ _strDefault = replacement;
+ }
+
+ public String DefaultString
+ {
+ get
+ {
+ return _strDefault;
+ }
+ }
+
+ public override DecoderFallbackBuffer CreateFallbackBuffer()
+ {
+ return new DecoderReplacementFallbackBuffer(this);
+ }
+
+ // Maximum number of characters that this instance of this fallback could return
+ public override int MaxCharCount
+ {
+ get
+ {
+ return _strDefault.Length;
+ }
+ }
+
+ public override bool Equals(Object value)
+ {
+ DecoderReplacementFallback that = value as DecoderReplacementFallback;
+ if (that != null)
+ {
+ return (_strDefault == that._strDefault);
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return _strDefault.GetHashCode();
+ }
+ }
+
+
+
+ public sealed class DecoderReplacementFallbackBuffer : DecoderFallbackBuffer
+ {
+ // Store our default string
+ private String _strDefault;
+ private int _fallbackCount = -1;
+ private int _fallbackIndex = -1;
+
+ // Construction
+ public DecoderReplacementFallbackBuffer(DecoderReplacementFallback fallback)
+ {
+ _strDefault = fallback.DefaultString;
+ }
+
+ // Fallback Methods
+ public override bool Fallback(byte[] bytesUnknown, int index)
+ {
+ // We expect no previous fallback in our buffer
+ // We can't call recursively but others might (note, we don't test on last char!!!)
+ if (_fallbackCount >= 1)
+ {
+ ThrowLastBytesRecursive(bytesUnknown);
+ }
+
+ // Go ahead and get our fallback
+ if (_strDefault.Length == 0)
+ return false;
+
+ _fallbackCount = _strDefault.Length;
+ _fallbackIndex = -1;
+
+ return true;
+ }
+
+ public override char GetNextChar()
+ {
+ // We want it to get < 0 because == 0 means that the current/last character is a fallback
+ // and we need to detect recursion. We could have a flag but we already have this counter.
+ _fallbackCount--;
+ _fallbackIndex++;
+
+ // Do we have anything left? 0 is now last fallback char, negative is nothing left
+ if (_fallbackCount < 0)
+ return '\0';
+
+ // Need to get it out of the buffer.
+ // Make sure it didn't wrap from the fast count-- path
+ if (_fallbackCount == int.MaxValue)
+ {
+ _fallbackCount = -1;
+ return '\0';
+ }
+
+ // Now make sure its in the expected range
+ Debug.Assert(_fallbackIndex < _strDefault.Length && _fallbackIndex >= 0,
+ "Index exceeds buffer range");
+
+ return _strDefault[_fallbackIndex];
+ }
+
+ public override bool MovePrevious()
+ {
+ // Back up one, only if we just processed the last character (or earlier)
+ if (_fallbackCount >= -1 && _fallbackIndex >= 0)
+ {
+ _fallbackIndex--;
+ _fallbackCount++;
+ return true;
+ }
+
+ // Return false 'cause we couldn't do it.
+ return false;
+ }
+
+ // How many characters left to output?
+ public override int Remaining
+ {
+ get
+ {
+ // Our count is 0 for 1 character left.
+ return (_fallbackCount < 0) ? 0 : _fallbackCount;
+ }
+ }
+
+ // Clear the buffer
+ public override unsafe void Reset()
+ {
+ _fallbackCount = -1;
+ _fallbackIndex = -1;
+ byteStart = null;
+ }
+
+ // This version just counts the fallback and doesn't actually copy anything.
+ internal unsafe override int InternalFallback(byte[] bytes, byte* pBytes)
+ // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
+ // array, and we might need the index, hence the byte*
+ {
+ // return our replacement string Length
+ return _strDefault.Length;
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Text/Encoder.cs b/src/System.Private.CoreLib/shared/System/Text/Encoder.cs
new file mode 100644
index 0000000000..df7d51203f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/Encoder.cs
@@ -0,0 +1,346 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ // An Encoder is used to encode a sequence of blocks of characters into
+ // a sequence of blocks of bytes. Following instantiation of an encoder,
+ // sequential blocks of characters are converted into blocks of bytes through
+ // calls to the GetBytes method. The encoder maintains state between the
+ // conversions, allowing it to correctly encode character sequences that span
+ // adjacent blocks.
+ //
+ // Instances of specific implementations of the Encoder abstract base
+ // class are typically obtained through calls to the GetEncoder method
+ // of Encoding objects.
+ //
+ public abstract class Encoder
+ {
+ internal EncoderFallback _fallback = null;
+
+ internal EncoderFallbackBuffer _fallbackBuffer = null;
+
+ protected Encoder()
+ {
+ // We don't call default reset because default reset probably isn't good if we aren't initialized.
+ }
+
+ public EncoderFallback Fallback
+ {
+ get
+ {
+ return _fallback;
+ }
+
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ // Can't change fallback if buffer is wrong
+ if (_fallbackBuffer != null && _fallbackBuffer.Remaining > 0)
+ throw new ArgumentException(
+ SR.Argument_FallbackBufferNotEmpty, nameof(value));
+
+ _fallback = value;
+ _fallbackBuffer = null;
+ }
+ }
+
+ // Note: we don't test for threading here because async access to Encoders and Decoders
+ // doesn't work anyway.
+ public EncoderFallbackBuffer FallbackBuffer
+ {
+ get
+ {
+ if (_fallbackBuffer == null)
+ {
+ if (_fallback != null)
+ _fallbackBuffer = _fallback.CreateFallbackBuffer();
+ else
+ _fallbackBuffer = EncoderFallback.ReplacementFallback.CreateFallbackBuffer();
+ }
+
+ return _fallbackBuffer;
+ }
+ }
+
+ internal bool InternalHasFallbackBuffer
+ {
+ get
+ {
+ return _fallbackBuffer != null;
+ }
+ }
+
+ // Reset the Encoder
+ //
+ // Normally if we call GetBytes() and an error is thrown we don't change the state of the encoder. This
+ // would allow the caller to correct the error condition and try again (such as if they need a bigger buffer.)
+ //
+ // If the caller doesn't want to try again after GetBytes() throws an error, then they need to call Reset().
+ //
+ // Virtual implementation has to call GetBytes with flush and a big enough buffer to clear a 0 char string
+ // We avoid GetMaxByteCount() because a) we can't call the base encoder and b) it might be really big.
+ public virtual void Reset()
+ {
+ char[] charTemp = { };
+ byte[] byteTemp = new byte[GetByteCount(charTemp, 0, 0, true)];
+ GetBytes(charTemp, 0, 0, byteTemp, 0, true);
+ if (_fallbackBuffer != null)
+ _fallbackBuffer.Reset();
+ }
+
+ // Returns the number of bytes the next call to GetBytes will
+ // produce if presented with the given range of characters and the given
+ // value of the flush parameter. The returned value takes into
+ // account the state in which the encoder was left following the last call
+ // to GetBytes. The state of the encoder is not affected by a call
+ // to this method.
+ //
+ public abstract int GetByteCount(char[] chars, int index, int count, bool flush);
+
+ // We expect this to be the workhorse for NLS encodings
+ // unfortunately for existing overrides, it has to call the [] version,
+ // which is really slow, so avoid this method if you might be calling external encodings.
+ [CLSCompliant(false)]
+ public virtual unsafe int GetByteCount(char* chars, int count, bool flush)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException(nameof(chars),
+ SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ char[] arrChar = new char[count];
+ int index;
+
+ for (index = 0; index < count; index++)
+ arrChar[index] = chars[index];
+
+ return GetByteCount(arrChar, 0, count, flush);
+ }
+
+ public virtual unsafe int GetByteCount(ReadOnlySpan<char> chars, bool flush)
+ {
+ fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars))
+ {
+ return GetByteCount(charsPtr, chars.Length, flush);
+ }
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. The method encodes charCount characters from
+ // chars starting at index charIndex, storing the resulting
+ // bytes in bytes starting at index byteIndex. The encoding
+ // takes into account the state in which the encoder was left following the
+ // last call to this method. The flush parameter indicates whether
+ // the encoder should flush any shift-states and partial characters at the
+ // end of the conversion. To ensure correct termination of a sequence of
+ // blocks of encoded bytes, the last call to GetBytes should specify
+ // a value of true for the flush parameter.
+ //
+ // An exception occurs if the byte array is not large enough to hold the
+ // complete encoding of the characters. The GetByteCount method can
+ // be used to determine the exact number of bytes that will be produced for
+ // a given range of characters. Alternatively, the GetMaxByteCount
+ // method of the Encoding that produced this encoder can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+ public abstract int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex, bool flush);
+
+ // We expect this to be the workhorse for NLS Encodings, but for existing
+ // ones we need a working (if slow) default implementation)
+ //
+ // WARNING WARNING WARNING
+ //
+ // WARNING: If this breaks it could be a security threat. Obviously we
+ // call this internally, so you need to make sure that your pointers, counts
+ // and indexes are correct when you call this method.
+ //
+ // In addition, we have internal code, which will be marked as "safe" calling
+ // this code. However this code is dependent upon the implementation of an
+ // external GetBytes() method, which could be overridden by a third party and
+ // the results of which cannot be guaranteed. We use that result to copy
+ // the byte[] to our byte* output buffer. If the result count was wrong, we
+ // could easily overflow our output buffer. Therefore we do an extra test
+ // when we copy the buffer so that we don't overflow byteCount either.
+ [CLSCompliant(false)]
+ public virtual unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, bool flush)
+ {
+ // Validate input parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? nameof(bytes) : nameof(chars),
+ SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? nameof(charCount) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Get the char array to convert
+ char[] arrChar = new char[charCount];
+
+ int index;
+ for (index = 0; index < charCount; index++)
+ arrChar[index] = chars[index];
+
+ // Get the byte array to fill
+ byte[] arrByte = new byte[byteCount];
+
+ // Do the work
+ int result = GetBytes(arrChar, 0, charCount, arrByte, 0, flush);
+
+ Debug.Assert(result <= byteCount, "Returned more bytes than we have space for");
+
+ // Copy the byte array
+ // WARNING: We MUST make sure that we don't copy too many bytes. We can't
+ // rely on result because it could be a 3rd party implementation. We need
+ // to make sure we never copy more than byteCount bytes no matter the value
+ // of result
+ if (result < byteCount)
+ byteCount = result;
+
+ // Don't copy too many bytes!
+ for (index = 0; index < byteCount; index++)
+ bytes[index] = arrByte[index];
+
+ return byteCount;
+ }
+
+ public virtual unsafe int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool flush)
+ {
+ fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars))
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ {
+ return GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length, flush);
+ }
+ }
+
+ // This method is used to avoid running out of output buffer space.
+ // It will encode until it runs out of chars, and then it will return
+ // true if it the entire input was converted. In either case it
+ // will also return the number of converted chars and output bytes used.
+ // It will only throw a buffer overflow exception if the entire lenght of bytes[] is
+ // too small to store the next byte. (like 0 or maybe 1 or 4 for some encodings)
+ // We're done processing this buffer only if completed returns true.
+ //
+ // Might consider checking Max...Count to avoid the extra counting step.
+ //
+ // Note that if all of the input chars are not consumed, then we'll do a /2, which means
+ // that its likely that we didn't consume as many chars as we could have. For some
+ // applications this could be slow. (Like trying to exactly fill an output buffer from a bigger stream)
+ public virtual void Convert(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex, int byteCount, bool flush,
+ out int charsUsed, out int bytesUsed, out bool completed)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? nameof(chars) : nameof(bytes)),
+ SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? nameof(charIndex) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? nameof(byteIndex) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException(nameof(chars),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException(nameof(bytes),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ charsUsed = charCount;
+
+ // Its easy to do if it won't overrun our buffer.
+ // Note: We don't want to call unsafe version because that might be an untrusted version
+ // which could be really unsafe and we don't want to mix it up.
+ while (charsUsed > 0)
+ {
+ if (GetByteCount(chars, charIndex, charsUsed, flush) <= byteCount)
+ {
+ bytesUsed = GetBytes(chars, charIndex, charsUsed, bytes, byteIndex, flush);
+ completed = (charsUsed == charCount &&
+ (_fallbackBuffer == null || _fallbackBuffer.Remaining == 0));
+ return;
+ }
+
+ // Try again with 1/2 the count, won't flush then 'cause won't read it all
+ flush = false;
+ charsUsed /= 2;
+ }
+
+ // Oops, we didn't have anything, we'll have to throw an overflow
+ throw new ArgumentException(SR.Argument_ConversionOverflow);
+ }
+
+ // Same thing, but using pointers
+ //
+ // Might consider checking Max...Count to avoid the extra counting step.
+ //
+ // Note that if all of the input chars are not consumed, then we'll do a /2, which means
+ // that its likely that we didn't consume as many chars as we could have. For some
+ // applications this could be slow. (Like trying to exactly fill an output buffer from a bigger stream)
+ [CLSCompliant(false)]
+ public virtual unsafe void Convert(char* chars, int charCount,
+ byte* bytes, int byteCount, bool flush,
+ out int charsUsed, out int bytesUsed, out bool completed)
+ {
+ // Validate input parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? nameof(bytes) : nameof(chars),
+ SR.ArgumentNull_Array);
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? nameof(charCount) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Get ready to do it
+ charsUsed = charCount;
+
+ // Its easy to do if it won't overrun our buffer.
+ while (charsUsed > 0)
+ {
+ if (GetByteCount(chars, charsUsed, flush) <= byteCount)
+ {
+ bytesUsed = GetBytes(chars, charsUsed, bytes, byteCount, flush);
+ completed = (charsUsed == charCount &&
+ (_fallbackBuffer == null || _fallbackBuffer.Remaining == 0));
+ return;
+ }
+
+ // Try again with 1/2 the count, won't flush then 'cause won't read it all
+ flush = false;
+ charsUsed /= 2;
+ }
+
+ // Oops, we didn't have anything, we'll have to throw an overflow
+ throw new ArgumentException(SR.Argument_ConversionOverflow);
+ }
+
+ public virtual unsafe void Convert(ReadOnlySpan<char> chars, Span<byte> bytes, bool flush, out int charsUsed, out int bytesUsed, out bool completed)
+ {
+ fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars))
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ {
+ Convert(charsPtr, chars.Length, bytesPtr, bytes.Length, flush, out charsUsed, out bytesUsed, out completed);
+ }
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Text/EncoderBestFitFallback.cs b/src/System.Private.CoreLib/shared/System/Text/EncoderBestFitFallback.cs
new file mode 100644
index 0000000000..7f3be2a7a6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/EncoderBestFitFallback.cs
@@ -0,0 +1,242 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// This is used internally to create best fit behavior as per the original windows best fit behavior.
+//
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Threading;
+
+namespace System.Text
+{
+ internal class InternalEncoderBestFitFallback : EncoderFallback
+ {
+ // Our variables
+ internal Encoding _encoding = null;
+ internal char[] _arrayBestFit = null;
+
+ internal InternalEncoderBestFitFallback(Encoding encoding)
+ {
+ // Need to load our replacement characters table.
+ _encoding = encoding;
+ }
+
+ public override EncoderFallbackBuffer CreateFallbackBuffer()
+ {
+ return new InternalEncoderBestFitFallbackBuffer(this);
+ }
+
+ // Maximum number of characters that this instance of this fallback could return
+ public override int MaxCharCount
+ {
+ get
+ {
+ return 1;
+ }
+ }
+
+ public override bool Equals(Object value)
+ {
+ InternalEncoderBestFitFallback that = value as InternalEncoderBestFitFallback;
+ if (that != null)
+ {
+ return (_encoding.CodePage == that._encoding.CodePage);
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return _encoding.CodePage;
+ }
+ }
+
+ internal sealed class InternalEncoderBestFitFallbackBuffer : EncoderFallbackBuffer
+ {
+ // Our variables
+ private char _cBestFit = '\0';
+ private InternalEncoderBestFitFallback _oFallback;
+ private int _iCount = -1;
+ private int _iSize;
+
+ // Private object for locking instead of locking on a public type for SQL reliability work.
+ private static Object s_InternalSyncObject;
+ private static Object InternalSyncObject
+ {
+ get
+ {
+ if (s_InternalSyncObject == null)
+ {
+ Object o = new Object();
+ Interlocked.CompareExchange<Object>(ref s_InternalSyncObject, o, null);
+ }
+ return s_InternalSyncObject;
+ }
+ }
+
+ // Constructor
+ public InternalEncoderBestFitFallbackBuffer(InternalEncoderBestFitFallback fallback)
+ {
+ _oFallback = fallback;
+
+ if (_oFallback._arrayBestFit == null)
+ {
+ // Lock so we don't confuse ourselves.
+ lock (InternalSyncObject)
+ {
+ // Double check before we do it again.
+ if (_oFallback._arrayBestFit == null)
+ _oFallback._arrayBestFit = fallback._encoding.GetBestFitUnicodeToBytesData();
+ }
+ }
+ }
+
+ // Fallback methods
+ public override bool Fallback(char charUnknown, int index)
+ {
+ // If we had a buffer already we're being recursive, throw, it's probably at the suspect
+ // character in our array.
+ // Shouldn't be able to get here for all of our code pages, table would have to be messed up.
+ Debug.Assert(_iCount < 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(non surrogate)] Fallback char " + ((int)_cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback");
+
+ _iCount = _iSize = 1;
+ _cBestFit = TryBestFit(charUnknown);
+ if (_cBestFit == '\0')
+ _cBestFit = '?';
+
+ return true;
+ }
+
+ public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
+ {
+ // Double check input surrogate pair
+ if (!Char.IsHighSurrogate(charUnknownHigh))
+ throw new ArgumentOutOfRangeException(nameof(charUnknownHigh),
+ SR.Format(SR.ArgumentOutOfRange_Range,
+ 0xD800, 0xDBFF));
+
+ if (!Char.IsLowSurrogate(charUnknownLow))
+ throw new ArgumentOutOfRangeException(nameof(charUnknownLow),
+ SR.Format(SR.ArgumentOutOfRange_Range,
+ 0xDC00, 0xDFFF));
+
+ // If we had a buffer already we're being recursive, throw, it's probably at the suspect
+ // character in our array. 0 is processing last character, < 0 is not falling back
+ // Shouldn't be able to get here, table would have to be messed up.
+ Debug.Assert(_iCount < 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(surrogate)] Fallback char " + ((int)_cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback");
+
+ // Go ahead and get our fallback, surrogates don't have best fit
+ _cBestFit = '?';
+ _iCount = _iSize = 2;
+
+ return true;
+ }
+
+ // Default version is overridden in EncoderReplacementFallback.cs
+ public override char GetNextChar()
+ {
+ // We want it to get < 0 because == 0 means that the current/last character is a fallback
+ // and we need to detect recursion. We could have a flag but we already have this counter.
+ _iCount--;
+
+ // Do we have anything left? 0 is now last fallback char, negative is nothing left
+ if (_iCount < 0)
+ return '\0';
+
+ // Need to get it out of the buffer.
+ // Make sure it didn't wrap from the fast count-- path
+ if (_iCount == int.MaxValue)
+ {
+ _iCount = -1;
+ return '\0';
+ }
+
+ // Return the best fit character
+ return _cBestFit;
+ }
+
+ public override bool MovePrevious()
+ {
+ // Exception fallback doesn't have anywhere to back up to.
+ if (_iCount >= 0)
+ _iCount++;
+
+ // Return true if we could do it.
+ return (_iCount >= 0 && _iCount <= _iSize);
+ }
+
+
+ // How many characters left to output?
+ public override int Remaining
+ {
+ get
+ {
+ return (_iCount > 0) ? _iCount : 0;
+ }
+ }
+
+ // Clear the buffer
+ public override unsafe void Reset()
+ {
+ _iCount = -1;
+ charStart = null;
+ bFallingBack = false;
+ }
+
+ // private helper methods
+ private char TryBestFit(char cUnknown)
+ {
+ // Need to figure out our best fit character, low is beginning of array, high is 1 AFTER end of array
+ int lowBound = 0;
+ int highBound = _oFallback._arrayBestFit.Length;
+ int index;
+
+ // Binary search the array
+ int iDiff;
+ while ((iDiff = (highBound - lowBound)) > 6)
+ {
+ // Look in the middle, which is complicated by the fact that we have 2 #s for each pair,
+ // so we don't want index to be odd because we want to be on word boundaries.
+ // Also note that index can never == highBound (because diff is rounded down)
+ index = ((iDiff / 2) + lowBound) & 0xFFFE;
+
+ char cTest = _oFallback._arrayBestFit[index];
+ if (cTest == cUnknown)
+ {
+ // We found it
+ Debug.Assert(index + 1 < _oFallback._arrayBestFit.Length,
+ "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
+ return _oFallback._arrayBestFit[index + 1];
+ }
+ else if (cTest < cUnknown)
+ {
+ // We weren't high enough
+ lowBound = index;
+ }
+ else
+ {
+ // We weren't low enough
+ highBound = index;
+ }
+ }
+
+ for (index = lowBound; index < highBound; index += 2)
+ {
+ if (_oFallback._arrayBestFit[index] == cUnknown)
+ {
+ // We found it
+ Debug.Assert(index + 1 < _oFallback._arrayBestFit.Length,
+ "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
+ return _oFallback._arrayBestFit[index + 1];
+ }
+ }
+
+ // Char wasn't in our table
+ return '\0';
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Text/EncoderExceptionFallback.cs b/src/System.Private.CoreLib/shared/System/Text/EncoderExceptionFallback.cs
new file mode 100644
index 0000000000..66de1940f6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/EncoderExceptionFallback.cs
@@ -0,0 +1,195 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.Text
+{
+ public sealed class EncoderExceptionFallback : EncoderFallback
+ {
+ // Construction
+ public EncoderExceptionFallback()
+ {
+ }
+
+ public override EncoderFallbackBuffer CreateFallbackBuffer()
+ {
+ return new EncoderExceptionFallbackBuffer();
+ }
+
+ // Maximum number of characters that this instance of this fallback could return
+ public override int MaxCharCount
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ public override bool Equals(Object value)
+ {
+ EncoderExceptionFallback that = value as EncoderExceptionFallback;
+ if (that != null)
+ {
+ return (true);
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return 654;
+ }
+ }
+
+
+ public sealed class EncoderExceptionFallbackBuffer : EncoderFallbackBuffer
+ {
+ public EncoderExceptionFallbackBuffer() { }
+ public override bool Fallback(char charUnknown, int index)
+ {
+ // Fall back our char
+ throw new EncoderFallbackException(
+ SR.Format(SR.Argument_InvalidCodePageConversionIndex, (int)charUnknown, index), charUnknown, index);
+ }
+
+ public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
+ {
+ if (!Char.IsHighSurrogate(charUnknownHigh))
+ {
+ throw new ArgumentOutOfRangeException(nameof(charUnknownHigh),
+ SR.Format(SR.ArgumentOutOfRange_Range, 0xD800, 0xDBFF));
+ }
+ if (!Char.IsLowSurrogate(charUnknownLow))
+ {
+ throw new ArgumentOutOfRangeException(nameof(charUnknownLow),
+ SR.Format(SR.ArgumentOutOfRange_Range, 0xDC00, 0xDFFF));
+ }
+
+ int iTemp = Char.ConvertToUtf32(charUnknownHigh, charUnknownLow);
+
+ // Fall back our char
+ throw new EncoderFallbackException(
+ SR.Format(SR.Argument_InvalidCodePageConversionIndex, iTemp, index), charUnknownHigh, charUnknownLow, index);
+ }
+
+ public override char GetNextChar()
+ {
+ return (char)0;
+ }
+
+ public override bool MovePrevious()
+ {
+ // Exception fallback doesn't have anywhere to back up to.
+ return false;
+ }
+
+ // Exceptions are always empty
+ public override int Remaining
+ {
+ get
+ {
+ return 0;
+ }
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class EncoderFallbackException : ArgumentException
+ {
+ private char _charUnknown;
+ private char _charUnknownHigh;
+ private char _charUnknownLow;
+ private int _index;
+
+ public EncoderFallbackException()
+ : base(SR.Arg_ArgumentException)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ public EncoderFallbackException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ public EncoderFallbackException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_ARGUMENT;
+ }
+
+ internal EncoderFallbackException(
+ String message, char charUnknown, int index) : base(message)
+ {
+ _charUnknown = charUnknown;
+ _index = index;
+ }
+
+ internal EncoderFallbackException(
+ String message, char charUnknownHigh, char charUnknownLow, int index) : base(message)
+ {
+ if (!Char.IsHighSurrogate(charUnknownHigh))
+ {
+ throw new ArgumentOutOfRangeException(nameof(charUnknownHigh),
+ SR.Format(SR.ArgumentOutOfRange_Range, 0xD800, 0xDBFF));
+ }
+ if (!Char.IsLowSurrogate(charUnknownLow))
+ {
+ throw new ArgumentOutOfRangeException(nameof(CharUnknownLow),
+ SR.Format(SR.ArgumentOutOfRange_Range, 0xDC00, 0xDFFF));
+ }
+
+ _charUnknownHigh = charUnknownHigh;
+ _charUnknownLow = charUnknownLow;
+ _index = index;
+ }
+
+ private EncoderFallbackException(SerializationInfo serializationInfo, StreamingContext streamingContext)
+ : base(serializationInfo, streamingContext)
+ {
+ }
+
+ public char CharUnknown
+ {
+ get
+ {
+ return (_charUnknown);
+ }
+ }
+
+ public char CharUnknownHigh
+ {
+ get
+ {
+ return (_charUnknownHigh);
+ }
+ }
+
+ public char CharUnknownLow
+ {
+ get
+ {
+ return (_charUnknownLow);
+ }
+ }
+
+ public int Index
+ {
+ get
+ {
+ return _index;
+ }
+ }
+
+ // Return true if the unknown character is a surrogate pair.
+ public bool IsUnknownSurrogate()
+ {
+ return (_charUnknownHigh != '\0');
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/EncoderFallback.cs b/src/System.Private.CoreLib/shared/System/Text/EncoderFallback.cs
new file mode 100644
index 0000000000..d860a02a76
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/EncoderFallback.cs
@@ -0,0 +1,202 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Threading;
+
+namespace System.Text
+{
+ public abstract class EncoderFallback
+ {
+ private static EncoderFallback s_replacementFallback; // Default fallback, uses no best fit & "?"
+ private static EncoderFallback s_exceptionFallback;
+
+ // Get each of our generic fallbacks.
+
+ public static EncoderFallback ReplacementFallback
+ {
+ get
+ {
+ if (s_replacementFallback == null)
+ Interlocked.CompareExchange<EncoderFallback>(ref s_replacementFallback, new EncoderReplacementFallback(), null);
+
+ return s_replacementFallback;
+ }
+ }
+
+
+ public static EncoderFallback ExceptionFallback
+ {
+ get
+ {
+ if (s_exceptionFallback == null)
+ Interlocked.CompareExchange<EncoderFallback>(ref s_exceptionFallback, new EncoderExceptionFallback(), null);
+
+ return s_exceptionFallback;
+ }
+ }
+
+ // Fallback
+ //
+ // Return the appropriate unicode string alternative to the character that need to fall back.
+ // Most implementations will be:
+ // return new MyCustomEncoderFallbackBuffer(this);
+
+ public abstract EncoderFallbackBuffer CreateFallbackBuffer();
+
+ // Maximum number of characters that this instance of this fallback could return
+
+ public abstract int MaxCharCount { get; }
+ }
+
+
+ public abstract class EncoderFallbackBuffer
+ {
+ // Most implementations will probably need an implementation-specific constructor
+
+ // Public methods that cannot be overridden that let us do our fallback thing
+ // These wrap the internal methods so that we can check for people doing stuff that is incorrect
+
+ public abstract bool Fallback(char charUnknown, int index);
+
+ public abstract bool Fallback(char charUnknownHigh, char charUnknownLow, int index);
+
+ // Get next character
+
+ public abstract char GetNextChar();
+
+ // Back up a character
+
+ public abstract bool MovePrevious();
+
+ // How many chars left in this fallback?
+
+ public abstract int Remaining { get; }
+
+ // Not sure if this should be public or not.
+ // Clear the buffer
+
+ public virtual void Reset()
+ {
+ while (GetNextChar() != (char)0) ;
+ }
+
+ // Internal items to help us figure out what we're doing as far as error messages, etc.
+ // These help us with our performance and messages internally
+ internal unsafe char* charStart;
+ internal unsafe char* charEnd;
+ internal EncoderNLS encoder;
+ internal bool setEncoder;
+ internal bool bUsedEncoder;
+ internal bool bFallingBack = false;
+ internal int iRecursionCount = 0;
+ private const int iMaxRecursion = 250;
+
+ // Internal Reset
+ // For example, what if someone fails a conversion and wants to reset one of our fallback buffers?
+ internal unsafe void InternalReset()
+ {
+ charStart = null;
+ bFallingBack = false;
+ iRecursionCount = 0;
+ Reset();
+ }
+
+ // Set the above values
+ // This can't be part of the constructor because EncoderFallbacks would have to know how to implement these.
+ internal unsafe void InternalInitialize(char* charStart, char* charEnd, EncoderNLS encoder, bool setEncoder)
+ {
+ this.charStart = charStart;
+ this.charEnd = charEnd;
+ this.encoder = encoder;
+ this.setEncoder = setEncoder;
+ this.bUsedEncoder = false;
+ this.bFallingBack = false;
+ this.iRecursionCount = 0;
+ }
+
+ internal char InternalGetNextChar()
+ {
+ char ch = GetNextChar();
+ bFallingBack = (ch != 0);
+ if (ch == 0) iRecursionCount = 0;
+ return ch;
+ }
+
+ // Fallback the current character using the remaining buffer and encoder if necessary
+ // This can only be called by our encodings (other have to use the public fallback methods), so
+ // we can use our EncoderNLS here too.
+ // setEncoder is true if we're calling from a GetBytes method, false if we're calling from a GetByteCount
+ //
+ // Note that this could also change the contents of this.encoder, which is the same
+ // object that the caller is using, so the caller could mess up the encoder for us
+ // if they aren't careful.
+ internal unsafe virtual bool InternalFallback(char ch, ref char* chars)
+ {
+ // Shouldn't have null charStart
+ Debug.Assert(charStart != null,
+ "[EncoderFallback.InternalFallbackBuffer]Fallback buffer is not initialized");
+
+ // Get our index, remember chars was preincremented to point at next char, so have to -1
+ int index = (int)(chars - charStart) - 1;
+
+ // See if it was a high surrogate
+ if (Char.IsHighSurrogate(ch))
+ {
+ // See if there's a low surrogate to go with it
+ if (chars >= this.charEnd)
+ {
+ // Nothing left in input buffer
+ // No input, return 0 if mustflush is false
+ if (this.encoder != null && !this.encoder.MustFlush)
+ {
+ // Done, nothing to fallback
+ if (this.setEncoder)
+ {
+ bUsedEncoder = true;
+ this.encoder._charLeftOver = ch;
+ }
+ bFallingBack = false;
+ return false;
+ }
+ }
+ else
+ {
+ // Might have a low surrogate
+ char cNext = *chars;
+ if (Char.IsLowSurrogate(cNext))
+ {
+ // If already falling back then fail
+ if (bFallingBack && iRecursionCount++ > iMaxRecursion)
+ ThrowLastCharRecursive(Char.ConvertToUtf32(ch, cNext));
+
+ // Next is a surrogate, add it as surrogate pair, and increment chars
+ chars++;
+ bFallingBack = Fallback(ch, cNext, index);
+ return bFallingBack;
+ }
+ // Next isn't a low surrogate, just fallback the high surrogate
+ }
+ }
+
+ // If already falling back then fail
+ if (bFallingBack && iRecursionCount++ > iMaxRecursion)
+ ThrowLastCharRecursive((int)ch);
+
+ // Fall back our char
+ bFallingBack = Fallback(ch, index);
+
+ return bFallingBack;
+ }
+
+ // private helper methods
+ internal void ThrowLastCharRecursive(int charRecursive)
+ {
+ // Throw it, using our complete character
+ throw new ArgumentException(
+ SR.Format(SR.Argument_RecursiveFallback,
+ charRecursive), "chars");
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/EncoderNLS.cs b/src/System.Private.CoreLib/shared/System/Text/EncoderNLS.cs
new file mode 100644
index 0000000000..e83666f7a3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/EncoderNLS.cs
@@ -0,0 +1,239 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ // An Encoder is used to encode a sequence of blocks of characters into
+ // a sequence of blocks of bytes. Following instantiation of an encoder,
+ // sequential blocks of characters are converted into blocks of bytes through
+ // calls to the GetBytes method. The encoder maintains state between the
+ // conversions, allowing it to correctly encode character sequences that span
+ // adjacent blocks.
+ //
+ // Instances of specific implementations of the Encoder abstract base
+ // class are typically obtained through calls to the GetEncoder method
+ // of Encoding objects.
+ //
+
+ internal class EncoderNLS : Encoder
+ {
+ // Need a place for the last left over character, most of our encodings use this
+ internal char _charLeftOver;
+ private Encoding _encoding;
+ private bool _mustFlush;
+ internal bool _throwOnOverflow;
+ internal int _charsUsed;
+
+ internal EncoderNLS(Encoding encoding)
+ {
+ _encoding = encoding;
+ _fallback = _encoding.EncoderFallback;
+ this.Reset();
+ }
+
+ internal EncoderNLS()
+ {
+ _encoding = null;
+ this.Reset();
+ }
+
+ public override void Reset()
+ {
+ _charLeftOver = (char)0;
+ if (_fallbackBuffer != null)
+ _fallbackBuffer.Reset();
+ }
+
+ public override unsafe int GetByteCount(char[] chars, int index, int count, bool flush)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException(nameof(chars),
+ SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - index < count)
+ throw new ArgumentOutOfRangeException(nameof(chars),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Just call the pointer version
+ int result = -1;
+ fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ {
+ result = GetByteCount(pChars + index, count, flush);
+ }
+ return result;
+ }
+
+ public unsafe override int GetByteCount(char* chars, int count, bool flush)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException(nameof(chars),
+ SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ _mustFlush = flush;
+ _throwOnOverflow = true;
+ return _encoding.GetByteCount(chars, count, this);
+ }
+
+ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex, bool flush)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? nameof(chars) : nameof(bytes)),
+ SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? nameof(charIndex) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException(nameof(chars),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException(nameof(byteIndex),
+ SR.ArgumentOutOfRange_Index);
+
+ int byteCount = bytes.Length - byteIndex;
+
+ // Just call pointer version
+ fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+
+ // Remember that charCount is # to decode, not size of array.
+ return GetBytes(pChars + charIndex, charCount,
+ pBytes + byteIndex, byteCount, flush);
+ }
+
+ public unsafe override int GetBytes(char* chars, int charCount, byte* bytes, int byteCount, bool flush)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? nameof(chars) : nameof(bytes)),
+ SR.ArgumentNull_Array);
+
+ if (byteCount < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((byteCount < 0 ? nameof(byteCount) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ _mustFlush = flush;
+ _throwOnOverflow = true;
+ return _encoding.GetBytes(chars, charCount, bytes, byteCount, this);
+ }
+
+ // This method is used when your output buffer might not be large enough for the entire result.
+ // Just call the pointer version. (This gets bytes)
+ public override unsafe void Convert(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex, int byteCount, bool flush,
+ out int charsUsed, out int bytesUsed, out bool completed)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? nameof(chars) : nameof(bytes)),
+ SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? nameof(charIndex) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? nameof(byteIndex) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException(nameof(chars),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException(nameof(bytes),
+ SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Just call the pointer version (can't do this for non-msft encoders)
+ fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ {
+ fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ {
+ Convert(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, flush,
+ out charsUsed, out bytesUsed, out completed);
+ }
+ }
+ }
+
+ // This is the version that uses pointers. We call the base encoding worker function
+ // after setting our appropriate internal variables. This is getting bytes
+ public override unsafe void Convert(char* chars, int charCount,
+ byte* bytes, int byteCount, bool flush,
+ out int charsUsed, out int bytesUsed, out bool completed)
+ {
+ // Validate input parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? nameof(bytes) : nameof(chars),
+ SR.ArgumentNull_Array);
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? nameof(charCount) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // We don't want to throw
+ _mustFlush = flush;
+ _throwOnOverflow = false;
+ _charsUsed = 0;
+
+ // Do conversion
+ bytesUsed = _encoding.GetBytes(chars, charCount, bytes, byteCount, this);
+ charsUsed = _charsUsed;
+
+ // Its completed if they've used what they wanted AND if they didn't want flush or if we are flushed
+ completed = (charsUsed == charCount) && (!flush || !this.HasState) &&
+ (_fallbackBuffer == null || _fallbackBuffer.Remaining == 0);
+
+ // Our data thingys are now full, we can return
+ }
+
+ public Encoding Encoding
+ {
+ get
+ {
+ return _encoding;
+ }
+ }
+
+ public bool MustFlush
+ {
+ get
+ {
+ return _mustFlush;
+ }
+ }
+
+
+ // Anything left in our encoder?
+ internal virtual bool HasState
+ {
+ get
+ {
+ return (_charLeftOver != (char)0);
+ }
+ }
+
+ // Allow encoding to clear our must flush instead of throwing (in ThrowBytesOverflow)
+ internal void ClearMustFlush()
+ {
+ _mustFlush = false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/EncoderReplacementFallback.cs b/src/System.Private.CoreLib/shared/System/Text/EncoderReplacementFallback.cs
new file mode 100644
index 0000000000..a1d0bbcd95
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/EncoderReplacementFallback.cs
@@ -0,0 +1,227 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime;
+using System.Diagnostics;
+
+namespace System.Text
+{
+ public sealed class EncoderReplacementFallback : EncoderFallback
+ {
+ // Our variables
+ private String _strDefault;
+
+ // Construction. Default replacement fallback uses no best fit and ? replacement string
+ public EncoderReplacementFallback() : this("?")
+ {
+ }
+
+ public EncoderReplacementFallback(String replacement)
+ {
+ // Must not be null
+ if (replacement == null)
+ throw new ArgumentNullException(nameof(replacement));
+
+ // Make sure it doesn't have bad surrogate pairs
+ bool bFoundHigh = false;
+ for (int i = 0; i < replacement.Length; i++)
+ {
+ // Found a surrogate?
+ if (Char.IsSurrogate(replacement, i))
+ {
+ // High or Low?
+ if (Char.IsHighSurrogate(replacement, i))
+ {
+ // if already had a high one, stop
+ if (bFoundHigh)
+ break; // break & throw at the bFoundHIgh below
+ bFoundHigh = true;
+ }
+ else
+ {
+ // Low, did we have a high?
+ if (!bFoundHigh)
+ {
+ // Didn't have one, make if fail when we stop
+ bFoundHigh = true;
+ break;
+ }
+
+ // Clear flag
+ bFoundHigh = false;
+ }
+ }
+ // If last was high we're in trouble (not surrogate so not low surrogate, so break)
+ else if (bFoundHigh)
+ break;
+ }
+ if (bFoundHigh)
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidCharSequenceNoIndex, nameof(replacement)));
+
+ _strDefault = replacement;
+ }
+
+ public String DefaultString
+ {
+ get
+ {
+ return _strDefault;
+ }
+ }
+
+ public override EncoderFallbackBuffer CreateFallbackBuffer()
+ {
+ return new EncoderReplacementFallbackBuffer(this);
+ }
+
+ // Maximum number of characters that this instance of this fallback could return
+ public override int MaxCharCount
+ {
+ get
+ {
+ return _strDefault.Length;
+ }
+ }
+
+ public override bool Equals(Object value)
+ {
+ EncoderReplacementFallback that = value as EncoderReplacementFallback;
+ if (that != null)
+ {
+ return (_strDefault == that._strDefault);
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return _strDefault.GetHashCode();
+ }
+ }
+
+
+
+ public sealed class EncoderReplacementFallbackBuffer : EncoderFallbackBuffer
+ {
+ // Store our default string
+ private String _strDefault;
+ private int _fallbackCount = -1;
+ private int _fallbackIndex = -1;
+
+ // Construction
+ public EncoderReplacementFallbackBuffer(EncoderReplacementFallback fallback)
+ {
+ // 2X in case we're a surrogate pair
+ _strDefault = fallback.DefaultString + fallback.DefaultString;
+ }
+
+ // Fallback Methods
+ public override bool Fallback(char charUnknown, int index)
+ {
+ // If we had a buffer already we're being recursive, throw, it's probably at the suspect
+ // character in our array.
+ if (_fallbackCount >= 1)
+ {
+ // If we're recursive we may still have something in our buffer that makes this a surrogate
+ if (char.IsHighSurrogate(charUnknown) && _fallbackCount >= 0 &&
+ char.IsLowSurrogate(_strDefault[_fallbackIndex + 1]))
+ ThrowLastCharRecursive(Char.ConvertToUtf32(charUnknown, _strDefault[_fallbackIndex + 1]));
+
+ // Nope, just one character
+ ThrowLastCharRecursive(unchecked((int)charUnknown));
+ }
+
+ // Go ahead and get our fallback
+ // Divide by 2 because we aren't a surrogate pair
+ _fallbackCount = _strDefault.Length / 2;
+ _fallbackIndex = -1;
+
+ return _fallbackCount != 0;
+ }
+
+ public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
+ {
+ // Double check input surrogate pair
+ if (!Char.IsHighSurrogate(charUnknownHigh))
+ throw new ArgumentOutOfRangeException(nameof(charUnknownHigh),
+ SR.Format(SR.ArgumentOutOfRange_Range, 0xD800, 0xDBFF));
+
+ if (!Char.IsLowSurrogate(charUnknownLow))
+ throw new ArgumentOutOfRangeException(nameof(charUnknownLow),
+ SR.Format(SR.ArgumentOutOfRange_Range, 0xDC00, 0xDFFF));
+
+ // If we had a buffer already we're being recursive, throw, it's probably at the suspect
+ // character in our array.
+ if (_fallbackCount >= 1)
+ ThrowLastCharRecursive(Char.ConvertToUtf32(charUnknownHigh, charUnknownLow));
+
+ // Go ahead and get our fallback
+ _fallbackCount = _strDefault.Length;
+ _fallbackIndex = -1;
+
+ return _fallbackCount != 0;
+ }
+
+ public override char GetNextChar()
+ {
+ // We want it to get < 0 because == 0 means that the current/last character is a fallback
+ // and we need to detect recursion. We could have a flag but we already have this counter.
+ _fallbackCount--;
+ _fallbackIndex++;
+
+ // Do we have anything left? 0 is now last fallback char, negative is nothing left
+ if (_fallbackCount < 0)
+ return '\0';
+
+ // Need to get it out of the buffer.
+ // Make sure it didn't wrap from the fast count-- path
+ if (_fallbackCount == int.MaxValue)
+ {
+ _fallbackCount = -1;
+ return '\0';
+ }
+
+ // Now make sure its in the expected range
+ Debug.Assert(_fallbackIndex < _strDefault.Length && _fallbackIndex >= 0,
+ "Index exceeds buffer range");
+
+ return _strDefault[_fallbackIndex];
+ }
+
+ public override bool MovePrevious()
+ {
+ // Back up one, only if we just processed the last character (or earlier)
+ if (_fallbackCount >= -1 && _fallbackIndex >= 0)
+ {
+ _fallbackIndex--;
+ _fallbackCount++;
+ return true;
+ }
+
+ // Return false 'cause we couldn't do it.
+ return false;
+ }
+
+ // How many characters left to output?
+ public override int Remaining
+ {
+ get
+ {
+ // Our count is 0 for 1 character left.
+ return (_fallbackCount < 0) ? 0 : _fallbackCount;
+ }
+ }
+
+ // Clear the buffer
+ public override unsafe void Reset()
+ {
+ _fallbackCount = -1;
+ _fallbackIndex = 0;
+ charStart = null;
+ bFallingBack = false;
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Text/Encoding.cs b/src/System.Private.CoreLib/shared/System/Text/Encoding.cs
new file mode 100644
index 0000000000..e191ce14fd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/Encoding.cs
@@ -0,0 +1,1789 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Threading;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Text
+{
+ // This abstract base class represents a character encoding. The class provides
+ // methods to convert arrays and strings of Unicode characters to and from
+ // arrays of bytes. A number of Encoding implementations are provided in
+ // the System.Text package, including:
+ //
+ // ASCIIEncoding, which encodes Unicode characters as single 7-bit
+ // ASCII characters. This encoding only supports character values between 0x00
+ // and 0x7F.
+ // BaseCodePageEncoding, which encapsulates a Windows code page. Any
+ // installed code page can be accessed through this encoding, and conversions
+ // are performed using the WideCharToMultiByte and
+ // MultiByteToWideChar Windows API functions.
+ // UnicodeEncoding, which encodes each Unicode character as two
+ // consecutive bytes. Both little-endian (code page 1200) and big-endian (code
+ // page 1201) encodings are recognized.
+ // UTF7Encoding, which encodes Unicode characters using the UTF-7
+ // encoding (UTF-7 stands for UCS Transformation Format, 7-bit form). This
+ // encoding supports all Unicode character values, and can also be accessed
+ // as code page 65000.
+ // UTF8Encoding, which encodes Unicode characters using the UTF-8
+ // encoding (UTF-8 stands for UCS Transformation Format, 8-bit form). This
+ // encoding supports all Unicode character values, and can also be accessed
+ // as code page 65001.
+ // UTF32Encoding, both 12000 (little endian) & 12001 (big endian)
+ //
+ // In addition to directly instantiating Encoding objects, an
+ // application can use the ForCodePage, GetASCII,
+ // GetDefault, GetUnicode, GetUTF7, and GetUTF8
+ // methods in this class to obtain encodings.
+ //
+ // Through an encoding, the GetBytes method is used to convert arrays
+ // of characters to arrays of bytes, and the GetChars method is used to
+ // convert arrays of bytes to arrays of characters. The GetBytes and
+ // GetChars methods maintain no state between conversions, and are
+ // generally intended for conversions of complete blocks of bytes and
+ // characters in one operation. When the data to be converted is only available
+ // in sequential blocks (such as data read from a stream) or when the amount of
+ // data is so large that it needs to be divided into smaller blocks, an
+ // application may choose to use a Decoder or an Encoder to
+ // perform the conversion. Decoders and encoders allow sequential blocks of
+ // data to be converted and they maintain the state required to support
+ // conversions of data that spans adjacent blocks. Decoders and encoders are
+ // obtained using the GetDecoder and GetEncoder methods.
+ //
+ // The core GetBytes and GetChars methods require the caller
+ // to provide the destination buffer and ensure that the buffer is large enough
+ // to hold the entire result of the conversion. When using these methods,
+ // either directly on an Encoding object or on an associated
+ // Decoder or Encoder, an application can use one of two methods
+ // to allocate destination buffers.
+ //
+ // The GetByteCount and GetCharCount methods can be used to
+ // compute the exact size of the result of a particular conversion, and an
+ // appropriately sized buffer for that conversion can then be allocated.
+ // The GetMaxByteCount and GetMaxCharCount methods can be
+ // be used to compute the maximum possible size of a conversion of a given
+ // number of bytes or characters, and a buffer of that size can then be reused
+ // for multiple conversions.
+ //
+ // The first method generally uses less memory, whereas the second method
+ // generally executes faster.
+ //
+
+ public abstract class Encoding : ICloneable
+ {
+ // For netcore we use UTF8 as default encoding since ANSI isn't available
+ private static readonly UTF8Encoding.UTF8EncodingSealed s_defaultEncoding = new UTF8Encoding.UTF8EncodingSealed(encoderShouldEmitUTF8Identifier: false);
+
+ // Returns an encoding for the system's current ANSI code page.
+ public static Encoding Default => s_defaultEncoding;
+
+ //
+ // The following values are from mlang.idl. These values
+ // should be in sync with those in mlang.idl.
+ //
+ internal const int MIMECONTF_MAILNEWS = 0x00000001;
+ internal const int MIMECONTF_BROWSER = 0x00000002;
+ internal const int MIMECONTF_SAVABLE_MAILNEWS = 0x00000100;
+ internal const int MIMECONTF_SAVABLE_BROWSER = 0x00000200;
+
+ // Special Case Code Pages
+ private const int CodePageDefault = 0;
+ private const int CodePageNoOEM = 1; // OEM Code page not supported
+ private const int CodePageNoMac = 2; // MAC code page not supported
+ private const int CodePageNoThread = 3; // Thread code page not supported
+ private const int CodePageNoSymbol = 42; // Symbol code page not supported
+ private const int CodePageUnicode = 1200; // Unicode
+ private const int CodePageBigEndian = 1201; // Big Endian Unicode
+ private const int CodePageWindows1252 = 1252; // Windows 1252 code page
+
+ // 20936 has same code page as 10008, so we'll special case it
+ private const int CodePageMacGB2312 = 10008;
+ private const int CodePageGB2312 = 20936;
+ private const int CodePageMacKorean = 10003;
+ private const int CodePageDLLKorean = 20949;
+
+ // ISO 2022 Code Pages
+ private const int ISO2022JP = 50220;
+ private const int ISO2022JPESC = 50221;
+ private const int ISO2022JPSISO = 50222;
+ private const int ISOKorean = 50225;
+ private const int ISOSimplifiedCN = 50227;
+ private const int EUCJP = 51932;
+ private const int ChineseHZ = 52936; // HZ has ~}~{~~ sequences
+
+ // 51936 is the same as 936
+ private const int DuplicateEUCCN = 51936;
+ private const int EUCCN = 936;
+
+ private const int EUCKR = 51949;
+
+ // Latin 1 & ASCII Code Pages
+ internal const int CodePageASCII = 20127; // ASCII
+ internal const int ISO_8859_1 = 28591; // Latin1
+
+ // ISCII
+ private const int ISCIIAssemese = 57006;
+ private const int ISCIIBengali = 57003;
+ private const int ISCIIDevanagari = 57002;
+ private const int ISCIIGujarathi = 57010;
+ private const int ISCIIKannada = 57008;
+ private const int ISCIIMalayalam = 57009;
+ private const int ISCIIOriya = 57007;
+ private const int ISCIIPanjabi = 57011;
+ private const int ISCIITamil = 57004;
+ private const int ISCIITelugu = 57005;
+
+ // GB18030
+ private const int GB18030 = 54936;
+
+ // Other
+ private const int ISO_8859_8I = 38598;
+ private const int ISO_8859_8_Visual = 28598;
+
+ // 50229 is currently unsupported // "Chinese Traditional (ISO-2022)"
+ private const int ENC50229 = 50229;
+
+ // Special code pages
+ private const int CodePageUTF7 = 65000;
+ private const int CodePageUTF8 = 65001;
+ private const int CodePageUTF32 = 12000;
+ private const int CodePageUTF32BE = 12001;
+
+ internal int _codePage = 0;
+
+ internal CodePageDataItem _dataItem = null;
+
+ // Because of encoders we may be read only
+ [OptionalField(VersionAdded = 2)]
+ private bool _isReadOnly = true;
+
+ // Encoding (encoder) fallback
+ internal EncoderFallback encoderFallback = null;
+ internal DecoderFallback decoderFallback = null;
+
+ protected Encoding() : this(0)
+ {
+ }
+
+
+ protected Encoding(int codePage)
+ {
+ // Validate code page
+ if (codePage < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(codePage));
+ }
+
+ // Remember code page
+ _codePage = codePage;
+
+ // Use default encoder/decoder fallbacks
+ this.SetDefaultFallbacks();
+ }
+
+ // This constructor is needed to allow any sub-classing implementation to provide encoder/decoder fallback objects
+ // because the encoding object is always created as read-only object and don't allow setting encoder/decoder fallback
+ // after the creation is done.
+ protected Encoding(int codePage, EncoderFallback encoderFallback, DecoderFallback decoderFallback)
+ {
+ // Validate code page
+ if (codePage < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(codePage));
+ }
+
+ // Remember code page
+ _codePage = codePage;
+
+ this.encoderFallback = encoderFallback ?? new InternalEncoderBestFitFallback(this);
+ this.decoderFallback = decoderFallback ?? new InternalDecoderBestFitFallback(this);
+ }
+
+ // Default fallback that we'll use.
+ internal virtual void SetDefaultFallbacks()
+ {
+ // For UTF-X encodings, we use a replacement fallback with an "\xFFFD" string,
+ // For ASCII we use "?" replacement fallback, etc.
+ encoderFallback = new InternalEncoderBestFitFallback(this);
+ decoderFallback = new InternalDecoderBestFitFallback(this);
+ }
+
+ // Converts a byte array from one encoding to another. The bytes in the
+ // bytes array are converted from srcEncoding to
+ // dstEncoding, and the returned value is a new byte array
+ // containing the result of the conversion.
+ //
+ public static byte[] Convert(Encoding srcEncoding, Encoding dstEncoding,
+ byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes));
+
+ return Convert(srcEncoding, dstEncoding, bytes, 0, bytes.Length);
+ }
+
+ // Converts a range of bytes in a byte array from one encoding to another.
+ // This method converts count bytes from bytes starting at
+ // index index from srcEncoding to dstEncoding, and
+ // returns a new byte array containing the result of the conversion.
+ //
+ public static byte[] Convert(Encoding srcEncoding, Encoding dstEncoding,
+ byte[] bytes, int index, int count)
+ {
+ if (srcEncoding == null || dstEncoding == null)
+ {
+ throw new ArgumentNullException((srcEncoding == null ? nameof(srcEncoding) : nameof(dstEncoding)),
+ SR.ArgumentNull_Array);
+ }
+ if (bytes == null)
+ {
+ throw new ArgumentNullException(nameof(bytes),
+ SR.ArgumentNull_Array);
+ }
+
+ return dstEncoding.GetBytes(srcEncoding.GetChars(bytes, index, count));
+ }
+
+ public static void RegisterProvider(EncodingProvider provider)
+ {
+ // Parameters validated inside EncodingProvider
+ EncodingProvider.AddProvider(provider);
+ }
+
+ public static Encoding GetEncoding(int codepage)
+ {
+ Encoding result = EncodingProvider.GetEncodingFromProvider(codepage);
+ if (result != null)
+ return result;
+
+ //
+ // NOTE: If you add a new encoding that can be retrieved by codepage, be sure to
+ // add the corresponding item in EncodingTable.
+ // Otherwise, the code below will throw exception when trying to call
+ // EncodingTable.GetDataItem().
+ //
+ if (codepage < 0 || codepage > 65535)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(codepage), SR.Format(SR.ArgumentOutOfRange_Range, 0, 65535));
+ }
+
+
+ switch (codepage)
+ {
+ case CodePageDefault: return Default; // 0
+ case CodePageUnicode: return Unicode; // 1200
+ case CodePageBigEndian: return BigEndianUnicode; // 1201
+ case CodePageUTF32: return UTF32; // 12000
+ case CodePageUTF32BE: return BigEndianUTF32; // 12001
+ case CodePageUTF7: return UTF7; // 65000
+ case CodePageUTF8: return UTF8; // 65001
+ case CodePageASCII: return ASCII; // 20127
+ case ISO_8859_1: return Latin1; // 28591
+
+ // We don't allow the following special code page values that Win32 allows.
+ case CodePageNoOEM: // 1 CP_OEMCP
+ case CodePageNoMac: // 2 CP_MACCP
+ case CodePageNoThread: // 3 CP_THREAD_ACP
+ case CodePageNoSymbol: // 42 CP_SYMBOL
+ throw new ArgumentException(SR.Format(SR.Argument_CodepageNotSupported, codepage), nameof(codepage));
+ }
+
+ // Is it a valid code page?
+ if (EncodingTable.GetCodePageDataItem(codepage) == null)
+ {
+ throw new NotSupportedException(
+ SR.Format(SR.NotSupported_NoCodepageData, codepage));
+ }
+
+ return UTF8;
+ }
+
+ public static Encoding GetEncoding(int codepage,
+ EncoderFallback encoderFallback, DecoderFallback decoderFallback)
+ {
+ Encoding baseEncoding = EncodingProvider.GetEncodingFromProvider(codepage, encoderFallback, decoderFallback);
+
+ if (baseEncoding != null)
+ return baseEncoding;
+
+ // Get the default encoding (which is cached and read only)
+ baseEncoding = GetEncoding(codepage);
+
+ // Clone it and set the fallback
+ Encoding fallbackEncoding = (Encoding)baseEncoding.Clone();
+ fallbackEncoding.EncoderFallback = encoderFallback;
+ fallbackEncoding.DecoderFallback = decoderFallback;
+
+ return fallbackEncoding;
+ }
+
+ // Returns an Encoding object for a given name or a given code page value.
+ //
+ public static Encoding GetEncoding(String name)
+ {
+ Encoding baseEncoding = EncodingProvider.GetEncodingFromProvider(name);
+ if (baseEncoding != null)
+ return baseEncoding;
+
+ //
+ // NOTE: If you add a new encoding that can be requested by name, be sure to
+ // add the corresponding item in EncodingTable.
+ // Otherwise, the code below will throw exception when trying to call
+ // EncodingTable.GetCodePageFromName().
+ //
+ return GetEncoding(EncodingTable.GetCodePageFromName(name));
+ }
+
+ // Returns an Encoding object for a given name or a given code page value.
+ //
+ public static Encoding GetEncoding(String name,
+ EncoderFallback encoderFallback, DecoderFallback decoderFallback)
+ {
+ Encoding baseEncoding = EncodingProvider.GetEncodingFromProvider(name, encoderFallback, decoderFallback);
+ if (baseEncoding != null)
+ return baseEncoding;
+
+ //
+ // NOTE: If you add a new encoding that can be requested by name, be sure to
+ // add the corresponding item in EncodingTable.
+ // Otherwise, the code below will throw exception when trying to call
+ // EncodingTable.GetCodePageFromName().
+ //
+ return (GetEncoding(EncodingTable.GetCodePageFromName(name), encoderFallback, decoderFallback));
+ }
+
+ // Return a list of all EncodingInfo objects describing all of our encodings
+ public static EncodingInfo[] GetEncodings()
+ {
+ return EncodingTable.GetEncodings();
+ }
+
+ public virtual byte[] GetPreamble()
+ {
+ return Array.Empty<byte>();
+ }
+
+ public virtual ReadOnlySpan<byte> Preamble => GetPreamble();
+
+ private void GetDataItem()
+ {
+ if (_dataItem == null)
+ {
+ _dataItem = EncodingTable.GetCodePageDataItem(_codePage);
+ if (_dataItem == null)
+ {
+ throw new NotSupportedException(SR.Format(SR.NotSupported_NoCodepageData, _codePage));
+ }
+ }
+ }
+
+ // Returns the name for this encoding that can be used with mail agent body tags.
+ // If the encoding may not be used, the string is empty.
+
+ public virtual String BodyName
+ {
+ get
+ {
+ if (_dataItem == null)
+ {
+ GetDataItem();
+ }
+ return (_dataItem.BodyName);
+ }
+ }
+
+ // Returns the human-readable description of the encoding ( e.g. Hebrew (DOS)).
+#if PROJECTN
+ public virtual String EncodingName
+ {
+ get
+ {
+ string encodingName = GetLocalizedEncodingNameResource(this.CodePage);
+ if (encodingName == null)
+ {
+ throw new NotSupportedException(SR.Format(SR.MissingEncodingNameResource, this.CodePage));
+ }
+
+ if (encodingName.StartsWith("Globalization_cp_", StringComparison.Ordinal))
+ {
+ // On ProjectN, resource strings are stripped from retail builds and replaced by
+ // their identifier names. Since this property is meant to be a localized string,
+ // but we don't localize ProjectN, we specifically need to do something reasonable
+ // in this case. This currently returns the English name of the encoding from a
+ // static data table.
+ encodingName = EncodingTable.GetCodePageDataItem(this.CodePage).EnglishName;
+ if (encodingName == null)
+ {
+ throw new NotSupportedException(SR.Format(SR.MissingEncodingNameResource, this.WebName, this.CodePage));
+ }
+ }
+ return encodingName;
+ }
+ }
+
+ private static string GetLocalizedEncodingNameResource(int codePage)
+ {
+ switch (codePage)
+ {
+ case 1200: return SR.Globalization_cp_1200;
+ case 1201: return SR.Globalization_cp_1201;
+ case 12000: return SR.Globalization_cp_12000;
+ case 12001: return SR.Globalization_cp_12001;
+ case 20127: return SR.Globalization_cp_20127;
+ case 28591: return SR.Globalization_cp_28591;
+ case 65000: return SR.Globalization_cp_65000;
+ case 65001: return SR.Globalization_cp_65001;
+ default: return null;
+ }
+ }
+#else
+ public virtual String EncodingName
+ {
+ get
+ {
+ return SR.GetResourceString("Globalization_cp_" + _codePage.ToString());
+ }
+ }
+#endif
+ // Returns the name for this encoding that can be used with mail agent header
+ // tags. If the encoding may not be used, the string is empty.
+
+ public virtual String HeaderName
+ {
+ get
+ {
+ if (_dataItem == null)
+ {
+ GetDataItem();
+ }
+ return (_dataItem.HeaderName);
+ }
+ }
+
+ // Returns the IANA preferred name for this encoding.
+ public virtual String WebName
+ {
+ get
+ {
+ if (_dataItem == null)
+ {
+ GetDataItem();
+ }
+ return (_dataItem.WebName);
+ }
+ }
+
+ // Returns the windows code page that most closely corresponds to this encoding.
+
+ public virtual int WindowsCodePage
+ {
+ get
+ {
+ if (_dataItem == null)
+ {
+ GetDataItem();
+ }
+ return (_dataItem.UIFamilyCodePage);
+ }
+ }
+
+
+ // True if and only if the encoding is used for display by browsers clients.
+
+ public virtual bool IsBrowserDisplay
+ {
+ get
+ {
+ if (_dataItem == null)
+ {
+ GetDataItem();
+ }
+ return ((_dataItem.Flags & MIMECONTF_BROWSER) != 0);
+ }
+ }
+
+ // True if and only if the encoding is used for saving by browsers clients.
+
+ public virtual bool IsBrowserSave
+ {
+ get
+ {
+ if (_dataItem == null)
+ {
+ GetDataItem();
+ }
+ return ((_dataItem.Flags & MIMECONTF_SAVABLE_BROWSER) != 0);
+ }
+ }
+
+ // True if and only if the encoding is used for display by mail and news clients.
+
+ public virtual bool IsMailNewsDisplay
+ {
+ get
+ {
+ if (_dataItem == null)
+ {
+ GetDataItem();
+ }
+ return ((_dataItem.Flags & MIMECONTF_MAILNEWS) != 0);
+ }
+ }
+
+
+ // True if and only if the encoding is used for saving documents by mail and
+ // news clients
+
+ public virtual bool IsMailNewsSave
+ {
+ get
+ {
+ if (_dataItem == null)
+ {
+ GetDataItem();
+ }
+ return ((_dataItem.Flags & MIMECONTF_SAVABLE_MAILNEWS) != 0);
+ }
+ }
+
+ // True if and only if the encoding only uses single byte code points. (Ie, ASCII, 1252, etc)
+
+ public virtual bool IsSingleByte
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+
+ public EncoderFallback EncoderFallback
+ {
+ get
+ {
+ return encoderFallback;
+ }
+
+ set
+ {
+ if (this.IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ encoderFallback = value;
+ }
+ }
+
+
+ public DecoderFallback DecoderFallback
+ {
+ get
+ {
+ return decoderFallback;
+ }
+
+ set
+ {
+ if (this.IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ decoderFallback = value;
+ }
+ }
+
+
+ public virtual Object Clone()
+ {
+ Encoding newEncoding = (Encoding)this.MemberwiseClone();
+
+ // New one should be readable
+ newEncoding._isReadOnly = false;
+ return newEncoding;
+ }
+
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return (_isReadOnly);
+ }
+ }
+
+ // Returns an encoding for the ASCII character set. The returned encoding
+ // will be an instance of the ASCIIEncoding class.
+
+ public static Encoding ASCII => ASCIIEncoding.s_default;
+
+ // Returns an encoding for the Latin1 character set. The returned encoding
+ // will be an instance of the Latin1Encoding class.
+ //
+ // This is for our optimizations
+ private static Encoding Latin1 => Latin1Encoding.s_default;
+
+ // Returns the number of bytes required to encode the given character
+ // array.
+ //
+ public virtual int GetByteCount(char[] chars)
+ {
+ if (chars == null)
+ {
+ throw new ArgumentNullException(nameof(chars),
+ SR.ArgumentNull_Array);
+ }
+
+ return GetByteCount(chars, 0, chars.Length);
+ }
+
+ public virtual int GetByteCount(String s)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+
+ char[] chars = s.ToCharArray();
+ return GetByteCount(chars, 0, chars.Length);
+ }
+
+ // Returns the number of bytes required to encode a range of characters in
+ // a character array.
+ //
+ public abstract int GetByteCount(char[] chars, int index, int count);
+
+ // Returns the number of bytes required to encode a string range.
+ //
+ public int GetByteCount(string s, int index, int count)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s),
+ SR.ArgumentNull_String);
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (index > s.Length - count)
+ throw new ArgumentOutOfRangeException(nameof(index),
+ SR.ArgumentOutOfRange_IndexCount);
+
+ unsafe
+ {
+ fixed (char* pChar = s)
+ {
+ return GetByteCount(pChar + index, count);
+ }
+ }
+ }
+
+ // We expect this to be the workhorse for NLS encodings
+ // unfortunately for existing overrides, it has to call the [] version,
+ // which is really slow, so this method should be avoided if you're calling
+ // a 3rd party encoding.
+ [CLSCompliant(false)]
+ public virtual unsafe int GetByteCount(char* chars, int count)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException(nameof(chars),
+ SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ char[] arrChar = new char[count];
+ int index;
+
+ for (index = 0; index < count; index++)
+ arrChar[index] = chars[index];
+
+ return GetByteCount(arrChar, 0, count);
+ }
+
+ public virtual unsafe int GetByteCount(ReadOnlySpan<char> chars)
+ {
+ fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars))
+ {
+ return GetByteCount(charsPtr, chars.Length);
+ }
+ }
+
+ // For NLS Encodings, workhorse takes an encoder (may be null)
+ // Always validate parameters before calling internal version, which will only assert.
+ internal virtual unsafe int GetByteCount(char* chars, int count, EncoderNLS encoder)
+ {
+ Debug.Assert(chars != null);
+ Debug.Assert(count >= 0);
+
+ return GetByteCount(chars, count);
+ }
+
+ // Returns a byte array containing the encoded representation of the given
+ // character array.
+ //
+ public virtual byte[] GetBytes(char[] chars)
+ {
+ if (chars == null)
+ {
+ throw new ArgumentNullException(nameof(chars),
+ SR.ArgumentNull_Array);
+ }
+ return GetBytes(chars, 0, chars.Length);
+ }
+
+ // Returns a byte array containing the encoded representation of a range
+ // of characters in a character array.
+ //
+ public virtual byte[] GetBytes(char[] chars, int index, int count)
+ {
+ byte[] result = new byte[GetByteCount(chars, index, count)];
+ GetBytes(chars, index, count, result, 0);
+ return result;
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. An exception occurs if the byte array is not large
+ // enough to hold the complete encoding of the characters. The
+ // GetByteCount method can be used to determine the exact number of
+ // bytes that will be produced for a given range of characters.
+ // Alternatively, the GetMaxByteCount method can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+ public abstract int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex);
+
+ // Returns a byte array containing the encoded representation of the given
+ // string.
+ //
+ public virtual byte[] GetBytes(String s)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s),
+ SR.ArgumentNull_String);
+
+ int byteCount = GetByteCount(s);
+ byte[] bytes = new byte[byteCount];
+ int bytesReceived = GetBytes(s, 0, s.Length, bytes, 0);
+ Debug.Assert(byteCount == bytesReceived);
+ return bytes;
+ }
+
+ // Returns a byte array containing the encoded representation of the given
+ // string range.
+ //
+ public byte[] GetBytes(string s, int index, int count)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s),
+ SR.ArgumentNull_String);
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (index > s.Length - count)
+ throw new ArgumentOutOfRangeException(nameof(index),
+ SR.ArgumentOutOfRange_IndexCount);
+
+ unsafe
+ {
+ fixed (char* pChar = s)
+ {
+ int byteCount = GetByteCount(pChar + index, count);
+ if (byteCount == 0)
+ return Array.Empty<byte>();
+
+ byte[] bytes = new byte[byteCount];
+ fixed (byte* pBytes = &bytes[0])
+ {
+ int bytesReceived = GetBytes(pChar + index, count, pBytes, byteCount);
+ Debug.Assert(byteCount == bytesReceived);
+ }
+ return bytes;
+ }
+ }
+ }
+
+ public virtual int GetBytes(String s, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+ return GetBytes(s.ToCharArray(), charIndex, charCount, bytes, byteIndex);
+ }
+
+ // This is our internal workhorse
+ // Always validate parameters before calling internal version, which will only assert.
+ internal virtual unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, EncoderNLS encoder)
+ {
+ return GetBytes(chars, charCount, bytes, byteCount);
+ }
+
+ // We expect this to be the workhorse for NLS Encodings, but for existing
+ // ones we need a working (if slow) default implementation)
+ //
+ // WARNING WARNING WARNING
+ //
+ // WARNING: If this breaks it could be a security threat. Obviously we
+ // call this internally, so you need to make sure that your pointers, counts
+ // and indexes are correct when you call this method.
+ //
+ // In addition, we have internal code, which will be marked as "safe" calling
+ // this code. However this code is dependent upon the implementation of an
+ // external GetBytes() method, which could be overridden by a third party and
+ // the results of which cannot be guaranteed. We use that result to copy
+ // the byte[] to our byte* output buffer. If the result count was wrong, we
+ // could easily overflow our output buffer. Therefore we do an extra test
+ // when we copy the buffer so that we don't overflow byteCount either.
+
+ [CLSCompliant(false)]
+ public virtual unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount)
+ {
+ // Validate input parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? nameof(bytes) : nameof(chars),
+ SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? nameof(charCount) : nameof(byteCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Get the char array to convert
+ char[] arrChar = new char[charCount];
+
+ int index;
+ for (index = 0; index < charCount; index++)
+ arrChar[index] = chars[index];
+
+ // Get the byte array to fill
+ byte[] arrByte = new byte[byteCount];
+
+ // Do the work
+ int result = GetBytes(arrChar, 0, charCount, arrByte, 0);
+
+ Debug.Assert(result <= byteCount, "[Encoding.GetBytes]Returned more bytes than we have space for");
+
+ // Copy the byte array
+ // WARNING: We MUST make sure that we don't copy too many bytes. We can't
+ // rely on result because it could be a 3rd party implementation. We need
+ // to make sure we never copy more than byteCount bytes no matter the value
+ // of result
+ if (result < byteCount)
+ byteCount = result;
+
+ // Copy the data, don't overrun our array!
+ for (index = 0; index < byteCount; index++)
+ bytes[index] = arrByte[index];
+
+ return byteCount;
+ }
+
+ public virtual unsafe int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes)
+ {
+ fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars))
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ {
+ return GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length);
+ }
+ }
+
+ // Returns the number of characters produced by decoding the given byte
+ // array.
+ //
+ public virtual int GetCharCount(byte[] bytes)
+ {
+ if (bytes == null)
+ {
+ throw new ArgumentNullException(nameof(bytes),
+ SR.ArgumentNull_Array);
+ }
+ return GetCharCount(bytes, 0, bytes.Length);
+ }
+
+ // Returns the number of characters produced by decoding a range of bytes
+ // in a byte array.
+ //
+ public abstract int GetCharCount(byte[] bytes, int index, int count);
+
+ // We expect this to be the workhorse for NLS Encodings, but for existing
+ // ones we need a working (if slow) default implementation)
+ [CLSCompliant(false)]
+ public virtual unsafe int GetCharCount(byte* bytes, int count)
+ {
+ // Validate input parameters
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ byte[] arrbyte = new byte[count];
+ int index;
+
+ for (index = 0; index < count; index++)
+ arrbyte[index] = bytes[index];
+
+ return GetCharCount(arrbyte, 0, count);
+ }
+
+ public virtual unsafe int GetCharCount(ReadOnlySpan<byte> bytes)
+ {
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ {
+ return GetCharCount(bytesPtr, bytes.Length);
+ }
+ }
+
+ // This is our internal workhorse
+ // Always validate parameters before calling internal version, which will only assert.
+ internal virtual unsafe int GetCharCount(byte* bytes, int count, DecoderNLS decoder)
+ {
+ return GetCharCount(bytes, count);
+ }
+
+ // Returns a character array containing the decoded representation of a
+ // given byte array.
+ //
+ public virtual char[] GetChars(byte[] bytes)
+ {
+ if (bytes == null)
+ {
+ throw new ArgumentNullException(nameof(bytes),
+ SR.ArgumentNull_Array);
+ }
+ return GetChars(bytes, 0, bytes.Length);
+ }
+
+ // Returns a character array containing the decoded representation of a
+ // range of bytes in a byte array.
+ //
+ public virtual char[] GetChars(byte[] bytes, int index, int count)
+ {
+ char[] result = new char[GetCharCount(bytes, index, count)];
+ GetChars(bytes, index, count, result, 0);
+ return result;
+ }
+
+ // Decodes a range of bytes in a byte array into a range of characters in a
+ // character array. An exception occurs if the character array is not large
+ // enough to hold the complete decoding of the bytes. The
+ // GetCharCount method can be used to determine the exact number of
+ // characters that will be produced for a given range of bytes.
+ // Alternatively, the GetMaxCharCount method can be used to
+ // determine the maximum number of characters that will be produced for a
+ // given number of bytes, regardless of the actual byte values.
+ //
+
+ public abstract int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex);
+
+
+ // We expect this to be the workhorse for NLS Encodings, but for existing
+ // ones we need a working (if slow) default implementation)
+ //
+ // WARNING WARNING WARNING
+ //
+ // WARNING: If this breaks it could be a security threat. Obviously we
+ // call this internally, so you need to make sure that your pointers, counts
+ // and indexes are correct when you call this method.
+ //
+ // In addition, we have internal code, which will be marked as "safe" calling
+ // this code. However this code is dependent upon the implementation of an
+ // external GetChars() method, which could be overridden by a third party and
+ // the results of which cannot be guaranteed. We use that result to copy
+ // the char[] to our char* output buffer. If the result count was wrong, we
+ // could easily overflow our output buffer. Therefore we do an extra test
+ // when we copy the buffer so that we don't overflow charCount either.
+
+ [CLSCompliant(false)]
+ public virtual unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount)
+ {
+ // Validate input parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException(chars == null ? nameof(chars) : nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ if (byteCount < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((byteCount < 0 ? nameof(byteCount) : nameof(charCount)),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Get the byte array to convert
+ byte[] arrByte = new byte[byteCount];
+
+ int index;
+ for (index = 0; index < byteCount; index++)
+ arrByte[index] = bytes[index];
+
+ // Get the char array to fill
+ char[] arrChar = new char[charCount];
+
+ // Do the work
+ int result = GetChars(arrByte, 0, byteCount, arrChar, 0);
+
+ Debug.Assert(result <= charCount, "[Encoding.GetChars]Returned more chars than we have space for");
+
+ // Copy the char array
+ // WARNING: We MUST make sure that we don't copy too many chars. We can't
+ // rely on result because it could be a 3rd party implementation. We need
+ // to make sure we never copy more than charCount chars no matter the value
+ // of result
+ if (result < charCount)
+ charCount = result;
+
+ // Copy the data, don't overrun our array!
+ for (index = 0; index < charCount; index++)
+ chars[index] = arrChar[index];
+
+ return charCount;
+ }
+
+ public virtual unsafe int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars)
+ {
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars))
+ {
+ return GetChars(bytesPtr, bytes.Length, charsPtr, chars.Length);
+ }
+ }
+
+ // This is our internal workhorse
+ // Always validate parameters before calling internal version, which will only assert.
+ internal virtual unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, DecoderNLS decoder)
+ {
+ return GetChars(bytes, byteCount, chars, charCount);
+ }
+
+
+ [CLSCompliant(false)]
+ public unsafe string GetString(byte* bytes, int byteCount)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes), SR.ArgumentNull_Array);
+
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(byteCount), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return String.CreateStringFromEncoding(bytes, byteCount, this);
+ }
+
+ public unsafe string GetString(ReadOnlySpan<byte> bytes)
+ {
+ fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes))
+ {
+ return GetString(bytesPtr, bytes.Length);
+ }
+ }
+
+
+ // Returns the code page identifier of this encoding. The returned value is
+ // an integer between 0 and 65535 if the encoding has a code page
+ // identifier, or -1 if the encoding does not represent a code page.
+ //
+
+ public virtual int CodePage
+ {
+ get
+ {
+ return _codePage;
+ }
+ }
+
+ // IsAlwaysNormalized
+ // Returns true if the encoding is always normalized for the specified encoding form
+ public bool IsAlwaysNormalized()
+ {
+ return this.IsAlwaysNormalized(NormalizationForm.FormC);
+ }
+
+ public virtual bool IsAlwaysNormalized(NormalizationForm form)
+ {
+ // Assume false unless the encoding knows otherwise
+ return false;
+ }
+
+ // Returns a Decoder object for this encoding. The returned object
+ // can be used to decode a sequence of bytes into a sequence of characters.
+ // Contrary to the GetChars family of methods, a Decoder can
+ // convert partial sequences of bytes into partial sequences of characters
+ // by maintaining the appropriate state between the conversions.
+ //
+ // This default implementation returns a Decoder that simply
+ // forwards calls to the GetCharCount and GetChars methods to
+ // the corresponding methods of this encoding. Encodings that require state
+ // to be maintained between successive conversions should override this
+ // method and return an instance of an appropriate Decoder
+ // implementation.
+ //
+
+ public virtual Decoder GetDecoder()
+ {
+ return new DefaultDecoder(this);
+ }
+
+ // Returns an Encoder object for this encoding. The returned object
+ // can be used to encode a sequence of characters into a sequence of bytes.
+ // Contrary to the GetBytes family of methods, an Encoder can
+ // convert partial sequences of characters into partial sequences of bytes
+ // by maintaining the appropriate state between the conversions.
+ //
+ // This default implementation returns an Encoder that simply
+ // forwards calls to the GetByteCount and GetBytes methods to
+ // the corresponding methods of this encoding. Encodings that require state
+ // to be maintained between successive conversions should override this
+ // method and return an instance of an appropriate Encoder
+ // implementation.
+ //
+
+ public virtual Encoder GetEncoder()
+ {
+ return new DefaultEncoder(this);
+ }
+
+ // Returns the maximum number of bytes required to encode a given number of
+ // characters. This method can be used to determine an appropriate buffer
+ // size for byte arrays passed to the GetBytes method of this
+ // encoding or the GetBytes method of an Encoder for this
+ // encoding. All encodings must guarantee that no buffer overflow
+ // exceptions will occur if buffers are sized according to the results of
+ // this method.
+ //
+ // WARNING: If you're using something besides the default replacement encoder fallback,
+ // then you could have more bytes than this returned from an actual call to GetBytes().
+ //
+ public abstract int GetMaxByteCount(int charCount);
+
+ // Returns the maximum number of characters produced by decoding a given
+ // number of bytes. This method can be used to determine an appropriate
+ // buffer size for character arrays passed to the GetChars method of
+ // this encoding or the GetChars method of a Decoder for this
+ // encoding. All encodings must guarantee that no buffer overflow
+ // exceptions will occur if buffers are sized according to the results of
+ // this method.
+ //
+ public abstract int GetMaxCharCount(int byteCount);
+
+ // Returns a string containing the decoded representation of a given byte
+ // array.
+ //
+ public virtual String GetString(byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException(nameof(bytes),
+ SR.ArgumentNull_Array);
+
+ return GetString(bytes, 0, bytes.Length);
+ }
+
+ // Returns a string containing the decoded representation of a range of
+ // bytes in a byte array.
+ //
+ // Internally we override this for performance
+ //
+ public virtual String GetString(byte[] bytes, int index, int count)
+ {
+ return new String(GetChars(bytes, index, count));
+ }
+
+ // Returns an encoding for Unicode format. The returned encoding will be
+ // an instance of the UnicodeEncoding class.
+ //
+ // It will use little endian byte order, but will detect
+ // input in big endian if it finds a byte order mark per Unicode 2.0.
+
+ public static Encoding Unicode => UnicodeEncoding.s_littleEndianDefault;
+
+ // Returns an encoding for Unicode format. The returned encoding will be
+ // an instance of the UnicodeEncoding class.
+ //
+ // It will use big endian byte order, but will detect
+ // input in little endian if it finds a byte order mark per Unicode 2.0.
+
+ public static Encoding BigEndianUnicode => UnicodeEncoding.s_bigEndianDefault;
+
+ // Returns an encoding for the UTF-7 format. The returned encoding will be
+ // an instance of the UTF7Encoding class.
+
+ public static Encoding UTF7 => UTF7Encoding.s_default;
+
+ // Returns an encoding for the UTF-8 format. The returned encoding will be
+ // an instance of the UTF8Encoding class.
+
+ public static Encoding UTF8 => UTF8Encoding.s_default;
+
+ // Returns an encoding for the UTF-32 format. The returned encoding will be
+ // an instance of the UTF32Encoding class.
+
+ public static Encoding UTF32 => UTF32Encoding.s_default;
+
+ // Returns an encoding for the UTF-32 format. The returned encoding will be
+ // an instance of the UTF32Encoding class.
+ //
+ // It will use big endian byte order.
+
+ private static Encoding BigEndianUTF32 => UTF32Encoding.s_bigEndianDefault;
+
+ public override bool Equals(Object value)
+ {
+ Encoding that = value as Encoding;
+ if (that != null)
+ return (_codePage == that._codePage) &&
+ (EncoderFallback.Equals(that.EncoderFallback)) &&
+ (DecoderFallback.Equals(that.DecoderFallback));
+ return (false);
+ }
+
+
+ public override int GetHashCode()
+ {
+ return _codePage + this.EncoderFallback.GetHashCode() + this.DecoderFallback.GetHashCode();
+ }
+
+ internal virtual char[] GetBestFitUnicodeToBytesData()
+ {
+ // Normally we don't have any best fit data.
+ return Array.Empty<char>();
+ }
+
+ internal virtual char[] GetBestFitBytesToUnicodeData()
+ {
+ // Normally we don't have any best fit data.
+ return Array.Empty<char>();
+ }
+
+ internal void ThrowBytesOverflow()
+ {
+ // Special message to include fallback type in case fallback's GetMaxCharCount is broken
+ // This happens if user has implemented an encoder fallback with a broken GetMaxCharCount
+ throw new ArgumentException(
+ SR.Format(SR.Argument_EncodingConversionOverflowBytes, EncodingName, EncoderFallback.GetType()), "bytes");
+ }
+
+ internal void ThrowBytesOverflow(EncoderNLS encoder, bool nothingEncoded)
+ {
+ if (encoder == null || encoder._throwOnOverflow || nothingEncoded)
+ {
+ if (encoder != null && encoder.InternalHasFallbackBuffer)
+ encoder.FallbackBuffer.InternalReset();
+ // Special message to include fallback type in case fallback's GetMaxCharCount is broken
+ // This happens if user has implemented an encoder fallback with a broken GetMaxCharCount
+ ThrowBytesOverflow();
+ }
+
+ // If we didn't throw, we are in convert and have to remember our flushing
+ encoder.ClearMustFlush();
+ }
+
+ internal void ThrowCharsOverflow()
+ {
+ // Special message to include fallback type in case fallback's GetMaxCharCount is broken
+ // This happens if user has implemented a decoder fallback with a broken GetMaxCharCount
+ throw new ArgumentException(
+ SR.Format(SR.Argument_EncodingConversionOverflowChars, EncodingName, DecoderFallback.GetType()), "chars");
+ }
+
+ internal void ThrowCharsOverflow(DecoderNLS decoder, bool nothingDecoded)
+ {
+ if (decoder == null || decoder._throwOnOverflow || nothingDecoded)
+ {
+ if (decoder != null && decoder.InternalHasFallbackBuffer)
+ decoder.FallbackBuffer.InternalReset();
+
+ // Special message to include fallback type in case fallback's GetMaxCharCount is broken
+ // This happens if user has implemented a decoder fallback with a broken GetMaxCharCount
+ ThrowCharsOverflow();
+ }
+
+ // If we didn't throw, we are in convert and have to remember our flushing
+ decoder.ClearMustFlush();
+ }
+
+ internal sealed class DefaultEncoder : Encoder, IObjectReference
+ {
+ private Encoding _encoding;
+
+ public DefaultEncoder(Encoding encoding)
+ {
+ _encoding = encoding;
+ }
+
+ public Object GetRealObject(StreamingContext context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ // Returns the number of bytes the next call to GetBytes will
+ // produce if presented with the given range of characters and the given
+ // value of the flush parameter. The returned value takes into
+ // account the state in which the encoder was left following the last call
+ // to GetBytes. The state of the encoder is not affected by a call
+ // to this method.
+ //
+
+ public override int GetByteCount(char[] chars, int index, int count, bool flush)
+ {
+ return _encoding.GetByteCount(chars, index, count);
+ }
+
+ public unsafe override int GetByteCount(char* chars, int count, bool flush)
+ {
+ return _encoding.GetByteCount(chars, count);
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. The method encodes charCount characters from
+ // chars starting at index charIndex, storing the resulting
+ // bytes in bytes starting at index byteIndex. The encoding
+ // takes into account the state in which the encoder was left following the
+ // last call to this method. The flush parameter indicates whether
+ // the encoder should flush any shift-states and partial characters at the
+ // end of the conversion. To ensure correct termination of a sequence of
+ // blocks of encoded bytes, the last call to GetBytes should specify
+ // a value of true for the flush parameter.
+ //
+ // An exception occurs if the byte array is not large enough to hold the
+ // complete encoding of the characters. The GetByteCount method can
+ // be used to determine the exact number of bytes that will be produced for
+ // a given range of characters. Alternatively, the GetMaxByteCount
+ // method of the Encoding that produced this encoder can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+
+ public override int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex, bool flush)
+ {
+ return _encoding.GetBytes(chars, charIndex, charCount, bytes, byteIndex);
+ }
+
+ public unsafe override int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, bool flush)
+ {
+ return _encoding.GetBytes(chars, charCount, bytes, byteCount);
+ }
+ }
+
+ internal sealed class DefaultDecoder : Decoder, IObjectReference
+ {
+ private Encoding _encoding;
+
+ public DefaultDecoder(Encoding encoding)
+ {
+ _encoding = encoding;
+ }
+
+ public Object GetRealObject(StreamingContext context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ // Returns the number of characters the next call to GetChars will
+ // produce if presented with the given range of bytes. The returned value
+ // takes into account the state in which the decoder was left following the
+ // last call to GetChars. The state of the decoder is not affected
+ // by a call to this method.
+ //
+
+ public override int GetCharCount(byte[] bytes, int index, int count)
+ {
+ return GetCharCount(bytes, index, count, false);
+ }
+
+ public override int GetCharCount(byte[] bytes, int index, int count, bool flush)
+ {
+ return _encoding.GetCharCount(bytes, index, count);
+ }
+
+ public unsafe override int GetCharCount(byte* bytes, int count, bool flush)
+ {
+ // By default just call the encoding version, no flush by default
+ return _encoding.GetCharCount(bytes, count);
+ }
+
+ // Decodes a range of bytes in a byte array into a range of characters
+ // in a character array. The method decodes byteCount bytes from
+ // bytes starting at index byteIndex, storing the resulting
+ // characters in chars starting at index charIndex. The
+ // decoding takes into account the state in which the decoder was left
+ // following the last call to this method.
+ //
+ // An exception occurs if the character array is not large enough to
+ // hold the complete decoding of the bytes. The GetCharCount method
+ // can be used to determine the exact number of characters that will be
+ // produced for a given range of bytes. Alternatively, the
+ // GetMaxCharCount method of the Encoding that produced this
+ // decoder can be used to determine the maximum number of characters that
+ // will be produced for a given number of bytes, regardless of the actual
+ // byte values.
+ //
+
+ public override int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex)
+ {
+ return GetChars(bytes, byteIndex, byteCount, chars, charIndex, false);
+ }
+
+ public override int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex, bool flush)
+ {
+ return _encoding.GetChars(bytes, byteIndex, byteCount, chars, charIndex);
+ }
+
+ public unsafe override int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, bool flush)
+ {
+ // By default just call the encoding's version
+ return _encoding.GetChars(bytes, byteCount, chars, charCount);
+ }
+ }
+
+ internal class EncodingCharBuffer
+ {
+ private unsafe char* _chars;
+ private unsafe char* _charStart;
+ private unsafe char* _charEnd;
+ private int _charCountResult = 0;
+ private Encoding _enc;
+ private DecoderNLS _decoder;
+ private unsafe byte* _byteStart;
+ private unsafe byte* _byteEnd;
+ private unsafe byte* _bytes;
+ private DecoderFallbackBuffer _fallbackBuffer;
+
+ internal unsafe EncodingCharBuffer(Encoding enc, DecoderNLS decoder, char* charStart, int charCount,
+ byte* byteStart, int byteCount)
+ {
+ _enc = enc;
+ _decoder = decoder;
+
+ _chars = charStart;
+ _charStart = charStart;
+ _charEnd = charStart + charCount;
+
+ _byteStart = byteStart;
+ _bytes = byteStart;
+ _byteEnd = byteStart + byteCount;
+
+ if (_decoder == null)
+ _fallbackBuffer = enc.DecoderFallback.CreateFallbackBuffer();
+ else
+ _fallbackBuffer = _decoder.FallbackBuffer;
+
+ // If we're getting chars or getting char count we don't expect to have
+ // to remember fallbacks between calls (so it should be empty)
+ Debug.Assert(_fallbackBuffer.Remaining == 0,
+ "[Encoding.EncodingCharBuffer.EncodingCharBuffer]Expected empty fallback buffer for getchars/charcount");
+ _fallbackBuffer.InternalInitialize(_bytes, _charEnd);
+ }
+
+ internal unsafe bool AddChar(char ch, int numBytes)
+ {
+ if (_chars != null)
+ {
+ if (_chars >= _charEnd)
+ {
+ // Throw maybe
+ _bytes -= numBytes; // Didn't encode these bytes
+ _enc.ThrowCharsOverflow(_decoder, _bytes <= _byteStart); // Throw?
+ return false; // No throw, but no store either
+ }
+
+ *(_chars++) = ch;
+ }
+ _charCountResult++;
+ return true;
+ }
+
+ internal unsafe bool AddChar(char ch)
+ {
+ return AddChar(ch, 1);
+ }
+
+
+ internal unsafe bool AddChar(char ch1, char ch2, int numBytes)
+ {
+ // Need room for 2 chars
+ if (_chars >= _charEnd - 1)
+ {
+ // Throw maybe
+ _bytes -= numBytes; // Didn't encode these bytes
+ _enc.ThrowCharsOverflow(_decoder, _bytes <= _byteStart); // Throw?
+ return false; // No throw, but no store either
+ }
+ return AddChar(ch1, numBytes) && AddChar(ch2, numBytes);
+ }
+
+ internal unsafe void AdjustBytes(int count)
+ {
+ _bytes += count;
+ }
+
+ internal unsafe bool MoreData
+ {
+ get
+ {
+ return _bytes < _byteEnd;
+ }
+ }
+
+ // Do we have count more bytes?
+ internal unsafe bool EvenMoreData(int count)
+ {
+ return (_bytes <= _byteEnd - count);
+ }
+
+ // GetNextByte shouldn't be called unless the caller's already checked more data or even more data,
+ // but we'll double check just to make sure.
+ internal unsafe byte GetNextByte()
+ {
+ Debug.Assert(_bytes < _byteEnd, "[EncodingCharBuffer.GetNextByte]Expected more date");
+ if (_bytes >= _byteEnd)
+ return 0;
+ return *(_bytes++);
+ }
+
+ internal unsafe int BytesUsed
+ {
+ get
+ {
+ return (int)(_bytes - _byteStart);
+ }
+ }
+
+ internal unsafe bool Fallback(byte fallbackByte)
+ {
+ // Build our buffer
+ byte[] byteBuffer = new byte[] { fallbackByte };
+
+ // Do the fallback and add the data.
+ return Fallback(byteBuffer);
+ }
+
+ internal unsafe bool Fallback(byte byte1, byte byte2)
+ {
+ // Build our buffer
+ byte[] byteBuffer = new byte[] { byte1, byte2 };
+
+ // Do the fallback and add the data.
+ return Fallback(byteBuffer);
+ }
+
+ internal unsafe bool Fallback(byte byte1, byte byte2, byte byte3, byte byte4)
+ {
+ // Build our buffer
+ byte[] byteBuffer = new byte[] { byte1, byte2, byte3, byte4 };
+
+ // Do the fallback and add the data.
+ return Fallback(byteBuffer);
+ }
+
+ internal unsafe bool Fallback(byte[] byteBuffer)
+ {
+ // Do the fallback and add the data.
+ if (_chars != null)
+ {
+ char* pTemp = _chars;
+ if (_fallbackBuffer.InternalFallback(byteBuffer, _bytes, ref _chars) == false)
+ {
+ // Throw maybe
+ _bytes -= byteBuffer.Length; // Didn't use how many ever bytes we're falling back
+ _fallbackBuffer.InternalReset(); // We didn't use this fallback.
+ _enc.ThrowCharsOverflow(_decoder, _chars == _charStart); // Throw?
+ return false; // No throw, but no store either
+ }
+ _charCountResult += unchecked((int)(_chars - pTemp));
+ }
+ else
+ {
+ _charCountResult += _fallbackBuffer.InternalFallback(byteBuffer, _bytes);
+ }
+
+ return true;
+ }
+
+ internal unsafe int Count
+ {
+ get
+ {
+ return _charCountResult;
+ }
+ }
+ }
+
+ internal class EncodingByteBuffer
+ {
+ private unsafe byte* _bytes;
+ private unsafe byte* _byteStart;
+ private unsafe byte* _byteEnd;
+ private unsafe char* _chars;
+ private unsafe char* _charStart;
+ private unsafe char* _charEnd;
+ private int _byteCountResult = 0;
+ private Encoding _enc;
+ private EncoderNLS _encoder;
+ internal EncoderFallbackBuffer fallbackBuffer;
+
+ internal unsafe EncodingByteBuffer(Encoding inEncoding, EncoderNLS inEncoder,
+ byte* inByteStart, int inByteCount, char* inCharStart, int inCharCount)
+ {
+ _enc = inEncoding;
+ _encoder = inEncoder;
+
+ _charStart = inCharStart;
+ _chars = inCharStart;
+ _charEnd = inCharStart + inCharCount;
+
+ _bytes = inByteStart;
+ _byteStart = inByteStart;
+ _byteEnd = inByteStart + inByteCount;
+
+ if (_encoder == null)
+ this.fallbackBuffer = _enc.EncoderFallback.CreateFallbackBuffer();
+ else
+ {
+ this.fallbackBuffer = _encoder.FallbackBuffer;
+ // If we're not converting we must not have data in our fallback buffer
+ if (_encoder._throwOnOverflow && _encoder.InternalHasFallbackBuffer &&
+ this.fallbackBuffer.Remaining > 0)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty,
+ _encoder.Encoding.EncodingName, _encoder.Fallback.GetType()));
+ }
+ fallbackBuffer.InternalInitialize(_chars, _charEnd, _encoder, _bytes != null);
+ }
+
+ internal unsafe bool AddByte(byte b, int moreBytesExpected)
+ {
+ Debug.Assert(moreBytesExpected >= 0, "[EncodingByteBuffer.AddByte]expected non-negative moreBytesExpected");
+ if (_bytes != null)
+ {
+ if (_bytes >= _byteEnd - moreBytesExpected)
+ {
+ // Throw maybe. Check which buffer to back up (only matters if Converting)
+ this.MovePrevious(true); // Throw if necessary
+ return false; // No throw, but no store either
+ }
+
+ *(_bytes++) = b;
+ }
+ _byteCountResult++;
+ return true;
+ }
+
+ internal unsafe bool AddByte(byte b1)
+ {
+ return (AddByte(b1, 0));
+ }
+
+ internal unsafe bool AddByte(byte b1, byte b2)
+ {
+ return (AddByte(b1, b2, 0));
+ }
+
+ internal unsafe bool AddByte(byte b1, byte b2, int moreBytesExpected)
+ {
+ return (AddByte(b1, 1 + moreBytesExpected) && AddByte(b2, moreBytesExpected));
+ }
+
+ internal unsafe bool AddByte(byte b1, byte b2, byte b3)
+ {
+ return AddByte(b1, b2, b3, (int)0);
+ }
+
+ internal unsafe bool AddByte(byte b1, byte b2, byte b3, int moreBytesExpected)
+ {
+ return (AddByte(b1, 2 + moreBytesExpected) &&
+ AddByte(b2, 1 + moreBytesExpected) &&
+ AddByte(b3, moreBytesExpected));
+ }
+
+ internal unsafe bool AddByte(byte b1, byte b2, byte b3, byte b4)
+ {
+ return (AddByte(b1, 3) &&
+ AddByte(b2, 2) &&
+ AddByte(b3, 1) &&
+ AddByte(b4, 0));
+ }
+
+ internal unsafe void MovePrevious(bool bThrow)
+ {
+ if (fallbackBuffer.bFallingBack)
+ fallbackBuffer.MovePrevious(); // don't use last fallback
+ else
+ {
+ Debug.Assert(_chars > _charStart ||
+ ((bThrow == true) && (_bytes == _byteStart)),
+ "[EncodingByteBuffer.MovePrevious]expected previous data or throw");
+ if (_chars > _charStart)
+ _chars--; // don't use last char
+ }
+
+ if (bThrow)
+ _enc.ThrowBytesOverflow(_encoder, _bytes == _byteStart); // Throw? (and reset fallback if not converting)
+ }
+
+ internal unsafe bool Fallback(char charFallback)
+ {
+ // Do the fallback
+ return fallbackBuffer.InternalFallback(charFallback, ref _chars);
+ }
+
+ internal unsafe bool MoreData
+ {
+ get
+ {
+ // See if fallbackBuffer is not empty or if there's data left in chars buffer.
+ return ((fallbackBuffer.Remaining > 0) || (_chars < _charEnd));
+ }
+ }
+
+ internal unsafe char GetNextChar()
+ {
+ // See if there's something in our fallback buffer
+ char cReturn = fallbackBuffer.InternalGetNextChar();
+
+ // Nothing in the fallback buffer, return our normal data.
+ if (cReturn == 0)
+ {
+ if (_chars < _charEnd)
+ cReturn = *(_chars++);
+ }
+
+ return cReturn;
+ }
+
+ internal unsafe int CharsUsed
+ {
+ get
+ {
+ return (int)(_chars - _charStart);
+ }
+ }
+
+ internal unsafe int Count
+ {
+ get
+ {
+ return _byteCountResult;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/EncodingInfo.cs b/src/System.Private.CoreLib/shared/System/Text/EncodingInfo.cs
new file mode 100644
index 0000000000..99995f759b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/EncodingInfo.cs
@@ -0,0 +1,71 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+
+namespace System.Text
+{
+ public sealed class EncodingInfo
+ {
+ private int iCodePage; // Code Page #
+ private string strEncodingName; // Short name (web name)
+ private string strDisplayName; // Full localized name
+
+ internal EncodingInfo(int codePage, string name, string displayName)
+ {
+ iCodePage = codePage;
+ strEncodingName = name;
+ strDisplayName = displayName;
+ }
+
+
+ public int CodePage
+ {
+ get
+ {
+ return iCodePage;
+ }
+ }
+
+
+ public string Name
+ {
+ get
+ {
+ return strEncodingName;
+ }
+ }
+
+
+ public string DisplayName
+ {
+ get
+ {
+ return strDisplayName;
+ }
+ }
+
+
+ public Encoding GetEncoding()
+ {
+ return Encoding.GetEncoding(iCodePage);
+ }
+
+ public override bool Equals(Object value)
+ {
+ EncodingInfo that = value as EncodingInfo;
+ if (that != null)
+ {
+ return (this.CodePage == that.CodePage);
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return this.CodePage;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/EncodingNLS.cs b/src/System.Private.CoreLib/shared/System/Text/EncodingNLS.cs
new file mode 100644
index 0000000000..d5de9e553a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/EncodingNLS.cs
@@ -0,0 +1,298 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.Text
+{
+ // This class overrides Encoding with the things we need for our NLS Encodings
+ //
+ // All of the GetBytes/Chars GetByte/CharCount methods are just wrappers for the pointer
+ // plus decoder/encoder method that is our real workhorse. Note that this is an internal
+ // class, so our public classes cannot derive from this class. Because of this, all of the
+ // GetBytes/Chars GetByte/CharCount wrapper methods are duplicated in all of our public
+ // encodings, which currently include:
+ //
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, & UnicodeEncoding
+ //
+ // So if you change the wrappers in this class, you must change the wrappers in the other classes
+ // as well because they should have the same behavior.
+
+ internal abstract class EncodingNLS : Encoding
+ {
+ protected EncodingNLS(int codePage) : base(codePage)
+ {
+ }
+
+ // Returns the number of bytes required to encode a range of characters in
+ // a character array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+ public override unsafe int GetByteCount(char[] chars, int index, int count)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - index < count)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input, return 0, avoid fixed empty array problem
+ if (count == 0)
+ return 0;
+
+ // Just call the pointer version
+ fixed (char* pChars = chars)
+ return GetByteCount(pChars + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+ public override unsafe int GetByteCount(String s)
+ {
+ // Validate input
+ if (s==null)
+ throw new ArgumentNullException("s");
+
+ fixed (char* pChars = s)
+ return GetByteCount(pChars, s.Length, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ public override unsafe int GetByteCount(char* chars, int count)
+ {
+ // Validate Parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Call it with empty encoder
+ return GetByteCount(chars, count, null);
+ }
+
+ // Parent method is safe.
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ public override unsafe int GetBytes(String s, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ if (s == null || bytes == null)
+ throw new ArgumentNullException((s == null ? "s" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (s.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("s", SR.ArgumentOutOfRange_IndexCount);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = s) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. An exception occurs if the byte array is not large
+ // enough to hold the complete encoding of the characters. The
+ // GetByteCount method can be used to determine the exact number of
+ // bytes that will be produced for a given range of characters.
+ // Alternatively, the GetMaxByteCount method can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? "chars" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ // If nothing to encode return 0, avoid fixed problem
+ if (charCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = chars) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ // Remember that byteCount is # to decode, not size of array.
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetBytes(chars, charCount, bytes, byteCount, null);
+ }
+
+ // Returns the number of characters produced by decoding a range of bytes
+ // in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+ public override unsafe int GetCharCount(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input just return 0, fixed doesn't like 0 length arrays
+ if (count == 0)
+ return 0;
+
+ // Just call pointer version
+ fixed (byte* pBytes = bytes)
+ return GetCharCount(pBytes + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ public override unsafe int GetCharCount(byte* bytes, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetCharCount(bytes, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? "byteIndex" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if ( bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (charIndex < 0 || charIndex > chars.Length)
+ throw new ArgumentOutOfRangeException("charIndex", SR.ArgumentOutOfRange_Index);
+
+ // If no input, return 0 & avoid fixed problem
+ if (byteCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int charCount = chars.Length - charIndex;
+
+ fixed (byte* pBytes = bytes) fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ // Remember that charCount is # to decode, not size of array
+ return GetChars(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ public unsafe override int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetChars(bytes, byteCount, chars, charCount, null);
+ }
+
+ // Returns a string containing the decoded representation of a range of
+ // bytes in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+ public override unsafe String GetString(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Avoid problems with empty input buffer
+ if (count == 0) return String.Empty;
+
+ fixed (byte* pBytes = bytes)
+ return String.CreateStringFromEncoding(
+ pBytes + index, count, this);
+ }
+
+ public override Decoder GetDecoder()
+ {
+ return new DecoderNLS(this);
+ }
+
+ public override Encoder GetEncoder()
+ {
+ return new EncoderNLS(this);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/EncodingProvider.cs b/src/System.Private.CoreLib/shared/System/Text/EncodingProvider.cs
new file mode 100644
index 0000000000..ce8c3e0208
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/EncodingProvider.cs
@@ -0,0 +1,136 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace System.Text
+{
+ public abstract class EncodingProvider
+ {
+ public EncodingProvider() { }
+ public abstract Encoding GetEncoding(string name);
+ public abstract Encoding GetEncoding(int codepage);
+
+ // GetEncoding should return either valid encoding or null. shouldn't throw any exception except on null name
+ public virtual Encoding GetEncoding(string name, EncoderFallback encoderFallback, DecoderFallback decoderFallback)
+ {
+ Encoding enc = GetEncoding(name);
+ if (enc != null)
+ {
+ enc = (Encoding)GetEncoding(name).Clone();
+ enc.EncoderFallback = encoderFallback;
+ enc.DecoderFallback = decoderFallback;
+ }
+
+ return enc;
+ }
+
+ public virtual Encoding GetEncoding(int codepage, EncoderFallback encoderFallback, DecoderFallback decoderFallback)
+ {
+ Encoding enc = GetEncoding(codepage);
+ if (enc != null)
+ {
+ enc = (Encoding)GetEncoding(codepage).Clone();
+ enc.EncoderFallback = encoderFallback;
+ enc.DecoderFallback = decoderFallback;
+ }
+
+ return enc;
+ }
+
+ internal static void AddProvider(EncodingProvider provider)
+ {
+ if (provider == null)
+ throw new ArgumentNullException(nameof(provider));
+
+ lock (s_InternalSyncObject)
+ {
+ if (s_providers == null)
+ {
+ s_providers = new EncodingProvider[1] { provider };
+ return;
+ }
+
+ if (Array.IndexOf(s_providers, provider) >= 0)
+ {
+ return;
+ }
+
+ EncodingProvider[] providers = new EncodingProvider[s_providers.Length + 1];
+ Array.Copy(s_providers, providers, s_providers.Length);
+ providers[providers.Length - 1] = provider;
+ s_providers = providers;
+ }
+ }
+
+ internal static Encoding GetEncodingFromProvider(int codepage)
+ {
+ if (s_providers == null)
+ return null;
+
+ EncodingProvider[] providers = s_providers;
+ foreach (EncodingProvider provider in providers)
+ {
+ Encoding enc = provider.GetEncoding(codepage);
+ if (enc != null)
+ return enc;
+ }
+
+ return null;
+ }
+
+ internal static Encoding GetEncodingFromProvider(string encodingName)
+ {
+ if (s_providers == null)
+ return null;
+
+ EncodingProvider[] providers = s_providers;
+ foreach (EncodingProvider provider in providers)
+ {
+ Encoding enc = provider.GetEncoding(encodingName);
+ if (enc != null)
+ return enc;
+ }
+
+ return null;
+ }
+
+ internal static Encoding GetEncodingFromProvider(int codepage, EncoderFallback enc, DecoderFallback dec)
+ {
+ if (s_providers == null)
+ return null;
+
+ EncodingProvider[] providers = s_providers;
+ foreach (EncodingProvider provider in providers)
+ {
+ Encoding encing = provider.GetEncoding(codepage, enc, dec);
+ if (encing != null)
+ return encing;
+ }
+
+ return null;
+ }
+
+ internal static Encoding GetEncodingFromProvider(string encodingName, EncoderFallback enc, DecoderFallback dec)
+ {
+ if (s_providers == null)
+ return null;
+
+ EncodingProvider[] providers = s_providers;
+ foreach (EncodingProvider provider in providers)
+ {
+ Encoding encoding = provider.GetEncoding(encodingName, enc, dec);
+ if (encoding != null)
+ return encoding;
+ }
+
+ return null;
+ }
+
+ private static Object s_InternalSyncObject = new Object();
+ private static volatile EncodingProvider[] s_providers;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/Latin1Encoding.cs b/src/System.Private.CoreLib/shared/System/Text/Latin1Encoding.cs
new file mode 100644
index 0000000000..335eb76e3c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/Latin1Encoding.cs
@@ -0,0 +1,884 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+
+namespace System.Text
+{
+ //
+ // Latin1Encoding is a simple override to optimize the GetString version of Latin1Encoding.
+ // because of the best fit cases we can't do this when encoding the string, only when decoding
+ //
+ internal class Latin1Encoding : EncodingNLS
+ {
+ // Used by Encoding.Latin1 for lazy initialization
+ // The initialization code will not be run until a static member of the class is referenced
+ internal static readonly Latin1Encoding s_default = new Latin1Encoding();
+
+ // We only use the best-fit table, of which ASCII is a superset for us.
+ public Latin1Encoding() : base(Encoding.ISO_8859_1)
+ {
+ }
+
+ // GetByteCount
+ // Note: We start by assuming that the output will be the same as count. Having
+ // an encoder or fallback may change that assumption
+ internal override unsafe int GetByteCount(char* chars, int charCount, EncoderNLS encoder)
+ {
+ // Just need to ASSERT, this is called by something else internal that checked parameters already
+ Debug.Assert(charCount >= 0, "[Latin1Encoding.GetByteCount]count is negative");
+ Debug.Assert(chars != null, "[Latin1Encoding.GetByteCount]chars is null");
+
+ // Assert because we shouldn't be able to have a null encoder.
+ Debug.Assert(encoderFallback != null, "[Latin1Encoding.GetByteCount]Attempting to use null fallback encoder");
+
+ char charLeftOver = (char)0;
+
+ // If we have an encoder AND we aren't using default fallback,
+ // then we may have a complicated count.
+ EncoderReplacementFallback fallback;
+ if (encoder != null)
+ {
+ charLeftOver = encoder._charLeftOver;
+ Debug.Assert(charLeftOver == 0 || Char.IsHighSurrogate(charLeftOver),
+ "[Latin1Encoding.GetByteCount]leftover character should be high surrogate");
+
+ fallback = encoder.Fallback as EncoderReplacementFallback;
+
+ // Verify that we have no fallbackbuffer, for Latin1 its always empty, so just assert
+ Debug.Assert(!encoder._throwOnOverflow || !encoder.InternalHasFallbackBuffer ||
+ encoder.FallbackBuffer.Remaining == 0,
+ "[Latin1CodePageEncoding.GetByteCount]Expected empty fallback buffer");
+ }
+ else
+ fallback = this.EncoderFallback as EncoderReplacementFallback;
+
+ if ((fallback != null && fallback.MaxCharCount == 1)/* || bIsBestFit*/)
+ {
+ // Replacement fallback encodes surrogate pairs as two ?? (or two whatever), so return size is always
+ // same as input size.
+ // Note that no existing SBCS code pages map code points to supplimentary characters, so this is easy.
+
+ // We could however have 1 extra byte if the last call had an encoder and a funky fallback and
+ // if we don't use the funky fallback this time.
+
+ // Do we have an extra char left over from last time?
+ if (charLeftOver > 0)
+ charCount++;
+
+ return (charCount);
+ }
+
+ // Count is more complicated if you have a funky fallback
+ // For fallback we may need a fallback buffer, we know we're not default fallback
+ int byteCount = 0;
+
+ // Start by assuming default count, then +/- for fallback characters
+ char* charEnd = chars + charCount;
+
+ // For fallback we may need a fallback buffer, we know we aren't default fallback.
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ // We may have a left over character from last time, try and process it.
+ if (charLeftOver > 0)
+ {
+ // Initialize the buffer
+ Debug.Assert(encoder != null,
+ "[Latin1Encoding.GetByteCount]Expected encoder if we have charLeftOver");
+ fallbackBuffer = encoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(chars, charEnd, encoder, false);
+
+ // Since left over char was a surrogate, it'll have to be fallen back.
+ // Get Fallback
+ // This will fallback a pair if *chars is a low surrogate
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+ }
+
+ // Now we may have fallback char[] already from the encoder
+
+ // Go ahead and do it, including the fallback.
+ char ch;
+ while ((ch = (fallbackBuffer == null) ? '\0' : fallbackBuffer.InternalGetNextChar()) != 0 ||
+ chars < charEnd)
+ {
+ // First unwind any fallback
+ if (ch == 0)
+ {
+ // No fallback, just get next char
+ ch = *chars;
+ chars++;
+ }
+
+ // Check for fallback, this'll catch surrogate pairs too.
+ // no chars >= 0x100 are allowed.
+ if (ch > 0xff)
+ {
+ // Initialize the buffer
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(charEnd - charCount, charEnd, encoder, false);
+ }
+
+ // Get Fallback
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(ch, ref charsForFallback);
+ chars = charsForFallback;
+ continue;
+ }
+
+ // We'll use this one
+ byteCount++;
+ }
+
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[Latin1Encoding.GetByteCount]Expected Empty fallback buffer");
+
+ return byteCount;
+ }
+
+ internal override unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, EncoderNLS encoder)
+ {
+ // Just need to ASSERT, this is called by something else internal that checked parameters already
+ Debug.Assert(bytes != null, "[Latin1Encoding.GetBytes]bytes is null");
+ Debug.Assert(byteCount >= 0, "[Latin1Encoding.GetBytes]byteCount is negative");
+ Debug.Assert(chars != null, "[Latin1Encoding.GetBytes]chars is null");
+ Debug.Assert(charCount >= 0, "[Latin1Encoding.GetBytes]charCount is negative");
+
+ // Assert because we shouldn't be able to have a null encoder.
+ Debug.Assert(encoderFallback != null, "[Latin1Encoding.GetBytes]Attempting to use null encoder fallback");
+
+ // Get any left over characters & check fast or slower fallback type
+ char charLeftOver = (char)0;
+ EncoderReplacementFallback fallback = null;
+ if (encoder != null)
+ {
+ charLeftOver = encoder._charLeftOver;
+ fallback = encoder.Fallback as EncoderReplacementFallback;
+ Debug.Assert(charLeftOver == 0 || Char.IsHighSurrogate(charLeftOver),
+ "[Latin1Encoding.GetBytes]leftover character should be high surrogate");
+
+ // Verify that we have no fallbackbuffer, for ASCII its always empty, so just assert
+ Debug.Assert(!encoder._throwOnOverflow || !encoder.InternalHasFallbackBuffer ||
+ encoder.FallbackBuffer.Remaining == 0,
+ "[Latin1CodePageEncoding.GetBytes]Expected empty fallback buffer");
+ }
+ else
+ {
+ fallback = this.EncoderFallback as EncoderReplacementFallback;
+ }
+
+ // prepare our end
+ char* charEnd = chars + charCount;
+ byte* byteStart = bytes;
+ char* charStart = chars;
+
+ // See if we do the fast default or slightly slower fallback
+ if (fallback != null && fallback.MaxCharCount == 1)
+ {
+ // Fast version
+ char cReplacement = fallback.DefaultString[0];
+
+ // Check for replacements in range, otherwise fall back to slow version.
+ if (cReplacement <= (char)0xff)
+ {
+ // We should have exactly as many output bytes as input bytes, unless there's a left
+ // over character, in which case we may need one more.
+
+ // If we had a left over character will have to add a ? (This happens if they had a funky
+ // fallback last time, but not this time.) (We can't spit any out though
+ // because with fallback encoder each surrogate is treated as a seperate code point)
+ if (charLeftOver > 0)
+ {
+ // Have to have room
+ // Throw even if doing no throw version because this is just 1 char,
+ // so buffer will never be big enough
+ if (byteCount == 0)
+ ThrowBytesOverflow(encoder, true);
+
+ // This'll make sure we still have more room and also make sure our return value is correct.
+ *(bytes++) = (byte)cReplacement;
+ byteCount--; // We used one of the ones we were counting.
+ }
+
+ // This keeps us from overrunning our output buffer
+ if (byteCount < charCount)
+ {
+ // Throw or make buffer smaller?
+ ThrowBytesOverflow(encoder, byteCount < 1);
+
+ // Just use what we can
+ charEnd = chars + byteCount;
+ }
+
+ // We just do a quick copy
+ while (chars < charEnd)
+ {
+ char ch2 = *(chars++);
+ if (ch2 > 0x00ff) *(bytes++) = (byte)cReplacement;
+ else *(bytes++) = (byte)ch2;
+ }
+
+ // Clear encoder
+ if (encoder != null)
+ {
+ encoder._charLeftOver = (char)0;
+ encoder._charsUsed = (int)(chars - charStart);
+ }
+ return (int)(bytes - byteStart);
+ }
+ }
+
+ // Slower version, have to do real fallback.
+
+ // prepare our end
+ byte* byteEnd = bytes + byteCount;
+
+ // For fallback we may need a fallback buffer, we know we aren't default fallback, create & init it
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ // We may have a left over character from last time, try and process it.
+ if (charLeftOver > 0)
+ {
+ // Since left over char was a surrogate, it'll have to be fallen back.
+ // Get Fallback
+ Debug.Assert(encoder != null,
+ "[Latin1Encoding.GetBytes]Expected encoder if we have charLeftOver");
+ fallbackBuffer = encoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(chars, charEnd, encoder, true);
+
+ // Since left over char was a surrogate, it'll have to be fallen back.
+ // Get Fallback
+ // This will fallback a pair if *chars is a low surrogate
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (fallbackBuffer.Remaining > byteEnd - bytes)
+ {
+ // Throw it, if we don't have enough for this we never will
+ ThrowBytesOverflow(encoder, true);
+ }
+ }
+
+ // Now we may have fallback char[] already from the encoder fallback above
+
+ // Go ahead and do it, including the fallback.
+ char ch;
+ while ((ch = (fallbackBuffer == null) ? '\0' : fallbackBuffer.InternalGetNextChar()) != 0 ||
+ chars < charEnd)
+ {
+ // First unwind any fallback
+ if (ch == 0)
+ {
+ // No fallback, just get next char
+ ch = *chars;
+ chars++;
+ }
+
+ // Check for fallback, this'll catch surrogate pairs too.
+ // All characters >= 0x100 must fall back.
+ if (ch > 0xff)
+ {
+ // Initialize the buffer
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+ fallbackBuffer.InternalInitialize(charEnd - charCount, charEnd, encoder, true);
+ }
+
+ // Get Fallback
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(ch, ref charsForFallback);
+ chars = charsForFallback;
+
+ // Make sure we have enough room. Each fallback char will be 1 output char
+ // (or else cause a recursion exception)
+ if (fallbackBuffer.Remaining > byteEnd - bytes)
+ {
+ // Didn't use this char, throw it. Chars should've advanced by now
+ // If we had encoder fallback data it would've thrown before the loop
+ Debug.Assert(chars > charStart,
+ "[Latin1Encoding.GetBytes]Expected chars to have advanced (fallback case)");
+ chars--;
+ fallbackBuffer.InternalReset();
+
+ // Throw it
+ ThrowBytesOverflow(encoder, chars == charStart);
+ break;
+ }
+
+ continue;
+ }
+
+ // We'll use this one
+ // Bounds check
+ if (bytes >= byteEnd)
+ {
+ // didn't use this char, we'll throw or use buffer
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.bFallingBack == false,
+ "[Latin1Encoding.GetBytes]Expected fallback to have throw initially if insufficient space");
+ if (fallbackBuffer == null || fallbackBuffer.bFallingBack == false)
+ {
+ Debug.Assert(chars > charStart,
+ "[Latin1Encoding.GetBytes]Expected chars to have advanced (fallback case)");
+ chars--; // don't use last char
+ }
+ ThrowBytesOverflow(encoder, chars == charStart); // throw ?
+ break; // don't throw, stop
+ }
+
+ // Go ahead and add it
+ *bytes = unchecked((byte)ch);
+ bytes++;
+ }
+
+ // Need to do encoder stuff
+ if (encoder != null)
+ {
+ // Fallback stuck it in encoder if necessary, but we have to clear MustFlush cases
+ if (fallbackBuffer != null && !fallbackBuffer.bUsedEncoder)
+ // Clear it in case of MustFlush
+ encoder._charLeftOver = (char)0;
+
+ // Set our chars used count
+ encoder._charsUsed = (int)(chars - charStart);
+ }
+
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[Latin1Encoding.GetBytes]Expected Empty fallback buffer");
+
+ return (int)(bytes - byteStart);
+ }
+
+ // This is internal and called by something else,
+ internal override unsafe int GetCharCount(byte* bytes, int count, DecoderNLS decoder)
+ {
+ // Just assert, we're called internally so these should be safe, checked already
+ Debug.Assert(bytes != null, "[Latin1Encoding.GetCharCount]bytes is null");
+ Debug.Assert(count >= 0, "[Latin1Encoding.GetCharCount]byteCount is negative");
+
+ // Just return length, SBCS stay the same length because they don't map to surrogate
+ // pairs and we don't have to fallback because all latin1Encoding code points are unicode
+ return count;
+ }
+
+ internal override unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, DecoderNLS decoder)
+ {
+ // Just need to ASSERT, this is called by something else internal that checked parameters already
+ Debug.Assert(bytes != null, "[Latin1Encoding.GetChars]bytes is null");
+ Debug.Assert(byteCount >= 0, "[Latin1Encoding.GetChars]byteCount is negative");
+ Debug.Assert(chars != null, "[Latin1Encoding.GetChars]chars is null");
+ Debug.Assert(charCount >= 0, "[Latin1Encoding.GetChars]charCount is negative");
+
+ // Need byteCount chars, otherwise too small buffer
+ if (charCount < byteCount)
+ {
+ // Buffer too small. Do we throw?
+ ThrowCharsOverflow(decoder, charCount < 1);
+
+ // Don't throw, correct buffer size
+ byteCount = charCount;
+ }
+
+ // Do it our fast way
+ byte* byteEnd = bytes + byteCount;
+
+ // Quick loop, all bytes are the same as chars, so no fallbacks for latin1
+ while (bytes < byteEnd)
+ {
+ *(chars) = unchecked((char)*(bytes));
+ chars++;
+ bytes++;
+ }
+
+ // Might need to know input bytes used
+ if (decoder != null)
+ decoder._bytesUsed = byteCount;
+
+ // Converted sequence is same length as input, so output charsUsed is same as byteCount;
+ return byteCount;
+ }
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ if (charCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(charCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Characters would be # of characters + 1 in case high surrogate is ? * max fallback
+ long byteCount = (long)charCount + 1;
+
+ if (EncoderFallback.MaxCharCount > 1)
+ byteCount *= EncoderFallback.MaxCharCount;
+
+ // 1 to 1 for most characters. Only surrogates with fallbacks have less.
+
+ if (byteCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GetByteCountOverflow);
+ return (int)byteCount;
+ }
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(byteCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Just return length, SBCS stay the same length because they don't map to surrogate
+ long charCount = (long)byteCount;
+
+ // 1 to 1 for most characters. Only surrogates with fallbacks have less, unknown fallbacks could be longer.
+ if (DecoderFallback.MaxCharCount > 1)
+ charCount *= DecoderFallback.MaxCharCount;
+
+ if (charCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(byteCount), SR.ArgumentOutOfRange_GetCharCountOverflow);
+
+ return (int)charCount;
+ }
+
+ // True if and only if the encoding only uses single byte code points. (Ie, ASCII, 1252, etc)
+ public override bool IsSingleByte
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public override bool IsAlwaysNormalized(NormalizationForm form)
+ {
+ // Latin-1 contains precomposed characters, so normal for Form C.
+ // Since some are composed, not normal for D & KD.
+ // Also some letters like 0x00A8 (spacing diarisis) have compatibility decompositions, so false for KD & KC.
+
+ // Only true for form C.
+ return (form == NormalizationForm.FormC);
+ }
+ // Since our best fit table is small we'll hard code it
+ internal override char[] GetBestFitUnicodeToBytesData()
+ {
+ // Get our best fit data
+ return Latin1Encoding.arrayCharBestFit;
+ }
+
+ // Best fit for ASCII, and since it works for ASCII, we use it for latin1 as well.
+ private static readonly char[] arrayCharBestFit =
+ {
+// The first many are in case you wanted to use this for ASCIIEncoding, which we don't need to do any more.
+// (char)0x00a0, (char)0x0020, // No-Break Space -> Space
+// (char)0x00a1, (char)0x0021, // Inverted Exclamation Mark -> !
+// (char)0x00a2, (char)0x0063, // Cent Sign -> c
+// (char)0x00a3, (char)0x003f, // Pound Sign
+// (char)0x00a4, (char)0x0024, // Currency Sign -> $
+// (char)0x00a5, (char)0x0059, // Yen Sign -> Y
+// (char)0x00a6, (char)0x007c, // Broken Bar -> |
+// (char)0x00a7, (char)0x003f, // Section Sign
+// (char)0x00a8, (char)0x003f, // Diaeresis
+// (char)0x00a9, (char)0x0043, // Copyright Sign -> C
+// (char)0x00aa, (char)0x0061, // Feminine Ordinal Indicator -> a
+// (char)0x00ab, (char)0x003c, // Left-Pointing Double Angle Quotation Mark -> <
+// (char)0x00ac, (char)0x003f, // Not Sign
+// (char)0x00ad, (char)0x002d, // Soft Hyphen -> -
+// (char)0x00ae, (char)0x0052, // Registered Sign -> R
+// (char)0x00af, (char)0x003f, // Macron
+// (char)0x00b0, (char)0x003f, // Degree Sign
+// (char)0x00b1, (char)0x003f, // Plus-Minus Sign
+// (char)0x00b2, (char)0x0032, // Superscript Two -> 2
+// (char)0x00b3, (char)0x0033, // Superscript Three -> 3
+// (char)0x00b4, (char)0x003f, // Acute Accent
+// (char)0x00b5, (char)0x003f, // Micro Sign
+// (char)0x00b6, (char)0x003f, // Pilcrow Sign
+// (char)0x00b7, (char)0x002e, // Middle Dot -> .
+// (char)0x00b8, (char)0x002c, // Cedilla -> ,
+// (char)0x00b9, (char)0x0031, // Superscript One -> 1
+// (char)0x00ba, (char)0x006f, // Masculine Ordinal Indicator -> o
+// (char)0x00bb, (char)0x003e, // Right-Pointing Double Angle Quotation Mark -> >
+// (char)0x00bc, (char)0x003f, // Vulgar Fraction One Quarter
+// (char)0x00bd, (char)0x003f, // Vulgar Fraction One Half
+// (char)0x00be, (char)0x003f, // Vulgar Fraction Three Quarters
+// (char)0x00bf, (char)0x003f, // Inverted Question Mark
+// (char)0x00c0, (char)0x0041, // Latin Capital Letter A With Grave -> A
+// (char)0x00c1, (char)0x0041, // Latin Capital Letter A With Acute -> A
+// (char)0x00c2, (char)0x0041, // Latin Capital Letter A With Circumflex -> A
+// (char)0x00c3, (char)0x0041, // Latin Capital Letter A With Tilde -> A
+// (char)0x00c4, (char)0x0041, // Latin Capital Letter A With Diaeresis -> A
+// (char)0x00c5, (char)0x0041, // Latin Capital Letter A With Ring Above -> A
+// (char)0x00c6, (char)0x0041, // Latin Capital Ligature Ae -> A
+// (char)0x00c7, (char)0x0043, // Latin Capital Letter C With Cedilla -> C
+// (char)0x00c8, (char)0x0045, // Latin Capital Letter E With Grave -> E
+// (char)0x00c9, (char)0x0045, // Latin Capital Letter E With Acute -> E
+// (char)0x00ca, (char)0x0045, // Latin Capital Letter E With Circumflex -> E
+// (char)0x00cb, (char)0x0045, // Latin Capital Letter E With Diaeresis -> E
+// (char)0x00cc, (char)0x0049, // Latin Capital Letter I With Grave -> I
+// (char)0x00cd, (char)0x0049, // Latin Capital Letter I With Acute -> I
+// (char)0x00ce, (char)0x0049, // Latin Capital Letter I With Circumflex -> I
+// (char)0x00cf, (char)0x0049, // Latin Capital Letter I With Diaeresis -> I
+// (char)0x00d0, (char)0x0044, // Latin Capital Letter Eth -> D
+// (char)0x00d1, (char)0x004e, // Latin Capital Letter N With Tilde -> N
+// (char)0x00d2, (char)0x004f, // Latin Capital Letter O With Grave -> O
+// (char)0x00d3, (char)0x004f, // Latin Capital Letter O With Acute -> O
+// (char)0x00d4, (char)0x004f, // Latin Capital Letter O With Circumflex -> O
+// (char)0x00d5, (char)0x004f, // Latin Capital Letter O With Tilde -> O
+// (char)0x00d6, (char)0x004f, // Latin Capital Letter O With Diaeresis -> O
+// (char)0x00d7, (char)0x003f, // Multiplication Sign
+// (char)0x00d8, (char)0x004f, // Latin Capital Letter O With Stroke -> O
+// (char)0x00d9, (char)0x0055, // Latin Capital Letter U With Grave -> U
+// (char)0x00da, (char)0x0055, // Latin Capital Letter U With Acute -> U
+// (char)0x00db, (char)0x0055, // Latin Capital Letter U With Circumflex -> U
+// (char)0x00dc, (char)0x0055, // Latin Capital Letter U With Diaeresis -> U
+// (char)0x00dd, (char)0x0059, // Latin Capital Letter Y With Acute -> Y
+// (char)0x00de, (char)0x003f, // Latin Capital Letter Thorn
+// (char)0x00df, (char)0x003f, // Latin Small Letter Sharp S
+// (char)0x00e0, (char)0x0061, // Latin Small Letter A With Grave -> a
+// (char)0x00e1, (char)0x0061, // Latin Small Letter A With Acute -> a
+// (char)0x00e2, (char)0x0061, // Latin Small Letter A With Circumflex -> a
+// (char)0x00e3, (char)0x0061, // Latin Small Letter A With Tilde -> a
+// (char)0x00e4, (char)0x0061, // Latin Small Letter A With Diaeresis -> a
+// (char)0x00e5, (char)0x0061, // Latin Small Letter A With Ring Above -> a
+// (char)0x00e6, (char)0x0061, // Latin Small Ligature Ae -> a
+// (char)0x00e7, (char)0x0063, // Latin Small Letter C With Cedilla -> c
+// (char)0x00e8, (char)0x0065, // Latin Small Letter E With Grave -> e
+// (char)0x00e9, (char)0x0065, // Latin Small Letter E With Acute -> e
+// (char)0x00ea, (char)0x0065, // Latin Small Letter E With Circumflex -> e
+// (char)0x00eb, (char)0x0065, // Latin Small Letter E With Diaeresis -> e
+// (char)0x00ec, (char)0x0069, // Latin Small Letter I With Grave -> i
+// (char)0x00ed, (char)0x0069, // Latin Small Letter I With Acute -> i
+// (char)0x00ee, (char)0x0069, // Latin Small Letter I With Circumflex -> i
+// (char)0x00ef, (char)0x0069, // Latin Small Letter I With Diaeresis -> i
+// (char)0x00f0, (char)0x003f, // Latin Small Letter Eth
+// (char)0x00f1, (char)0x006e, // Latin Small Letter N With Tilde -> n
+// (char)0x00f2, (char)0x006f, // Latin Small Letter O With Grave -> o
+// (char)0x00f3, (char)0x006f, // Latin Small Letter O With Acute -> o
+// (char)0x00f4, (char)0x006f, // Latin Small Letter O With Circumflex -> o
+// (char)0x00f5, (char)0x006f, // Latin Small Letter O With Tilde -> o
+// (char)0x00f6, (char)0x006f, // Latin Small Letter O With Diaeresis -> o
+// (char)0x00f7, (char)0x003f, // Division Sign
+// (char)0x00f8, (char)0x006f, // Latin Small Letter O With Stroke -> o
+// (char)0x00f9, (char)0x0075, // Latin Small Letter U With Grave -> u
+// (char)0x00fa, (char)0x0075, // Latin Small Letter U With Acute -> u
+// (char)0x00fb, (char)0x0075, // Latin Small Letter U With Circumflex -> u
+// (char)0x00fc, (char)0x0075, // Latin Small Letter U With Diaeresis -> u
+// (char)0x00fd, (char)0x0079, // Latin Small Letter Y With Acute -> y
+// (char)0x00fe, (char)0x003f, // Latin Small Letter Thorn
+// (char)0x00ff, (char)0x0079, // Latin Small Letter Y With Diaeresis -> y
+ (char)0x0100, (char)0x0041, // Latin Capital Letter A With Macron -> A
+ (char)0x0101, (char)0x0061, // Latin Small Letter A With Macron -> a
+ (char)0x0102, (char)0x0041, // Latin Capital Letter A With Breve -> A
+ (char)0x0103, (char)0x0061, // Latin Small Letter A With Breve -> a
+ (char)0x0104, (char)0x0041, // Latin Capital Letter A With Ogonek -> A
+ (char)0x0105, (char)0x0061, // Latin Small Letter A With Ogonek -> a
+ (char)0x0106, (char)0x0043, // Latin Capital Letter C With Acute -> C
+ (char)0x0107, (char)0x0063, // Latin Small Letter C With Acute -> c
+ (char)0x0108, (char)0x0043, // Latin Capital Letter C With Circumflex -> C
+ (char)0x0109, (char)0x0063, // Latin Small Letter C With Circumflex -> c
+ (char)0x010a, (char)0x0043, // Latin Capital Letter C With Dot Above -> C
+ (char)0x010b, (char)0x0063, // Latin Small Letter C With Dot Above -> c
+ (char)0x010c, (char)0x0043, // Latin Capital Letter C With Caron -> C
+ (char)0x010d, (char)0x0063, // Latin Small Letter C With Caron -> c
+ (char)0x010e, (char)0x0044, // Latin Capital Letter D With Caron -> D
+ (char)0x010f, (char)0x0064, // Latin Small Letter D With Caron -> d
+ (char)0x0110, (char)0x0044, // Latin Capital Letter D With Stroke -> D
+ (char)0x0111, (char)0x0064, // Latin Small Letter D With Stroke -> d
+ (char)0x0112, (char)0x0045, // Latin Capital Letter E With Macron -> E
+ (char)0x0113, (char)0x0065, // Latin Small Letter E With Macron -> e
+ (char)0x0114, (char)0x0045, // Latin Capital Letter E With Breve -> E
+ (char)0x0115, (char)0x0065, // Latin Small Letter E With Breve -> e
+ (char)0x0116, (char)0x0045, // Latin Capital Letter E With Dot Above -> E
+ (char)0x0117, (char)0x0065, // Latin Small Letter E With Dot Above -> e
+ (char)0x0118, (char)0x0045, // Latin Capital Letter E With Ogonek -> E
+ (char)0x0119, (char)0x0065, // Latin Small Letter E With Ogonek -> e
+ (char)0x011a, (char)0x0045, // Latin Capital Letter E With Caron -> E
+ (char)0x011b, (char)0x0065, // Latin Small Letter E With Caron -> e
+ (char)0x011c, (char)0x0047, // Latin Capital Letter G With Circumflex -> G
+ (char)0x011d, (char)0x0067, // Latin Small Letter G With Circumflex -> g
+ (char)0x011e, (char)0x0047, // Latin Capital Letter G With Breve -> G
+ (char)0x011f, (char)0x0067, // Latin Small Letter G With Breve -> g
+ (char)0x0120, (char)0x0047, // Latin Capital Letter G With Dot Above -> G
+ (char)0x0121, (char)0x0067, // Latin Small Letter G With Dot Above -> g
+ (char)0x0122, (char)0x0047, // Latin Capital Letter G With Cedilla -> G
+ (char)0x0123, (char)0x0067, // Latin Small Letter G With Cedilla -> g
+ (char)0x0124, (char)0x0048, // Latin Capital Letter H With Circumflex -> H
+ (char)0x0125, (char)0x0068, // Latin Small Letter H With Circumflex -> h
+ (char)0x0126, (char)0x0048, // Latin Capital Letter H With Stroke -> H
+ (char)0x0127, (char)0x0068, // Latin Small Letter H With Stroke -> h
+ (char)0x0128, (char)0x0049, // Latin Capital Letter I With Tilde -> I
+ (char)0x0129, (char)0x0069, // Latin Small Letter I With Tilde -> i
+ (char)0x012a, (char)0x0049, // Latin Capital Letter I With Macron -> I
+ (char)0x012b, (char)0x0069, // Latin Small Letter I With Macron -> i
+ (char)0x012c, (char)0x0049, // Latin Capital Letter I With Breve -> I
+ (char)0x012d, (char)0x0069, // Latin Small Letter I With Breve -> i
+ (char)0x012e, (char)0x0049, // Latin Capital Letter I With Ogonek -> I
+ (char)0x012f, (char)0x0069, // Latin Small Letter I With Ogonek -> i
+ (char)0x0130, (char)0x0049, // Latin Capital Letter I With Dot Above -> I
+ (char)0x0131, (char)0x0069, // Latin Small Letter Dotless I -> i
+ (char)0x0134, (char)0x004a, // Latin Capital Letter J With Circumflex -> J
+ (char)0x0135, (char)0x006a, // Latin Small Letter J With Circumflex -> j
+ (char)0x0136, (char)0x004b, // Latin Capital Letter K With Cedilla -> K
+ (char)0x0137, (char)0x006b, // Latin Small Letter K With Cedilla -> k
+ (char)0x0139, (char)0x004c, // Latin Capital Letter L With Acute -> L
+ (char)0x013a, (char)0x006c, // Latin Small Letter L With Acute -> l
+ (char)0x013b, (char)0x004c, // Latin Capital Letter L With Cedilla -> L
+ (char)0x013c, (char)0x006c, // Latin Small Letter L With Cedilla -> l
+ (char)0x013d, (char)0x004c, // Latin Capital Letter L With Caron -> L
+ (char)0x013e, (char)0x006c, // Latin Small Letter L With Caron -> l
+ (char)0x0141, (char)0x004c, // Latin Capital Letter L With Stroke -> L
+ (char)0x0142, (char)0x006c, // Latin Small Letter L With Stroke -> l
+ (char)0x0143, (char)0x004e, // Latin Capital Letter N With Acute -> N
+ (char)0x0144, (char)0x006e, // Latin Small Letter N With Acute -> n
+ (char)0x0145, (char)0x004e, // Latin Capital Letter N With Cedilla -> N
+ (char)0x0146, (char)0x006e, // Latin Small Letter N With Cedilla -> n
+ (char)0x0147, (char)0x004e, // Latin Capital Letter N With Caron -> N
+ (char)0x0148, (char)0x006e, // Latin Small Letter N With Caron -> n
+ (char)0x014c, (char)0x004f, // Latin Capital Letter O With Macron -> O
+ (char)0x014d, (char)0x006f, // Latin Small Letter O With Macron -> o
+ (char)0x014e, (char)0x004f, // Latin Capital Letter O With Breve -> O
+ (char)0x014f, (char)0x006f, // Latin Small Letter O With Breve -> o
+ (char)0x0150, (char)0x004f, // Latin Capital Letter O With Double Acute -> O
+ (char)0x0151, (char)0x006f, // Latin Small Letter O With Double Acute -> o
+ (char)0x0152, (char)0x004f, // Latin Capital Ligature Oe -> O
+ (char)0x0153, (char)0x006f, // Latin Small Ligature Oe -> o
+ (char)0x0154, (char)0x0052, // Latin Capital Letter R With Acute -> R
+ (char)0x0155, (char)0x0072, // Latin Small Letter R With Acute -> r
+ (char)0x0156, (char)0x0052, // Latin Capital Letter R With Cedilla -> R
+ (char)0x0157, (char)0x0072, // Latin Small Letter R With Cedilla -> r
+ (char)0x0158, (char)0x0052, // Latin Capital Letter R With Caron -> R
+ (char)0x0159, (char)0x0072, // Latin Small Letter R With Caron -> r
+ (char)0x015a, (char)0x0053, // Latin Capital Letter S With Acute -> S
+ (char)0x015b, (char)0x0073, // Latin Small Letter S With Acute -> s
+ (char)0x015c, (char)0x0053, // Latin Capital Letter S With Circumflex -> S
+ (char)0x015d, (char)0x0073, // Latin Small Letter S With Circumflex -> s
+ (char)0x015e, (char)0x0053, // Latin Capital Letter S With Cedilla -> S
+ (char)0x015f, (char)0x0073, // Latin Small Letter S With Cedilla -> s
+ (char)0x0160, (char)0x0053, // Latin Capital Letter S With Caron -> S
+ (char)0x0161, (char)0x0073, // Latin Small Letter S With Caron -> s
+ (char)0x0162, (char)0x0054, // Latin Capital Letter T With Cedilla -> T
+ (char)0x0163, (char)0x0074, // Latin Small Letter T With Cedilla -> t
+ (char)0x0164, (char)0x0054, // Latin Capital Letter T With Caron -> T
+ (char)0x0165, (char)0x0074, // Latin Small Letter T With Caron -> t
+ (char)0x0166, (char)0x0054, // Latin Capital Letter T With Stroke -> T
+ (char)0x0167, (char)0x0074, // Latin Small Letter T With Stroke -> t
+ (char)0x0168, (char)0x0055, // Latin Capital Letter U With Tilde -> U
+ (char)0x0169, (char)0x0075, // Latin Small Letter U With Tilde -> u
+ (char)0x016a, (char)0x0055, // Latin Capital Letter U With Macron -> U
+ (char)0x016b, (char)0x0075, // Latin Small Letter U With Macron -> u
+ (char)0x016c, (char)0x0055, // Latin Capital Letter U With Breve -> U
+ (char)0x016d, (char)0x0075, // Latin Small Letter U With Breve -> u
+ (char)0x016e, (char)0x0055, // Latin Capital Letter U With Ring Above -> U
+ (char)0x016f, (char)0x0075, // Latin Small Letter U With Ring Above -> u
+ (char)0x0170, (char)0x0055, // Latin Capital Letter U With Double Acute -> U
+ (char)0x0171, (char)0x0075, // Latin Small Letter U With Double Acute -> u
+ (char)0x0172, (char)0x0055, // Latin Capital Letter U With Ogonek -> U
+ (char)0x0173, (char)0x0075, // Latin Small Letter U With Ogonek -> u
+ (char)0x0174, (char)0x0057, // Latin Capital Letter W With Circumflex -> W
+ (char)0x0175, (char)0x0077, // Latin Small Letter W With Circumflex -> w
+ (char)0x0176, (char)0x0059, // Latin Capital Letter Y With Circumflex -> Y
+ (char)0x0177, (char)0x0079, // Latin Small Letter Y With Circumflex -> y
+ (char)0x0178, (char)0x0059, // Latin Capital Letter Y With Diaeresis -> Y
+ (char)0x0179, (char)0x005a, // Latin Capital Letter Z With Acute -> Z
+ (char)0x017a, (char)0x007a, // Latin Small Letter Z With Acute -> z
+ (char)0x017b, (char)0x005a, // Latin Capital Letter Z With Dot Above -> Z
+ (char)0x017c, (char)0x007a, // Latin Small Letter Z With Dot Above -> z
+ (char)0x017d, (char)0x005a, // Latin Capital Letter Z With Caron -> Z
+ (char)0x017e, (char)0x007a, // Latin Small Letter Z With Caron -> z
+ (char)0x0180, (char)0x0062, // Latin Small Letter B With Stroke -> b
+ (char)0x0189, (char)0x0044, // Latin Capital Letter African D -> D
+ (char)0x0191, (char)0x0046, // Latin Capital Letter F With Hook -> F
+ (char)0x0192, (char)0x0066, // Latin Small Letter F With Hook -> f
+ (char)0x0197, (char)0x0049, // Latin Capital Letter I With Stroke -> I
+ (char)0x019a, (char)0x006c, // Latin Small Letter L With Bar -> l
+ (char)0x019f, (char)0x004f, // Latin Capital Letter O With Middle Tilde -> O
+ (char)0x01a0, (char)0x004f, // Latin Capital Letter O With Horn -> O
+ (char)0x01a1, (char)0x006f, // Latin Small Letter O With Horn -> o
+ (char)0x01ab, (char)0x0074, // Latin Small Letter T With Palatal Hook -> t
+ (char)0x01ae, (char)0x0054, // Latin Capital Letter T With Retroflex Hook -> T
+ (char)0x01af, (char)0x0055, // Latin Capital Letter U With Horn -> U
+ (char)0x01b0, (char)0x0075, // Latin Small Letter U With Horn -> u
+ (char)0x01b6, (char)0x007a, // Latin Small Letter Z With Stroke -> z
+ (char)0x01cd, (char)0x0041, // Latin Capital Letter A With Caron -> A
+ (char)0x01ce, (char)0x0061, // Latin Small Letter A With Caron -> a
+ (char)0x01cf, (char)0x0049, // Latin Capital Letter I With Caron -> I
+ (char)0x01d0, (char)0x0069, // Latin Small Letter I With Caron -> i
+ (char)0x01d1, (char)0x004f, // Latin Capital Letter O With Caron -> O
+ (char)0x01d2, (char)0x006f, // Latin Small Letter O With Caron -> o
+ (char)0x01d3, (char)0x0055, // Latin Capital Letter U With Caron -> U
+ (char)0x01d4, (char)0x0075, // Latin Small Letter U With Caron -> u
+ (char)0x01d5, (char)0x0055, // Latin Capital Letter U With Diaeresis And Macron -> U
+ (char)0x01d6, (char)0x0075, // Latin Small Letter U With Diaeresis And Macron -> u
+ (char)0x01d7, (char)0x0055, // Latin Capital Letter U With Diaeresis And Acute -> U
+ (char)0x01d8, (char)0x0075, // Latin Small Letter U With Diaeresis And Acute -> u
+ (char)0x01d9, (char)0x0055, // Latin Capital Letter U With Diaeresis And Caron -> U
+ (char)0x01da, (char)0x0075, // Latin Small Letter U With Diaeresis And Caron -> u
+ (char)0x01db, (char)0x0055, // Latin Capital Letter U With Diaeresis And Grave -> U
+ (char)0x01dc, (char)0x0075, // Latin Small Letter U With Diaeresis And Grave -> u
+ (char)0x01de, (char)0x0041, // Latin Capital Letter A With Diaeresis And Macron -> A
+ (char)0x01df, (char)0x0061, // Latin Small Letter A With Diaeresis And Macron -> a
+ (char)0x01e4, (char)0x0047, // Latin Capital Letter G With Stroke -> G
+ (char)0x01e5, (char)0x0067, // Latin Small Letter G With Stroke -> g
+ (char)0x01e6, (char)0x0047, // Latin Capital Letter G With Caron -> G
+ (char)0x01e7, (char)0x0067, // Latin Small Letter G With Caron -> g
+ (char)0x01e8, (char)0x004b, // Latin Capital Letter K With Caron -> K
+ (char)0x01e9, (char)0x006b, // Latin Small Letter K With Caron -> k
+ (char)0x01ea, (char)0x004f, // Latin Capital Letter O With Ogonek -> O
+ (char)0x01eb, (char)0x006f, // Latin Small Letter O With Ogonek -> o
+ (char)0x01ec, (char)0x004f, // Latin Capital Letter O With Ogonek And Macron -> O
+ (char)0x01ed, (char)0x006f, // Latin Small Letter O With Ogonek And Macron -> o
+ (char)0x01f0, (char)0x006a, // Latin Small Letter J With Caron -> j
+ (char)0x0261, (char)0x0067, // Latin Small Letter Script G -> g
+ (char)0x02b9, (char)0x0027, // Modifier Letter Prime -> '
+ (char)0x02ba, (char)0x0022, // Modifier Letter Double Prime -> "
+ (char)0x02bc, (char)0x0027, // Modifier Letter Apostrophe -> '
+ (char)0x02c4, (char)0x005e, // Modifier Letter Up Arrowhead -> ^
+ (char)0x02c6, (char)0x005e, // Modifier Letter Circumflex Accent -> ^
+ (char)0x02c8, (char)0x0027, // Modifier Letter Vertical Line -> '
+ (char)0x02c9, (char)0x003f, // Modifier Letter Macron
+ (char)0x02ca, (char)0x003f, // Modifier Letter Acute Accent
+ (char)0x02cb, (char)0x0060, // Modifier Letter Grave Accent -> `
+ (char)0x02cd, (char)0x005f, // Modifier Letter Low Macron -> _
+ (char)0x02da, (char)0x003f, // Ring Above
+ (char)0x02dc, (char)0x007e, // Small Tilde -> ~
+ (char)0x0300, (char)0x0060, // Combining Grave Accent -> `
+ (char)0x0302, (char)0x005e, // Combining Circumflex Accent -> ^
+ (char)0x0303, (char)0x007e, // Combining Tilde -> ~
+ (char)0x030e, (char)0x0022, // Combining Double Vertical Line Above -> "
+ (char)0x0331, (char)0x005f, // Combining Macron Below -> _
+ (char)0x0332, (char)0x005f, // Combining Low Line -> _
+ (char)0x2000, (char)0x0020, // En Quad
+ (char)0x2001, (char)0x0020, // Em Quad
+ (char)0x2002, (char)0x0020, // En Space
+ (char)0x2003, (char)0x0020, // Em Space
+ (char)0x2004, (char)0x0020, // Three-Per-Em Space
+ (char)0x2005, (char)0x0020, // Four-Per-Em Space
+ (char)0x2006, (char)0x0020, // Six-Per-Em Space
+ (char)0x2010, (char)0x002d, // Hyphen -> -
+ (char)0x2011, (char)0x002d, // Non-Breaking Hyphen -> -
+ (char)0x2013, (char)0x002d, // En Dash -> -
+ (char)0x2014, (char)0x002d, // Em Dash -> -
+ (char)0x2018, (char)0x0027, // Left Single Quotation Mark -> '
+ (char)0x2019, (char)0x0027, // Right Single Quotation Mark -> '
+ (char)0x201a, (char)0x002c, // Single Low-9 Quotation Mark -> ,
+ (char)0x201c, (char)0x0022, // Left Double Quotation Mark -> "
+ (char)0x201d, (char)0x0022, // Right Double Quotation Mark -> "
+ (char)0x201e, (char)0x0022, // Double Low-9 Quotation Mark -> "
+ (char)0x2020, (char)0x003f, // Dagger
+ (char)0x2021, (char)0x003f, // Double Dagger
+ (char)0x2022, (char)0x002e, // Bullet -> .
+ (char)0x2026, (char)0x002e, // Horizontal Ellipsis -> .
+ (char)0x2030, (char)0x003f, // Per Mille Sign
+ (char)0x2032, (char)0x0027, // Prime -> '
+ (char)0x2035, (char)0x0060, // Reversed Prime -> `
+ (char)0x2039, (char)0x003c, // Single Left-Pointing Angle Quotation Mark -> <
+ (char)0x203a, (char)0x003e, // Single Right-Pointing Angle Quotation Mark -> >
+ (char)0x2122, (char)0x0054, // Trade Mark Sign -> T
+ (char)0xff01, (char)0x0021, // Fullwidth Exclamation Mark -> !
+ (char)0xff02, (char)0x0022, // Fullwidth Quotation Mark -> "
+ (char)0xff03, (char)0x0023, // Fullwidth Number Sign -> #
+ (char)0xff04, (char)0x0024, // Fullwidth Dollar Sign -> $
+ (char)0xff05, (char)0x0025, // Fullwidth Percent Sign -> %
+ (char)0xff06, (char)0x0026, // Fullwidth Ampersand -> &
+ (char)0xff07, (char)0x0027, // Fullwidth Apostrophe -> '
+ (char)0xff08, (char)0x0028, // Fullwidth Left Parenthesis -> (
+ (char)0xff09, (char)0x0029, // Fullwidth Right Parenthesis -> )
+ (char)0xff0a, (char)0x002a, // Fullwidth Asterisk -> *
+ (char)0xff0b, (char)0x002b, // Fullwidth Plus Sign -> +
+ (char)0xff0c, (char)0x002c, // Fullwidth Comma -> ,
+ (char)0xff0d, (char)0x002d, // Fullwidth Hyphen-Minus -> -
+ (char)0xff0e, (char)0x002e, // Fullwidth Full Stop -> .
+ (char)0xff0f, (char)0x002f, // Fullwidth Solidus -> /
+ (char)0xff10, (char)0x0030, // Fullwidth Digit Zero -> 0
+ (char)0xff11, (char)0x0031, // Fullwidth Digit One -> 1
+ (char)0xff12, (char)0x0032, // Fullwidth Digit Two -> 2
+ (char)0xff13, (char)0x0033, // Fullwidth Digit Three -> 3
+ (char)0xff14, (char)0x0034, // Fullwidth Digit Four -> 4
+ (char)0xff15, (char)0x0035, // Fullwidth Digit Five -> 5
+ (char)0xff16, (char)0x0036, // Fullwidth Digit Six -> 6
+ (char)0xff17, (char)0x0037, // Fullwidth Digit Seven -> 7
+ (char)0xff18, (char)0x0038, // Fullwidth Digit Eight -> 8
+ (char)0xff19, (char)0x0039, // Fullwidth Digit Nine -> 9
+ (char)0xff1a, (char)0x003a, // Fullwidth Colon -> :
+ (char)0xff1b, (char)0x003b, // Fullwidth Semicolon -> ;
+ (char)0xff1c, (char)0x003c, // Fullwidth Less-Than Sign -> <
+ (char)0xff1d, (char)0x003d, // Fullwidth Equals Sign -> =
+ (char)0xff1e, (char)0x003e, // Fullwidth Greater-Than Sign -> >
+ (char)0xff1f, (char)0x003f, // Fullwidth Question Mark
+ (char)0xff20, (char)0x0040, // Fullwidth Commercial At -> @
+ (char)0xff21, (char)0x0041, // Fullwidth Latin Capital Letter A -> A
+ (char)0xff22, (char)0x0042, // Fullwidth Latin Capital Letter B -> B
+ (char)0xff23, (char)0x0043, // Fullwidth Latin Capital Letter C -> C
+ (char)0xff24, (char)0x0044, // Fullwidth Latin Capital Letter D -> D
+ (char)0xff25, (char)0x0045, // Fullwidth Latin Capital Letter E -> E
+ (char)0xff26, (char)0x0046, // Fullwidth Latin Capital Letter F -> F
+ (char)0xff27, (char)0x0047, // Fullwidth Latin Capital Letter G -> G
+ (char)0xff28, (char)0x0048, // Fullwidth Latin Capital Letter H -> H
+ (char)0xff29, (char)0x0049, // Fullwidth Latin Capital Letter I -> I
+ (char)0xff2a, (char)0x004a, // Fullwidth Latin Capital Letter J -> J
+ (char)0xff2b, (char)0x004b, // Fullwidth Latin Capital Letter K -> K
+ (char)0xff2c, (char)0x004c, // Fullwidth Latin Capital Letter L -> L
+ (char)0xff2d, (char)0x004d, // Fullwidth Latin Capital Letter M -> M
+ (char)0xff2e, (char)0x004e, // Fullwidth Latin Capital Letter N -> N
+ (char)0xff2f, (char)0x004f, // Fullwidth Latin Capital Letter O -> O
+ (char)0xff30, (char)0x0050, // Fullwidth Latin Capital Letter P -> P
+ (char)0xff31, (char)0x0051, // Fullwidth Latin Capital Letter Q -> Q
+ (char)0xff32, (char)0x0052, // Fullwidth Latin Capital Letter R -> R
+ (char)0xff33, (char)0x0053, // Fullwidth Latin Capital Letter S -> S
+ (char)0xff34, (char)0x0054, // Fullwidth Latin Capital Letter T -> T
+ (char)0xff35, (char)0x0055, // Fullwidth Latin Capital Letter U -> U
+ (char)0xff36, (char)0x0056, // Fullwidth Latin Capital Letter V -> V
+ (char)0xff37, (char)0x0057, // Fullwidth Latin Capital Letter W -> W
+ (char)0xff38, (char)0x0058, // Fullwidth Latin Capital Letter X -> X
+ (char)0xff39, (char)0x0059, // Fullwidth Latin Capital Letter Y -> Y
+ (char)0xff3a, (char)0x005a, // Fullwidth Latin Capital Letter Z -> Z
+ (char)0xff3b, (char)0x005b, // Fullwidth Left Square Bracket -> [
+ (char)0xff3c, (char)0x005c, // Fullwidth Reverse Solidus -> \
+ (char)0xff3d, (char)0x005d, // Fullwidth Right Square Bracket -> ]
+ (char)0xff3e, (char)0x005e, // Fullwidth Circumflex Accent -> ^
+ (char)0xff3f, (char)0x005f, // Fullwidth Low Line -> _
+ (char)0xff40, (char)0x0060, // Fullwidth Grave Accent -> `
+ (char)0xff41, (char)0x0061, // Fullwidth Latin Small Letter A -> a
+ (char)0xff42, (char)0x0062, // Fullwidth Latin Small Letter B -> b
+ (char)0xff43, (char)0x0063, // Fullwidth Latin Small Letter C -> c
+ (char)0xff44, (char)0x0064, // Fullwidth Latin Small Letter D -> d
+ (char)0xff45, (char)0x0065, // Fullwidth Latin Small Letter E -> e
+ (char)0xff46, (char)0x0066, // Fullwidth Latin Small Letter F -> f
+ (char)0xff47, (char)0x0067, // Fullwidth Latin Small Letter G -> g
+ (char)0xff48, (char)0x0068, // Fullwidth Latin Small Letter H -> h
+ (char)0xff49, (char)0x0069, // Fullwidth Latin Small Letter I -> i
+ (char)0xff4a, (char)0x006a, // Fullwidth Latin Small Letter J -> j
+ (char)0xff4b, (char)0x006b, // Fullwidth Latin Small Letter K -> k
+ (char)0xff4c, (char)0x006c, // Fullwidth Latin Small Letter L -> l
+ (char)0xff4d, (char)0x006d, // Fullwidth Latin Small Letter M -> m
+ (char)0xff4e, (char)0x006e, // Fullwidth Latin Small Letter N -> n
+ (char)0xff4f, (char)0x006f, // Fullwidth Latin Small Letter O -> o
+ (char)0xff50, (char)0x0070, // Fullwidth Latin Small Letter P -> p
+ (char)0xff51, (char)0x0071, // Fullwidth Latin Small Letter Q -> q
+ (char)0xff52, (char)0x0072, // Fullwidth Latin Small Letter R -> r
+ (char)0xff53, (char)0x0073, // Fullwidth Latin Small Letter S -> s
+ (char)0xff54, (char)0x0074, // Fullwidth Latin Small Letter T -> t
+ (char)0xff55, (char)0x0075, // Fullwidth Latin Small Letter U -> u
+ (char)0xff56, (char)0x0076, // Fullwidth Latin Small Letter V -> v
+ (char)0xff57, (char)0x0077, // Fullwidth Latin Small Letter W -> w
+ (char)0xff58, (char)0x0078, // Fullwidth Latin Small Letter X -> x
+ (char)0xff59, (char)0x0079, // Fullwidth Latin Small Letter Y -> y
+ (char)0xff5a, (char)0x007a, // Fullwidth Latin Small Letter Z -> z
+ (char)0xff5b, (char)0x007b, // Fullwidth Left Curly Bracket -> {
+ (char)0xff5c, (char)0x007c, // Fullwidth Vertical Line -> |
+ (char)0xff5d, (char)0x007d, // Fullwidth Right Curly Bracket -> }
+ (char)0xff5e, (char)0x007e // Fullwidth Tilde -> ~
+ };
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/NormalizationForm.cs b/src/System.Private.CoreLib/shared/System/Text/NormalizationForm.cs
new file mode 100644
index 0000000000..976756251a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/NormalizationForm.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Text
+{
+ public enum NormalizationForm
+ {
+ FormC = 1,
+ FormD = 2,
+ FormKC = 5,
+ FormKD = 6
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/StringBuilder.Debug.cs b/src/System.Private.CoreLib/shared/System/Text/StringBuilder.Debug.cs
new file mode 100644
index 0000000000..a62c4777ad
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/StringBuilder.Debug.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Text
+{
+ public sealed partial class StringBuilder
+ {
+ private void ShowChunks(int maxChunksToShow = 10)
+ {
+ int count = 0;
+ StringBuilder head = this, current = this;
+ while (current != null)
+ {
+ if (count < maxChunksToShow)
+ {
+ count++;
+ }
+ else
+ {
+ head = head.m_ChunkPrevious;
+ }
+ current = current.m_ChunkPrevious;
+ }
+ current = head;
+ string[] chunks = new string[count];
+ for (int i = count; i > 0; i--)
+ {
+ chunks[i - 1] = new string(current.m_ChunkChars).Replace('\0', '.');
+ current = current.m_ChunkPrevious;
+ }
+ Debug.WriteLine('|' + string.Join('|', chunks) + '|');
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs b/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs
new file mode 100644
index 0000000000..8c1e045216
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/StringBuilder.cs
@@ -0,0 +1,2445 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System.Runtime;
+using System.Runtime.Serialization;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Security;
+using System.Threading;
+using System.Globalization;
+using System.Diagnostics;
+using System.Collections.Generic;
+
+namespace System.Text
+{
+ // This class represents a mutable string. It is convenient for situations in
+ // which it is desirable to modify a string, perhaps by removing, replacing, or
+ // inserting characters, without creating a new String subsequent to
+ // each modification.
+ //
+ // The methods contained within this class do not return a new StringBuilder
+ // object unless specified otherwise. This class may be used in conjunction with the String
+ // class to carry out modifications upon strings.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed partial class StringBuilder : ISerializable
+ {
+ // A StringBuilder is internally represented as a linked list of blocks each of which holds
+ // a chunk of the string. It turns out string as a whole can also be represented as just a chunk,
+ // so that is what we do.
+
+ /// <summary>
+ /// The character buffer for this chunk.
+ /// </summary>
+ internal char[] m_ChunkChars;
+
+ /// <summary>
+ /// The chunk that logically precedes this chunk.
+ /// </summary>
+ internal StringBuilder m_ChunkPrevious;
+
+ /// <summary>
+ /// The number of characters in this chunk.
+ /// This is the number of elements in <see cref="m_ChunkChars"/> that are in use, from the start of the buffer.
+ /// </summary>
+ internal int m_ChunkLength;
+
+ /// <summary>
+ /// The logical offset of this chunk's characters in the string it is a part of.
+ /// This is the sum of the number of characters in preceding blocks.
+ /// </summary>
+ internal int m_ChunkOffset;
+
+ /// <summary>
+ /// The maximum capacity this builder is allowed to have.
+ /// </summary>
+ internal int m_MaxCapacity;
+
+ /// <summary>
+ /// The default capacity of a <see cref="StringBuilder"/>.
+ /// </summary>
+ internal const int DefaultCapacity = 16;
+
+ private const string CapacityField = "Capacity"; // Do not rename (binary serialization)
+ private const string MaxCapacityField = "m_MaxCapacity"; // Do not rename (binary serialization)
+ private const string StringValueField = "m_StringValue"; // Do not rename (binary serialization)
+ private const string ThreadIDField = "m_currentThread"; // Do not rename (binary serialization)
+
+ // We want to keep chunk arrays out of large object heap (< 85K bytes ~ 40K chars) to be sure.
+ // Making the maximum chunk size big means less allocation code called, but also more waste
+ // in unused characters and slower inserts / replaces (since you do need to slide characters over
+ // within a buffer).
+ internal const int MaxChunkSize = 8000;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ public StringBuilder()
+ {
+ m_MaxCapacity = int.MaxValue;
+ m_ChunkChars = new char[DefaultCapacity];
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="capacity">The initial capacity of this builder.</param>
+ public StringBuilder(int capacity)
+ : this(capacity, int.MaxValue)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="value">The initial contents of this builder.</param>
+ public StringBuilder(string value)
+ : this(value, DefaultCapacity)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="value">The initial contents of this builder.</param>
+ /// <param name="capacity">The initial capacity of this builder.</param>
+ public StringBuilder(string value, int capacity)
+ : this(value, 0, value?.Length ?? 0, capacity)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="value">The initial contents of this builder.</param>
+ /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
+ /// <param name="length">The number of characters to read in <paramref name="value"/>.</param>
+ /// <param name="capacity">The initial capacity of this builder.</param>
+ public StringBuilder(string value, int startIndex, int length, int capacity)
+ {
+ if (capacity < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(capacity)));
+ }
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.Format(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(length)));
+ }
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+ }
+
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+ if (startIndex > value.Length - length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
+ }
+
+ m_MaxCapacity = int.MaxValue;
+ if (capacity == 0)
+ {
+ capacity = DefaultCapacity;
+ }
+ capacity = Math.Max(capacity, length);
+
+ m_ChunkChars = new char[capacity];
+ m_ChunkLength = length;
+
+ unsafe
+ {
+ fixed (char* sourcePtr = value)
+ {
+ ThreadSafeCopy(sourcePtr + startIndex, m_ChunkChars, 0, length);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="capacity">The initial capacity of this builder.</param>
+ /// <param name="maxCapacity">The maximum capacity of this builder.</param>
+ public StringBuilder(int capacity, int maxCapacity)
+ {
+ if (capacity > maxCapacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity);
+ }
+ if (maxCapacity < 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(maxCapacity), SR.ArgumentOutOfRange_SmallMaxCapacity);
+ }
+ if (capacity < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(capacity)));
+ }
+
+ if (capacity == 0)
+ {
+ capacity = Math.Min(DefaultCapacity, maxCapacity);
+ }
+
+ m_MaxCapacity = maxCapacity;
+ m_ChunkChars = new char[capacity];
+ }
+
+ private StringBuilder(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ int persistedCapacity = 0;
+ string persistedString = null;
+ int persistedMaxCapacity = Int32.MaxValue;
+ bool capacityPresent = false;
+
+ // Get the data
+ SerializationInfoEnumerator enumerator = info.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ switch (enumerator.Name)
+ {
+ case MaxCapacityField:
+ persistedMaxCapacity = info.GetInt32(MaxCapacityField);
+ break;
+ case StringValueField:
+ persistedString = info.GetString(StringValueField);
+ break;
+ case CapacityField:
+ persistedCapacity = info.GetInt32(CapacityField);
+ capacityPresent = true;
+ break;
+ default:
+ // Ignore other fields for forwards-compatibility.
+ break;
+ }
+ }
+
+ // Check values and set defaults
+ if (persistedString == null)
+ {
+ persistedString = string.Empty;
+ }
+ if (persistedMaxCapacity < 1 || persistedString.Length > persistedMaxCapacity)
+ {
+ throw new SerializationException(SR.Serialization_StringBuilderMaxCapacity);
+ }
+
+ if (!capacityPresent)
+ {
+ // StringBuilder in V1.X did not persist the Capacity, so this is a valid legacy code path.
+ persistedCapacity = Math.Min(Math.Max(DefaultCapacity, persistedString.Length), persistedMaxCapacity);
+ }
+
+ if (persistedCapacity < 0 || persistedCapacity < persistedString.Length || persistedCapacity > persistedMaxCapacity)
+ {
+ throw new SerializationException(SR.Serialization_StringBuilderCapacity);
+ }
+
+ // Assign
+ m_MaxCapacity = persistedMaxCapacity;
+ m_ChunkChars = new char[persistedCapacity];
+ persistedString.CopyTo(0, m_ChunkChars, 0, persistedString.Length);
+ m_ChunkLength = persistedString.Length;
+ m_ChunkPrevious = null;
+ AssertInvariants();
+ }
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ AssertInvariants();
+ info.AddValue(MaxCapacityField, m_MaxCapacity);
+ info.AddValue(CapacityField, Capacity);
+ info.AddValue(StringValueField, ToString());
+ // Note: persist "m_currentThread" to be compatible with old versions
+ info.AddValue(ThreadIDField, 0);
+ }
+
+ [System.Diagnostics.Conditional("DEBUG")]
+ private void AssertInvariants()
+ {
+ Debug.Assert(m_ChunkOffset + m_ChunkChars.Length >= m_ChunkOffset, "The length of the string is greater than int.MaxValue.");
+
+ StringBuilder currentBlock = this;
+ int maxCapacity = this.m_MaxCapacity;
+ for (;;)
+ {
+ // All blocks have the same max capacity.
+ Debug.Assert(currentBlock.m_MaxCapacity == maxCapacity);
+ Debug.Assert(currentBlock.m_ChunkChars != null);
+
+ Debug.Assert(currentBlock.m_ChunkLength <= currentBlock.m_ChunkChars.Length);
+ Debug.Assert(currentBlock.m_ChunkLength >= 0);
+ Debug.Assert(currentBlock.m_ChunkOffset >= 0);
+
+ StringBuilder prevBlock = currentBlock.m_ChunkPrevious;
+ if (prevBlock == null)
+ {
+ Debug.Assert(currentBlock.m_ChunkOffset == 0);
+ break;
+ }
+ // There are no gaps in the blocks.
+ Debug.Assert(currentBlock.m_ChunkOffset == prevBlock.m_ChunkOffset + prevBlock.m_ChunkLength);
+ currentBlock = prevBlock;
+ }
+ }
+
+ public int Capacity
+ {
+ get { return m_ChunkChars.Length + m_ChunkOffset; }
+ set
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NegativeCapacity);
+ }
+ if (value > MaxCapacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_Capacity);
+ }
+ if (value < Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_SmallCapacity);
+ }
+
+ if (Capacity != value)
+ {
+ int newLen = value - m_ChunkOffset;
+ char[] newArray = new char[newLen];
+ Array.Copy(m_ChunkChars, 0, newArray, 0, m_ChunkLength);
+ m_ChunkChars = newArray;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the maximum capacity this builder is allowed to have.
+ /// </summary>
+ public int MaxCapacity => m_MaxCapacity;
+
+ /// <summary>
+ /// Ensures that the capacity of this builder is at least the specified value.
+ /// </summary>
+ /// <param name="capacity">The new capacity for this builder.</param>
+ /// <remarks>
+ /// If <paramref name="capacity"/> is less than or equal to the current capacity of
+ /// this builder, the capacity remains unchanged.
+ /// </remarks>
+ public int EnsureCapacity(int capacity)
+ {
+ if (capacity < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NegativeCapacity);
+ }
+
+ if (Capacity < capacity)
+ Capacity = capacity;
+ return Capacity;
+ }
+
+ public override String ToString()
+ {
+ AssertInvariants();
+
+ if (Length == 0)
+ {
+ return string.Empty;
+ }
+
+ string result = string.FastAllocateString(Length);
+ StringBuilder chunk = this;
+ unsafe
+ {
+ fixed (char* destinationPtr = result)
+ {
+ do
+ {
+ if (chunk.m_ChunkLength > 0)
+ {
+ // Copy these into local variables so that they are stable even in the presence of race conditions
+ char[] sourceArray = chunk.m_ChunkChars;
+ int chunkOffset = chunk.m_ChunkOffset;
+ int chunkLength = chunk.m_ChunkLength;
+
+ // Check that we will not overrun our boundaries.
+ if ((uint)(chunkLength + chunkOffset) <= (uint)result.Length && (uint)chunkLength <= (uint)sourceArray.Length)
+ {
+ fixed (char* sourcePtr = &sourceArray[0])
+ string.wstrcpy(destinationPtr + chunkOffset, sourcePtr, chunkLength);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(chunkLength), SR.ArgumentOutOfRange_Index);
+ }
+ }
+ chunk = chunk.m_ChunkPrevious;
+ }
+ while (chunk != null);
+
+ return result;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates a string from a substring of this builder.
+ /// </summary>
+ /// <param name="startIndex">The index to start in this builder.</param>
+ /// <param name="length">The number of characters to read in this builder.</param>
+ public string ToString(int startIndex, int length)
+ {
+ int currentLength = this.Length;
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+ }
+ if (startIndex > currentLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndexLargerThanLength);
+ }
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
+ }
+ if (startIndex > currentLength - length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
+ }
+
+ AssertInvariants();
+ string result = string.FastAllocateString(length);
+ unsafe
+ {
+ fixed (char* destinationPtr = result)
+ {
+ this.CopyTo(startIndex, new Span<char>(destinationPtr, length), length);
+ return result;
+ }
+ }
+ }
+
+ public StringBuilder Clear()
+ {
+ this.Length = 0;
+ return this;
+ }
+
+ /// <summary>
+ /// Gets or sets the length of this builder.
+ /// </summary>
+ public int Length
+ {
+ get
+ {
+ return m_ChunkOffset + m_ChunkLength;
+ }
+ set
+ {
+ //If the new length is less than 0 or greater than our Maximum capacity, bail.
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NegativeLength);
+ }
+
+ if (value > MaxCapacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_SmallCapacity);
+ }
+
+ if (value == 0 && m_ChunkPrevious == null)
+ {
+ m_ChunkLength = 0;
+ m_ChunkOffset = 0;
+ return;
+ }
+
+ int delta = value - Length;
+ if (delta > 0)
+ {
+ // Pad ourselves with null characters.
+ Append('\0', delta);
+ }
+ else
+ {
+ StringBuilder chunk = FindChunkForIndex(value);
+ if (chunk != this)
+ {
+ // Avoid possible infinite capacity growth. See https://github.com/dotnet/coreclr/pull/16926
+ int capacityToPreserve = Math.Min(Capacity, Math.Max(Length * 6 / 5, m_ChunkChars.Length));
+ int newLen = capacityToPreserve - chunk.m_ChunkOffset;
+ if (newLen > chunk.m_ChunkChars.Length)
+ {
+ // We crossed a chunk boundary when reducing the Length. We must replace this middle-chunk with a new larger chunk,
+ // to ensure the capacity we want is preserved.
+ char[] newArray = new char[newLen];
+ Array.Copy(chunk.m_ChunkChars, 0, newArray, 0, chunk.m_ChunkLength);
+ m_ChunkChars = newArray;
+ }
+ else
+ {
+ // Special case where the capacity we want to keep corresponds exactly to the size of the content.
+ // Just take ownership of the array.
+ Debug.Assert(newLen == chunk.m_ChunkChars.Length, "The new chunk should be larger or equal to the one it is replacing.");
+ m_ChunkChars = chunk.m_ChunkChars;
+ }
+
+ m_ChunkPrevious = chunk.m_ChunkPrevious;
+ m_ChunkOffset = chunk.m_ChunkOffset;
+ }
+ m_ChunkLength = value - chunk.m_ChunkOffset;
+ AssertInvariants();
+ }
+ Debug.Assert(Length == value, "Something went wrong setting Length.");
+ }
+ }
+
+ [IndexerName("Chars")]
+ public char this[int index]
+ {
+ get
+ {
+ StringBuilder chunk = this;
+ for (;;)
+ {
+ int indexInBlock = index - chunk.m_ChunkOffset;
+ if (indexInBlock >= 0)
+ {
+ if (indexInBlock >= chunk.m_ChunkLength)
+ {
+ throw new IndexOutOfRangeException();
+ }
+ return chunk.m_ChunkChars[indexInBlock];
+ }
+ chunk = chunk.m_ChunkPrevious;
+ if (chunk == null)
+ {
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ set
+ {
+ StringBuilder chunk = this;
+ for (;;)
+ {
+ int indexInBlock = index - chunk.m_ChunkOffset;
+ if (indexInBlock >= 0)
+ {
+ if (indexInBlock >= chunk.m_ChunkLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+ chunk.m_ChunkChars[indexInBlock] = value;
+ return;
+ }
+ chunk = chunk.m_ChunkPrevious;
+ if (chunk == null)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Appends a character 0 or more times to the end of this builder.
+ /// </summary>
+ /// <param name="value">The character to append.</param>
+ /// <param name="repeatCount">The number of times to append <paramref name="value"/>.</param>
+ public StringBuilder Append(char value, int repeatCount)
+ {
+ if (repeatCount < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_NegativeCount);
+ }
+
+ if (repeatCount == 0)
+ {
+ return this;
+ }
+
+ // this is where we can check if the repeatCount will put us over m_MaxCapacity
+ // We are doing the check here to prevent the corruption of the StringBuilder.
+ int newLength = Length + repeatCount;
+ if (newLength > m_MaxCapacity || newLength < repeatCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
+ }
+
+ int index = m_ChunkLength;
+ while (repeatCount > 0)
+ {
+ if (index < m_ChunkChars.Length)
+ {
+ m_ChunkChars[index++] = value;
+ --repeatCount;
+ }
+ else
+ {
+ m_ChunkLength = index;
+ ExpandByABlock(repeatCount);
+ Debug.Assert(m_ChunkLength == 0);
+ index = 0;
+ }
+ }
+
+ m_ChunkLength = index;
+ AssertInvariants();
+ return this;
+ }
+
+ /// <summary>
+ /// Appends a range of characters to the end of this builder.
+ /// </summary>
+ /// <param name="value">The characters to append.</param>
+ /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
+ /// <param name="charCount">The number of characters to read in <paramref name="value"/>.</param>
+ public StringBuilder Append(char[] value, int startIndex, int charCount)
+ {
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_GenericPositive);
+ }
+ if (charCount < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GenericPositive);
+ }
+
+ if (value == null)
+ {
+ if (startIndex == 0 && charCount == 0)
+ {
+ return this;
+ }
+
+ throw new ArgumentNullException(nameof(value));
+ }
+ if (charCount > value.Length - startIndex)
+ {
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (charCount == 0)
+ {
+ return this;
+ }
+
+ unsafe
+ {
+ fixed (char* valueChars = &value[startIndex])
+ {
+ Append(valueChars, charCount);
+ return this;
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// Appends a string to the end of this builder.
+ /// </summary>
+ /// <param name="value">The string to append.</param>
+ public StringBuilder Append(String value)
+ {
+ if (value != null)
+ {
+ // We could have just called AppendHelper here; this is a hand-specialization of that code.
+ char[] chunkChars = m_ChunkChars;
+ int chunkLength = m_ChunkLength;
+ int valueLen = value.Length;
+ int newCurrentIndex = chunkLength + valueLen;
+
+ if (newCurrentIndex < chunkChars.Length) // Use strictly < to avoid issues if count == 0, newIndex == length
+ {
+ if (valueLen <= 2)
+ {
+ if (valueLen > 0)
+ chunkChars[chunkLength] = value[0];
+ if (valueLen > 1)
+ chunkChars[chunkLength + 1] = value[1];
+ }
+ else
+ {
+ unsafe
+ {
+ fixed (char* valuePtr = value)
+ fixed (char* destPtr = &chunkChars[chunkLength])
+ {
+ string.wstrcpy(destPtr, valuePtr, valueLen);
+ }
+ }
+ }
+
+ m_ChunkLength = newCurrentIndex;
+ }
+ else
+ {
+ AppendHelper(value);
+ }
+ }
+
+ return this;
+ }
+
+ // We put this fixed in its own helper to avoid the cost of zero-initing `valueChars` in the
+ // case we don't actually use it.
+ private void AppendHelper(string value)
+ {
+ unsafe
+ {
+ fixed (char* valueChars = value)
+ {
+ Append(valueChars, value.Length);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Appends part of a string to the end of this builder.
+ /// </summary>
+ /// <param name="value">The string to append.</param>
+ /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
+ /// <param name="count">The number of characters to read in <paramref name="value"/>.</param>
+ public StringBuilder Append(string value, int startIndex, int count)
+ {
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_GenericPositive);
+ }
+
+ if (value == null)
+ {
+ if (startIndex == 0 && count == 0)
+ {
+ return this;
+ }
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if (count == 0)
+ {
+ return this;
+ }
+
+ if (startIndex > value.Length - count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ unsafe
+ {
+ fixed (char* valueChars = value)
+ {
+ Append(valueChars + startIndex, count);
+ return this;
+ }
+ }
+ }
+
+ public StringBuilder Append(StringBuilder value)
+ {
+ if (value != null && value.Length != 0)
+ {
+ return AppendCore(value, 0, value.Length);
+ }
+ return this;
+ }
+
+ public StringBuilder Append(StringBuilder value, int startIndex, int count)
+ {
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_GenericPositive);
+ }
+
+ if (value == null)
+ {
+ if (startIndex == 0 && count == 0)
+ {
+ return this;
+ }
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if (count == 0)
+ {
+ return this;
+ }
+
+ if (count > value.Length - startIndex)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ return AppendCore(value, startIndex, count);
+ }
+
+ private StringBuilder AppendCore(StringBuilder value, int startIndex, int count)
+ {
+ if (value == this)
+ return Append(value.ToString(startIndex, count));
+
+ int newLength = Length + count;
+
+ if ((uint)newLength > (uint)m_MaxCapacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Capacity), SR.ArgumentOutOfRange_Capacity);
+ }
+
+ while (count > 0)
+ {
+ int length = Math.Min(m_ChunkChars.Length - m_ChunkLength, count);
+ if (length == 0)
+ {
+ ExpandByABlock(count);
+ length = Math.Min(m_ChunkChars.Length - m_ChunkLength, count);
+ }
+ value.CopyTo(startIndex, new Span<char>(m_ChunkChars, m_ChunkLength, length), length);
+
+ m_ChunkLength += length;
+ startIndex += length;
+ count -= length;
+ }
+
+ return this;
+ }
+
+ public StringBuilder AppendLine() => Append(Environment.NewLine);
+
+ public StringBuilder AppendLine(string value)
+ {
+ Append(value);
+ return Append(Environment.NewLine);
+ }
+
+ public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
+ {
+ if (destination == null)
+ {
+ throw new ArgumentNullException(nameof(destination));
+ }
+
+ if (destinationIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.Format(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(destinationIndex)));
+ }
+
+ if (destinationIndex > destination.Length - count)
+ {
+ throw new ArgumentException(SR.ArgumentOutOfRange_OffsetOut);
+ }
+
+ CopyTo(sourceIndex, new Span<char>(destination).Slice(destinationIndex), count);
+ }
+
+ public void CopyTo(int sourceIndex, Span<char> destination, int count)
+ {
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.Arg_NegativeArgCount);
+ }
+
+ if ((uint)sourceIndex > (uint)Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (sourceIndex > Length - count)
+ {
+ throw new ArgumentException(SR.Arg_LongerThanSrcString);
+ }
+
+ AssertInvariants();
+
+ StringBuilder chunk = this;
+ int sourceEndIndex = sourceIndex + count;
+ int curDestIndex = count;
+ while (count > 0)
+ {
+ int chunkEndIndex = sourceEndIndex - chunk.m_ChunkOffset;
+ if (chunkEndIndex >= 0)
+ {
+ chunkEndIndex = Math.Min(chunkEndIndex, chunk.m_ChunkLength);
+
+ int chunkCount = count;
+ int chunkStartIndex = chunkEndIndex - count;
+ if (chunkStartIndex < 0)
+ {
+ chunkCount += chunkStartIndex;
+ chunkStartIndex = 0;
+ }
+ curDestIndex -= chunkCount;
+ count -= chunkCount;
+
+ // We ensure that chunkStartIndex + chunkCount are within range of m_chunkChars as well as
+ // ensuring that curDestIndex + chunkCount are within range of destination
+ ThreadSafeCopy(chunk.m_ChunkChars, chunkStartIndex, destination, curDestIndex, chunkCount);
+ }
+ chunk = chunk.m_ChunkPrevious;
+ }
+ }
+
+ /// <summary>
+ /// Inserts a string 0 or more times into this builder at the specified position.
+ /// </summary>
+ /// <param name="index">The index to insert in this builder.</param>
+ /// <param name="value">The string to insert.</param>
+ /// <param name="count">The number of times to insert the string.</param>
+ public StringBuilder Insert(int index, String value, int count)
+ {
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ int currentLength = Length;
+ if ((uint)index > (uint)currentLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (string.IsNullOrEmpty(value) || count == 0)
+ {
+ return this;
+ }
+
+ // Ensure we don't insert more chars than we can hold, and we don't
+ // have any integer overflow in our new length.
+ long insertingChars = (long)value.Length * count;
+ if (insertingChars > MaxCapacity - this.Length)
+ {
+ throw new OutOfMemoryException();
+ }
+ Debug.Assert(insertingChars + this.Length < int.MaxValue);
+
+ StringBuilder chunk;
+ int indexInChunk;
+ MakeRoom(index, (int)insertingChars, out chunk, out indexInChunk, false);
+ unsafe
+ {
+ fixed (char* valuePtr = value)
+ {
+ while (count > 0)
+ {
+ ReplaceInPlaceAtChunk(ref chunk, ref indexInChunk, valuePtr, value.Length);
+ --count;
+ }
+
+ return this;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Removes a range of characters from this builder.
+ /// </summary>
+ /// <remarks>
+ /// This method does not reduce the capacity of this builder.
+ /// </remarks>
+ public StringBuilder Remove(int startIndex, int length)
+ {
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
+ }
+
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+ }
+
+ if (length > Length - startIndex)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (Length == length && startIndex == 0)
+ {
+ Length = 0;
+ return this;
+ }
+
+ if (length > 0)
+ {
+ StringBuilder chunk;
+ int indexInChunk;
+ Remove(startIndex, length, out chunk, out indexInChunk);
+ }
+
+ return this;
+ }
+
+ public StringBuilder Append(bool value) => Append(value.ToString());
+
+ public StringBuilder Append(char value)
+ {
+ if (m_ChunkLength < m_ChunkChars.Length)
+ {
+ m_ChunkChars[m_ChunkLength++] = value;
+ }
+ else
+ {
+ Append(value, 1);
+ }
+
+ return this;
+ }
+
+ [CLSCompliant(false)]
+ public StringBuilder Append(sbyte value) => AppendSpanFormattable(value);
+
+ public StringBuilder Append(byte value) => AppendSpanFormattable(value);
+
+ public StringBuilder Append(short value) => AppendSpanFormattable(value);
+
+ public StringBuilder Append(int value) => AppendSpanFormattable(value);
+
+ public StringBuilder Append(long value) => AppendSpanFormattable(value);
+
+ public StringBuilder Append(float value) => AppendSpanFormattable(value);
+
+ public StringBuilder Append(double value) => AppendSpanFormattable(value);
+
+ public StringBuilder Append(decimal value) => AppendSpanFormattable(value);
+
+ [CLSCompliant(false)]
+ public StringBuilder Append(ushort value) => AppendSpanFormattable(value);
+
+ [CLSCompliant(false)]
+ public StringBuilder Append(uint value) => AppendSpanFormattable(value);
+
+ [CLSCompliant(false)]
+ public StringBuilder Append(ulong value) => AppendSpanFormattable(value);
+
+ private StringBuilder AppendSpanFormattable<T>(T value) where T : ISpanFormattable
+ {
+ if (value.TryFormat(RemainingCurrentChunk, out int charsWritten, format: default, provider: null))
+ {
+ m_ChunkLength += charsWritten;
+ return this;
+ }
+
+ return Append(value.ToString());
+ }
+
+ public StringBuilder Append(object value) => (value == null) ? this : Append(value.ToString());
+
+ public StringBuilder Append(char[] value)
+ {
+ if (value?.Length > 0)
+ {
+ unsafe
+ {
+ fixed (char* valueChars = &value[0])
+ {
+ Append(valueChars, value.Length);
+ }
+ }
+ }
+ return this;
+ }
+
+ public StringBuilder Append(ReadOnlySpan<char> value)
+ {
+ if (value.Length > 0)
+ {
+ unsafe
+ {
+ fixed (char* valueChars = &MemoryMarshal.GetReference(value))
+ {
+ Append(valueChars, value.Length);
+ }
+ }
+ }
+ return this;
+ }
+
+ #region AppendJoin
+
+ public unsafe StringBuilder AppendJoin(string separator, params object[] values)
+ {
+ separator = separator ?? string.Empty;
+ fixed (char* pSeparator = separator)
+ {
+ return AppendJoinCore(pSeparator, separator.Length, values);
+ }
+ }
+
+ public unsafe StringBuilder AppendJoin<T>(string separator, IEnumerable<T> values)
+ {
+ separator = separator ?? string.Empty;
+ fixed (char* pSeparator = separator)
+ {
+ return AppendJoinCore(pSeparator, separator.Length, values);
+ }
+ }
+
+ public unsafe StringBuilder AppendJoin(string separator, params string[] values)
+ {
+ separator = separator ?? string.Empty;
+ fixed (char* pSeparator = separator)
+ {
+ return AppendJoinCore(pSeparator, separator.Length, values);
+ }
+ }
+
+ public unsafe StringBuilder AppendJoin(char separator, params object[] values)
+ {
+ return AppendJoinCore(&separator, 1, values);
+ }
+
+ public unsafe StringBuilder AppendJoin<T>(char separator, IEnumerable<T> values)
+ {
+ return AppendJoinCore(&separator, 1, values);
+ }
+
+ public unsafe StringBuilder AppendJoin(char separator, params string[] values)
+ {
+ return AppendJoinCore(&separator, 1, values);
+ }
+
+ private unsafe StringBuilder AppendJoinCore<T>(char* separator, int separatorLength, IEnumerable<T> values)
+ {
+ Debug.Assert(separator != null);
+ Debug.Assert(separatorLength >= 0);
+
+ if (values == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values);
+ }
+
+ using (IEnumerator<T> en = values.GetEnumerator())
+ {
+ if (!en.MoveNext())
+ {
+ return this;
+ }
+
+ var value = en.Current;
+ if (value != null)
+ {
+ Append(value.ToString());
+ }
+
+ while (en.MoveNext())
+ {
+ Append(separator, separatorLength);
+ value = en.Current;
+ if (value != null)
+ {
+ Append(value.ToString());
+ }
+ }
+ }
+ return this;
+ }
+
+ private unsafe StringBuilder AppendJoinCore<T>(char* separator, int separatorLength, T[] values)
+ {
+ if (values == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values);
+ }
+
+ if (values.Length == 0)
+ {
+ return this;
+ }
+
+ if (values[0] != null)
+ {
+ Append(values[0].ToString());
+ }
+
+ for (int i = 1; i < values.Length; i++)
+ {
+ Append(separator, separatorLength);
+ if (values[i] != null)
+ {
+ Append(values[i].ToString());
+ }
+ }
+ return this;
+ }
+
+ #endregion
+
+ public StringBuilder Insert(int index, String value)
+ {
+ if ((uint)index > (uint)Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (value != null)
+ {
+ unsafe
+ {
+ fixed (char* sourcePtr = value)
+ Insert(index, sourcePtr, value.Length);
+ }
+ }
+ return this;
+ }
+
+ public StringBuilder Insert(int index, bool value) => Insert(index, value.ToString(), 1);
+
+ [CLSCompliant(false)]
+ public StringBuilder Insert(int index, sbyte value) => Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, byte value) => Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, short value) => Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, char value)
+ {
+ unsafe
+ {
+ Insert(index, &value, 1);
+ }
+ return this;
+ }
+
+ public StringBuilder Insert(int index, char[] value)
+ {
+ if ((uint)index > (uint)Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (value != null)
+ Insert(index, value, 0, value.Length);
+ return this;
+ }
+
+ public StringBuilder Insert(int index, char[] value, int startIndex, int charCount)
+ {
+ int currentLength = Length;
+ if ((uint)index > (uint)currentLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (value == null)
+ {
+ if (startIndex == 0 && charCount == 0)
+ {
+ return this;
+ }
+ throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
+ }
+
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
+ }
+
+ if (charCount < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GenericPositive);
+ }
+
+ if (startIndex > value.Length - charCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (charCount > 0)
+ {
+ unsafe
+ {
+ fixed (char* sourcePtr = &value[startIndex])
+ Insert(index, sourcePtr, charCount);
+ }
+ }
+ return this;
+ }
+
+ public StringBuilder Insert(int index, int value) => Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, long value) => Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, float value) => Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, double value) => Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, decimal value) => Insert(index, value.ToString(), 1);
+
+ [CLSCompliant(false)]
+ public StringBuilder Insert(int index, ushort value) => Insert(index, value.ToString(), 1);
+
+ [CLSCompliant(false)]
+ public StringBuilder Insert(int index, uint value) => Insert(index, value.ToString(), 1);
+
+ [CLSCompliant(false)]
+ public StringBuilder Insert(int index, ulong value) => Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, Object value) => (value == null) ? this : Insert(index, value.ToString(), 1);
+
+ public StringBuilder Insert(int index, ReadOnlySpan<char> value)
+ {
+ if ((uint)index > (uint)Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (value.Length > 0)
+ {
+ unsafe
+ {
+ fixed (char* sourcePtr = &MemoryMarshal.GetReference(value))
+ Insert(index, sourcePtr, value.Length);
+ }
+ }
+ return this;
+ }
+
+ public StringBuilder AppendFormat(String format, Object arg0) => AppendFormatHelper(null, format, new ParamsArray(arg0));
+
+ public StringBuilder AppendFormat(String format, Object arg0, Object arg1) => AppendFormatHelper(null, format, new ParamsArray(arg0, arg1));
+
+ public StringBuilder AppendFormat(String format, Object arg0, Object arg1, Object arg2) => AppendFormatHelper(null, format, new ParamsArray(arg0, arg1, arg2));
+
+ public StringBuilder AppendFormat(String format, params Object[] args)
+ {
+ if (args == null)
+ {
+ // To preserve the original exception behavior, throw an exception about format if both
+ // args and format are null. The actual null check for format is in AppendFormatHelper.
+ string paramName = (format == null) ? nameof(format) : nameof(args);
+ throw new ArgumentNullException(paramName);
+ }
+
+ return AppendFormatHelper(null, format, new ParamsArray(args));
+ }
+
+ public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0) => AppendFormatHelper(provider, format, new ParamsArray(arg0));
+
+ public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0, Object arg1) => AppendFormatHelper(provider, format, new ParamsArray(arg0, arg1));
+
+ public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0, Object arg1, Object arg2) => AppendFormatHelper(provider, format, new ParamsArray(arg0, arg1, arg2));
+
+ public StringBuilder AppendFormat(IFormatProvider provider, String format, params Object[] args)
+ {
+ if (args == null)
+ {
+ // To preserve the original exception behavior, throw an exception about format if both
+ // args and format are null. The actual null check for format is in AppendFormatHelper.
+ string paramName = (format == null) ? nameof(format) : nameof(args);
+ throw new ArgumentNullException(paramName);
+ }
+
+ return AppendFormatHelper(provider, format, new ParamsArray(args));
+ }
+
+ private static void FormatError()
+ {
+ throw new FormatException(SR.Format_InvalidString);
+ }
+
+ // Undocumented exclusive limits on the range for Argument Hole Index and Argument Hole Alignment.
+ private const int IndexLimit = 1000000; // Note: 0 <= ArgIndex < IndexLimit
+ private const int WidthLimit = 1000000; // Note: -WidthLimit < ArgAlign < WidthLimit
+
+ internal StringBuilder AppendFormatHelper(IFormatProvider provider, String format, ParamsArray args)
+ {
+ if (format == null)
+ {
+ throw new ArgumentNullException(nameof(format));
+ }
+
+ int pos = 0;
+ int len = format.Length;
+ char ch = '\x0';
+ StringBuilder unescapedItemFormat = null;
+
+ ICustomFormatter cf = null;
+ if (provider != null)
+ {
+ cf = (ICustomFormatter)provider.GetFormat(typeof(ICustomFormatter));
+ }
+
+ while (true)
+ {
+ while (pos < len)
+ {
+ ch = format[pos];
+
+ pos++;
+ // Is it a closing brace?
+ if (ch == '}')
+ {
+ // Check next character (if there is one) to see if it is escaped. eg }}
+ if (pos < len && format[pos] == '}')
+ pos++;
+ else
+ // Otherwise treat it as an error (Mismatched closing brace)
+ FormatError();
+ }
+ // Is it a opening brace?
+ if (ch == '{')
+ {
+ // Check next character (if there is one) to see if it is escaped. eg {{
+ if (pos < len && format[pos] == '{')
+ pos++;
+ else
+ {
+ // Otherwise treat it as the opening brace of an Argument Hole.
+ pos--;
+ break;
+ }
+ }
+ // If it neither then treat the character as just text.
+ Append(ch);
+ }
+
+ //
+ // Start of parsing of Argument Hole.
+ // Argument Hole ::= { Index (, WS* Alignment WS*)? (: Formatting)? }
+ //
+ if (pos == len) break;
+
+ //
+ // Start of parsing required Index parameter.
+ // Index ::= ('0'-'9')+ WS*
+ //
+ pos++;
+ // If reached end of text then error (Unexpected end of text)
+ // or character is not a digit then error (Unexpected Character)
+ if (pos == len || (ch = format[pos]) < '0' || ch > '9') FormatError();
+ int index = 0;
+ do
+ {
+ index = index * 10 + ch - '0';
+ pos++;
+ // If reached end of text then error (Unexpected end of text)
+ if (pos == len) FormatError();
+ ch = format[pos];
+ // so long as character is digit and value of the index is less than 1000000 ( index limit )
+ }
+ while (ch >= '0' && ch <= '9' && index < IndexLimit);
+
+ // If value of index is not within the range of the arguments passed in then error (Index out of range)
+ if (index >= args.Length) throw new FormatException(SR.Format_IndexOutOfRange);
+
+ // Consume optional whitespace.
+ while (pos < len && (ch = format[pos]) == ' ') pos++;
+ // End of parsing index parameter.
+
+ //
+ // Start of parsing of optional Alignment
+ // Alignment ::= comma WS* minus? ('0'-'9')+ WS*
+ //
+ bool leftJustify = false;
+ int width = 0;
+ // Is the character a comma, which indicates the start of alignment parameter.
+ if (ch == ',')
+ {
+ pos++;
+
+ // Consume Optional whitespace
+ while (pos < len && format[pos] == ' ') pos++;
+
+ // If reached the end of the text then error (Unexpected end of text)
+ if (pos == len) FormatError();
+
+ // Is there a minus sign?
+ ch = format[pos];
+ if (ch == '-')
+ {
+ // Yes, then alignment is left justified.
+ leftJustify = true;
+ pos++;
+ // If reached end of text then error (Unexpected end of text)
+ if (pos == len) FormatError();
+ ch = format[pos];
+ }
+
+ // If current character is not a digit then error (Unexpected character)
+ if (ch < '0' || ch > '9') FormatError();
+ // Parse alignment digits.
+ do
+ {
+ width = width * 10 + ch - '0';
+ pos++;
+ // If reached end of text then error. (Unexpected end of text)
+ if (pos == len) FormatError();
+ ch = format[pos];
+ // So long a current character is a digit and the value of width is less than 100000 ( width limit )
+ }
+ while (ch >= '0' && ch <= '9' && width < WidthLimit);
+ // end of parsing Argument Alignment
+ }
+
+ // Consume optional whitespace
+ while (pos < len && (ch = format[pos]) == ' ') pos++;
+
+ //
+ // Start of parsing of optional formatting parameter.
+ //
+ Object arg = args[index];
+ String itemFormat = null;
+ ReadOnlySpan<char> itemFormatSpan = default; // used if itemFormat is null
+ // Is current character a colon? which indicates start of formatting parameter.
+ if (ch == ':')
+ {
+ pos++;
+ int startPos = pos;
+
+ while (true)
+ {
+ // If reached end of text then error. (Unexpected end of text)
+ if (pos == len) FormatError();
+ ch = format[pos];
+ pos++;
+
+ // Is character a opening or closing brace?
+ if (ch == '}' || ch == '{')
+ {
+ if (ch == '{')
+ {
+ // Yes, is next character also a opening brace, then treat as escaped. eg {{
+ if (pos < len && format[pos] == '{')
+ pos++;
+ else
+ // Error Argument Holes can not be nested.
+ FormatError();
+ }
+ else
+ {
+ // Yes, is next character also a closing brace, then treat as escaped. eg }}
+ if (pos < len && format[pos] == '}')
+ pos++;
+ else
+ {
+ // No, then treat it as the closing brace of an Arg Hole.
+ pos--;
+ break;
+ }
+ }
+
+ // Reaching here means the brace has been escaped
+ // so we need to build up the format string in segments
+ if (unescapedItemFormat == null)
+ {
+ unescapedItemFormat = new StringBuilder();
+ }
+ unescapedItemFormat.Append(format, startPos, pos - startPos - 1);
+ startPos = pos;
+ }
+ }
+
+ if (unescapedItemFormat == null || unescapedItemFormat.Length == 0)
+ {
+ if (startPos != pos)
+ {
+ // There was no brace escaping, extract the item format as a single string
+ itemFormatSpan = format.AsSpan(startPos, pos - startPos);
+ }
+ }
+ else
+ {
+ unescapedItemFormat.Append(format, startPos, pos - startPos);
+ itemFormatSpan = itemFormat = unescapedItemFormat.ToString();
+ unescapedItemFormat.Clear();
+ }
+ }
+ // If current character is not a closing brace then error. (Unexpected Character)
+ if (ch != '}') FormatError();
+ // Construct the output for this arg hole.
+ pos++;
+ String s = null;
+ if (cf != null)
+ {
+ if (itemFormatSpan.Length != 0 && itemFormat == null)
+ {
+ itemFormat = new string(itemFormatSpan);
+ }
+ s = cf.Format(itemFormat, arg, provider);
+ }
+
+ if (s == null)
+ {
+ // If arg is ISpanFormattable and the beginning doesn't need padding,
+ // try formatting it into the remaining current chunk.
+ if (arg is ISpanFormattable spanFormattableArg &&
+ (leftJustify || width == 0) &&
+ spanFormattableArg.TryFormat(RemainingCurrentChunk, out int charsWritten, itemFormatSpan, provider))
+ {
+ m_ChunkLength += charsWritten;
+
+ // Pad the end, if needed.
+ int padding = width - charsWritten;
+ if (leftJustify && padding > 0) Append(' ', padding);
+
+ // Continue to parse other characters.
+ continue;
+ }
+
+ // Otherwise, fallback to trying IFormattable or calling ToString.
+ if (arg is IFormattable formattableArg)
+ {
+ if (itemFormatSpan.Length != 0 && itemFormat == null)
+ {
+ itemFormat = new string(itemFormatSpan);
+ }
+ s = formattableArg.ToString(itemFormat, provider);
+ }
+ else if (arg != null)
+ {
+ s = arg.ToString();
+ }
+ }
+ // Append it to the final output of the Format String.
+ if (s == null) s = String.Empty;
+ int pad = width - s.Length;
+ if (!leftJustify && pad > 0) Append(' ', pad);
+ Append(s);
+ if (leftJustify && pad > 0) Append(' ', pad);
+ // Continue to parse other characters.
+ }
+ return this;
+ }
+
+ /// <summary>
+ /// Replaces all instances of one string with another in this builder.
+ /// </summary>
+ /// <param name="oldValue">The string to replace.</param>
+ /// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
+ /// <remarks>
+ /// If <paramref name="newValue"/> is <c>null</c>, instances of <paramref name="oldValue"/>
+ /// are removed from this builder.
+ /// </remarks>
+ public StringBuilder Replace(String oldValue, String newValue) => Replace(oldValue, newValue, 0, Length);
+
+ /// <summary>
+ /// Determines if the contents of this builder are equal to the contents of another builder.
+ /// </summary>
+ /// <param name="sb">The other builder.</param>
+ public bool Equals(StringBuilder sb)
+ {
+ if (sb == null)
+ return false;
+ if (Capacity != sb.Capacity || MaxCapacity != sb.MaxCapacity || Length != sb.Length)
+ return false;
+ if (sb == this)
+ return true;
+
+ StringBuilder thisChunk = this;
+ int thisChunkIndex = thisChunk.m_ChunkLength;
+ StringBuilder sbChunk = sb;
+ int sbChunkIndex = sbChunk.m_ChunkLength;
+ for (;;)
+ {
+ --thisChunkIndex;
+ --sbChunkIndex;
+
+ while (thisChunkIndex < 0)
+ {
+ thisChunk = thisChunk.m_ChunkPrevious;
+ if (thisChunk == null)
+ break;
+ thisChunkIndex = thisChunk.m_ChunkLength + thisChunkIndex;
+ }
+
+ while (sbChunkIndex < 0)
+ {
+ sbChunk = sbChunk.m_ChunkPrevious;
+ if (sbChunk == null)
+ break;
+ sbChunkIndex = sbChunk.m_ChunkLength + sbChunkIndex;
+ }
+
+ if (thisChunkIndex < 0)
+ return sbChunkIndex < 0;
+ if (sbChunkIndex < 0)
+ return false;
+ if (thisChunk.m_ChunkChars[thisChunkIndex] != sbChunk.m_ChunkChars[sbChunkIndex])
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Determines if the contents of this builder are equal to the contents of ReadOnlySpan<char>.
+ /// </summary>
+ /// <param name="span">The ReadOnlySpan{char}.</param>
+ public bool Equals(ReadOnlySpan<char> span)
+ {
+ if (span.Length != Length)
+ return false;
+
+ StringBuilder sbChunk = this;
+ int offset = 0;
+
+ do
+ {
+ int chunk_length = sbChunk.m_ChunkLength;
+ offset += chunk_length;
+
+ ReadOnlySpan<char> chunk = new ReadOnlySpan<char>(sbChunk.m_ChunkChars, 0, chunk_length);
+
+ if (!chunk.EqualsOrdinal(span.Slice(span.Length - offset, chunk_length)))
+ return false;
+
+ sbChunk = sbChunk.m_ChunkPrevious;
+ } while (sbChunk != null);
+
+ Debug.Assert(offset == Length);
+ return true;
+ }
+
+ /// <summary>
+ /// Replaces all instances of one string with another in part of this builder.
+ /// </summary>
+ /// <param name="oldValue">The string to replace.</param>
+ /// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
+ /// <param name="startIndex">The index to start in this builder.</param>
+ /// <param name="count">The number of characters to read in this builder.</param>
+ /// <remarks>
+ /// If <paramref name="newValue"/> is <c>null</c>, instances of <paramref name="oldValue"/>
+ /// are removed from this builder.
+ /// </remarks>
+ public StringBuilder Replace(String oldValue, String newValue, int startIndex, int count)
+ {
+ int currentLength = Length;
+ if ((uint)startIndex > (uint)currentLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+ if (count < 0 || startIndex > currentLength - count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Index);
+ }
+ if (oldValue == null)
+ {
+ throw new ArgumentNullException(nameof(oldValue));
+ }
+ if (oldValue.Length == 0)
+ {
+ throw new ArgumentException(SR.Argument_EmptyName, nameof(oldValue));
+ }
+
+ newValue = newValue ?? string.Empty;
+
+ int deltaLength = newValue.Length - oldValue.Length;
+
+ int[] replacements = null; // A list of replacement positions in a chunk to apply
+ int replacementsCount = 0;
+
+ // Find the chunk, indexInChunk for the starting point
+ StringBuilder chunk = FindChunkForIndex(startIndex);
+ int indexInChunk = startIndex - chunk.m_ChunkOffset;
+ while (count > 0)
+ {
+ // Look for a match in the chunk,indexInChunk pointer
+ if (StartsWith(chunk, indexInChunk, count, oldValue))
+ {
+ // Push it on the replacements array (with growth), we will do all replacements in a
+ // given chunk in one operation below (see ReplaceAllInChunk) so we don't have to slide
+ // many times.
+ if (replacements == null)
+ {
+ replacements = new int[5];
+ }
+ else if (replacementsCount >= replacements.Length)
+ {
+ Array.Resize(ref replacements, replacements.Length * 3 / 2 + 4); // Grow by ~1.5x, but more in the begining
+ }
+ replacements[replacementsCount++] = indexInChunk;
+ indexInChunk += oldValue.Length;
+ count -= oldValue.Length;
+ }
+ else
+ {
+ indexInChunk++;
+ --count;
+ }
+
+ if (indexInChunk >= chunk.m_ChunkLength || count == 0) // Have we moved out of the current chunk?
+ {
+ // Replacing mutates the blocks, so we need to convert to a logical index and back afterwards.
+ int index = indexInChunk + chunk.m_ChunkOffset;
+ int indexBeforeAdjustment = index;
+
+ // See if we accumulated any replacements, if so apply them.
+ ReplaceAllInChunk(replacements, replacementsCount, chunk, oldValue.Length, newValue);
+ // The replacement has affected the logical index. Adjust it.
+ index += ((newValue.Length - oldValue.Length) * replacementsCount);
+ replacementsCount = 0;
+
+ chunk = FindChunkForIndex(index);
+ indexInChunk = index - chunk.m_ChunkOffset;
+ Debug.Assert(chunk != null || count == 0, "Chunks ended prematurely!");
+ }
+ }
+
+ AssertInvariants();
+ return this;
+ }
+
+ /// <summary>
+ /// Replaces all instances of one character with another in this builder.
+ /// </summary>
+ /// <param name="oldChar">The character to replace.</param>
+ /// <param name="newChar">The character to replace <paramref name="oldChar"/> with.</param>
+ public StringBuilder Replace(char oldChar, char newChar)
+ {
+ return Replace(oldChar, newChar, 0, Length);
+ }
+
+ /// <summary>
+ /// Replaces all instances of one character with another in this builder.
+ /// </summary>
+ /// <param name="oldChar">The character to replace.</param>
+ /// <param name="newChar">The character to replace <paramref name="oldChar"/> with.</param>
+ /// <param name="startIndex">The index to start in this builder.</param>
+ /// <param name="count">The number of characters to read in this builder.</param>
+ public StringBuilder Replace(char oldChar, char newChar, int startIndex, int count)
+ {
+ int currentLength = Length;
+ if ((uint)startIndex > (uint)currentLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (count < 0 || startIndex > currentLength - count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Index);
+ }
+
+ int endIndex = startIndex + count;
+ StringBuilder chunk = this;
+
+ for (;;)
+ {
+ int endIndexInChunk = endIndex - chunk.m_ChunkOffset;
+ int startIndexInChunk = startIndex - chunk.m_ChunkOffset;
+ if (endIndexInChunk >= 0)
+ {
+ int curInChunk = Math.Max(startIndexInChunk, 0);
+ int endInChunk = Math.Min(chunk.m_ChunkLength, endIndexInChunk);
+ while (curInChunk < endInChunk)
+ {
+ if (chunk.m_ChunkChars[curInChunk] == oldChar)
+ chunk.m_ChunkChars[curInChunk] = newChar;
+ curInChunk++;
+ }
+ }
+ if (startIndexInChunk >= 0)
+ break;
+ chunk = chunk.m_ChunkPrevious;
+ }
+
+ AssertInvariants();
+ return this;
+ }
+
+ /// <summary>
+ /// Appends a character buffer to this builder.
+ /// </summary>
+ /// <param name="value">The pointer to the start of the buffer.</param>
+ /// <param name="valueCount">The number of characters in the buffer.</param>
+ [CLSCompliant(false)]
+ public unsafe StringBuilder Append(char* value, int valueCount)
+ {
+ // We don't check null value as this case will throw null reference exception anyway
+ if (valueCount < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(valueCount), SR.ArgumentOutOfRange_NegativeCount);
+ }
+
+ // this is where we can check if the valueCount will put us over m_MaxCapacity
+ // We are doing the check here to prevent the corruption of the StringBuilder.
+ int newLength = Length + valueCount;
+ if (newLength > m_MaxCapacity || newLength < valueCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(valueCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
+ }
+
+ // This case is so common we want to optimize for it heavily.
+ int newIndex = valueCount + m_ChunkLength;
+ if (newIndex <= m_ChunkChars.Length)
+ {
+ ThreadSafeCopy(value, m_ChunkChars, m_ChunkLength, valueCount);
+ m_ChunkLength = newIndex;
+ }
+ else
+ {
+ // Copy the first chunk
+ int firstLength = m_ChunkChars.Length - m_ChunkLength;
+ if (firstLength > 0)
+ {
+ ThreadSafeCopy(value, m_ChunkChars, m_ChunkLength, firstLength);
+ m_ChunkLength = m_ChunkChars.Length;
+ }
+
+ // Expand the builder to add another chunk.
+ int restLength = valueCount - firstLength;
+ ExpandByABlock(restLength);
+ Debug.Assert(m_ChunkLength == 0, "A new block was not created.");
+
+ // Copy the second chunk
+ ThreadSafeCopy(value + firstLength, m_ChunkChars, 0, restLength);
+ m_ChunkLength = restLength;
+ }
+ AssertInvariants();
+ return this;
+ }
+
+ /// <summary>
+ /// Inserts a character buffer into this builder at the specified position.
+ /// </summary>
+ /// <param name="index">The index to insert in this builder.</param>
+ /// <param name="value">The pointer to the start of the buffer.</param>
+ /// <param name="valueCount">The number of characters in the buffer.</param>
+ private unsafe void Insert(int index, char* value, int valueCount)
+ {
+ if ((uint)index > (uint)Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
+
+ if (valueCount > 0)
+ {
+ StringBuilder chunk;
+ int indexInChunk;
+ MakeRoom(index, valueCount, out chunk, out indexInChunk, false);
+ ReplaceInPlaceAtChunk(ref chunk, ref indexInChunk, value, valueCount);
+ }
+ }
+
+ /// <summary>
+ /// Replaces strings at specified indices with a new string in a chunk.
+ /// </summary>
+ /// <param name="replacements">The list of indices, relative to the beginning of the chunk, to remove at.</param>
+ /// <param name="replacementsCount">The number of replacements to make.</param>
+ /// <param name="sourceChunk">The source chunk.</param>
+ /// <param name="removeCount">The number of characters to remove at each replacement.</param>
+ /// <param name="value">The string to insert at each replacement.</param>
+ /// <remarks>
+ /// This routine is very efficient because it does replacements in bulk.
+ /// </remarks>
+ private void ReplaceAllInChunk(int[] replacements, int replacementsCount, StringBuilder sourceChunk, int removeCount, string value)
+ {
+ if (replacementsCount <= 0)
+ {
+ return;
+ }
+
+ unsafe
+ {
+ fixed (char* valuePtr = value)
+ {
+ // calculate the total amount of extra space or space needed for all the replacements.
+ int delta = (value.Length - removeCount) * replacementsCount;
+
+ StringBuilder targetChunk = sourceChunk; // the target as we copy chars down
+ int targetIndexInChunk = replacements[0];
+
+ // Make the room needed for all the new characters if needed.
+ if (delta > 0)
+ MakeRoom(targetChunk.m_ChunkOffset + targetIndexInChunk, delta, out targetChunk, out targetIndexInChunk, true);
+ // We made certain that characters after the insertion point are not moved,
+ int i = 0;
+ for (;;)
+ {
+ // Copy in the new string for the ith replacement
+ ReplaceInPlaceAtChunk(ref targetChunk, ref targetIndexInChunk, valuePtr, value.Length);
+ int gapStart = replacements[i] + removeCount;
+ i++;
+ if (i >= replacementsCount)
+ {
+ break;
+ }
+
+ int gapEnd = replacements[i];
+ Debug.Assert(gapStart < sourceChunk.m_ChunkChars.Length, "gap starts at end of buffer. Should not happen");
+ Debug.Assert(gapStart <= gapEnd, "negative gap size");
+ Debug.Assert(gapEnd <= sourceChunk.m_ChunkLength, "gap too big");
+ if (delta != 0) // can skip the sliding of gaps if source an target string are the same size.
+ {
+ // Copy the gap data between the current replacement and the next replacement
+ fixed (char* sourcePtr = &sourceChunk.m_ChunkChars[gapStart])
+ ReplaceInPlaceAtChunk(ref targetChunk, ref targetIndexInChunk, sourcePtr, gapEnd - gapStart);
+ }
+ else
+ {
+ targetIndexInChunk += gapEnd - gapStart;
+ Debug.Assert(targetIndexInChunk <= targetChunk.m_ChunkLength, "gap not in chunk");
+ }
+ }
+
+ // Remove extra space if necessary.
+ if (delta < 0)
+ Remove(targetChunk.m_ChunkOffset + targetIndexInChunk, -delta, out targetChunk, out targetIndexInChunk);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a value indicating whether a substring of a builder starts with a specified prefix.
+ /// </summary>
+ /// <param name="chunk">The chunk in which the substring starts.</param>
+ /// <param name="indexInChunk">The index in <paramref name="chunk"/> at which the substring starts.</param>
+ /// <param name="count">The logical count of the substring.</param>
+ /// <param name="value">The prefix.</param>
+ private bool StartsWith(StringBuilder chunk, int indexInChunk, int count, string value)
+ {
+ for (int i = 0; i < value.Length; i++)
+ {
+ if (count == 0)
+ {
+ return false;
+ }
+
+ if (indexInChunk >= chunk.m_ChunkLength)
+ {
+ chunk = Next(chunk);
+ if (chunk == null)
+ return false;
+ indexInChunk = 0;
+ }
+
+ if (value[i] != chunk.m_ChunkChars[indexInChunk])
+ {
+ return false;
+ }
+
+ indexInChunk++;
+ --count;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Replaces characters at a specified location with the contents of a character buffer.
+ /// This function is the logical equivalent of memcpy.
+ /// </summary>
+ /// <param name="chunk">
+ /// The chunk in which to start replacing characters.
+ /// Receives the chunk in which character replacement ends.
+ /// </param>
+ /// <param name="indexInChunk">
+ /// The index in <paramref name="chunk"/> to start replacing characters at.
+ /// Receives the index at which character replacement ends.
+ /// </param>
+ /// <param name="value">The pointer to the start of the character buffer.</param>
+ /// <param name="count">The number of characters in the buffer.</param>
+ private unsafe void ReplaceInPlaceAtChunk(ref StringBuilder chunk, ref int indexInChunk, char* value, int count)
+ {
+ if (count != 0)
+ {
+ for (;;)
+ {
+ int lengthInChunk = chunk.m_ChunkLength - indexInChunk;
+ Debug.Assert(lengthInChunk >= 0, "Index isn't in the chunk.");
+
+ int lengthToCopy = Math.Min(lengthInChunk, count);
+ ThreadSafeCopy(value, chunk.m_ChunkChars, indexInChunk, lengthToCopy);
+
+ // Advance the index.
+ indexInChunk += lengthToCopy;
+ if (indexInChunk >= chunk.m_ChunkLength)
+ {
+ chunk = Next(chunk);
+ indexInChunk = 0;
+ }
+ count -= lengthToCopy;
+ if (count == 0)
+ {
+ break;
+ }
+ value += lengthToCopy;
+ }
+ }
+ }
+
+ /// <remarks>
+ /// This method prevents out-of-bounds writes in the case a different thread updates a field in the builder just before a copy begins.
+ /// All interesting variables are copied out of the heap into the parameters of this method, and then bounds checks are run.
+ /// </remarks>
+ private static unsafe void ThreadSafeCopy(char* sourcePtr, char[] destination, int destinationIndex, int count)
+ {
+ if (count > 0)
+ {
+ if ((uint)destinationIndex <= (uint)destination.Length && (destinationIndex + count) <= destination.Length)
+ {
+ fixed (char* destinationPtr = &destination[destinationIndex])
+ string.wstrcpy(destinationPtr, sourcePtr, count);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_Index);
+ }
+ }
+ }
+
+ private static unsafe void ThreadSafeCopy(char[] source, int sourceIndex, Span<char> destination, int destinationIndex, int count)
+ {
+ if (count > 0)
+ {
+ if ((uint)sourceIndex > (uint)source.Length || count > source.Length - sourceIndex)
+ {
+ throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ if ((uint)destinationIndex > (uint)destination.Length || count > destination.Length - destinationIndex)
+ {
+ throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_Index);
+ }
+
+ fixed (char* sourcePtr = &source[sourceIndex])
+ fixed (char* destinationPtr = &MemoryMarshal.GetReference(destination))
+ string.wstrcpy(destinationPtr + destinationIndex, sourcePtr, count);
+ }
+ }
+
+ /// <summary>
+ /// Gets the chunk corresponding to the logical index in this builder.
+ /// </summary>
+ /// <param name="index">The logical index in this builder.</param>
+ /// <remarks>
+ /// After calling this method, you can obtain the actual index within the chunk by
+ /// subtracting <see cref="m_ChunkOffset"/> from <paramref name="index"/>.
+ /// </remarks>
+ private StringBuilder FindChunkForIndex(int index)
+ {
+ Debug.Assert(0 <= index && index <= Length);
+
+ StringBuilder result = this;
+ while (result.m_ChunkOffset > index)
+ {
+ result = result.m_ChunkPrevious;
+ }
+
+ Debug.Assert(result != null);
+ return result;
+ }
+
+ /// <summary>
+ /// Gets the chunk corresponding to the logical byte index in this builder.
+ /// </summary>
+ /// <param name="byteIndex">The logical byte index in this builder.</param>
+ private StringBuilder FindChunkForByte(int byteIndex)
+ {
+ Debug.Assert(0 <= byteIndex && byteIndex <= Length * sizeof(char));
+
+ StringBuilder result = this;
+ while (result.m_ChunkOffset * sizeof(char) > byteIndex)
+ {
+ result = result.m_ChunkPrevious;
+ }
+
+ Debug.Assert(result != null);
+ return result;
+ }
+
+ /// <summary>Gets a span representing the remaining space available in the current chunk.</summary>
+ private Span<char> RemainingCurrentChunk
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new Span<char>(m_ChunkChars, m_ChunkLength, m_ChunkChars.Length - m_ChunkLength);
+ }
+
+ /// <summary>
+ /// Finds the chunk that logically succeeds the specified chunk.
+ /// </summary>
+ /// <param name="chunk">The chunk whose successor should be found.</param>
+ /// <remarks>
+ /// Each chunk only stores the pointer to its logical predecessor, so this routine has to start
+ /// from the 'this' pointer (which is assumed to represent the whole StringBuilder) and work its
+ /// way down until it finds the specified chunk (which is O(n)). Thus, it is more expensive than
+ /// a field fetch.
+ /// </remarks>
+ private StringBuilder Next(StringBuilder chunk) => chunk == this ? null : FindChunkForIndex(chunk.m_ChunkOffset + chunk.m_ChunkLength);
+
+ /// <summary>
+ /// Transfers the character buffer from this chunk to a new chunk, and allocates a new buffer with a minimum size for this chunk.
+ /// </summary>
+ /// <param name="minBlockCharCount">The minimum size of the new buffer to be allocated for this chunk.</param>
+ /// <remarks>
+ /// This method requires that the current chunk is full. Otherwise, there's no point in shifting the characters over.
+ /// It also assumes that 'this' is the last chunk in the linked list.
+ /// </remarks>
+ private void ExpandByABlock(int minBlockCharCount)
+ {
+ Debug.Assert(Capacity == Length, nameof(ExpandByABlock) + " should only be called when there is no space left.");
+ Debug.Assert(minBlockCharCount > 0);
+
+ AssertInvariants();
+
+ if ((minBlockCharCount + Length) > m_MaxCapacity || minBlockCharCount + Length < minBlockCharCount)
+ {
+ throw new ArgumentOutOfRangeException("requiredLength", SR.ArgumentOutOfRange_SmallCapacity);
+ }
+
+ // - We always need to make the new chunk at least as big as was requested (`minBlockCharCount`).
+ // - We'd also prefer to make it at least at big as the current length (thus doubling capacity).
+ // - But this is only up to a maximum, so we stay in the small object heap, and never allocate
+ // really big chunks even if the string gets really big.
+ int newBlockLength = Math.Max(minBlockCharCount, Math.Min(Length, MaxChunkSize));
+
+ // Move all of the data from this chunk to a new one, via a few O(1) pointer adjustments.
+ // Then, have this chunk point to the new one as its predecessor.
+ m_ChunkPrevious = new StringBuilder(this);
+ m_ChunkOffset += m_ChunkLength;
+ m_ChunkLength = 0;
+
+ // Check for integer overflow (logical buffer size > int.MaxValue)
+ if (m_ChunkOffset + newBlockLength < newBlockLength)
+ {
+ m_ChunkChars = null;
+ throw new OutOfMemoryException();
+ }
+ m_ChunkChars = new char[newBlockLength];
+
+ AssertInvariants();
+ }
+
+ /// <summary>
+ /// Creates a new chunk with fields copied from an existing chunk.
+ /// </summary>
+ /// <param name="from">The chunk from which to copy fields.</param>
+ /// <remarks>
+ /// <para>
+ /// This method runs in O(1) time. It does not copy data within the character buffer
+ /// <paramref name="from"/> holds, but copies the reference to the character buffer itself
+ /// (plus a few other fields).
+ /// </para>
+ /// <para>
+ /// Callers are expected to update <paramref name="from"/> subsequently to point to this
+ /// chunk as its predecessor.
+ /// </para>
+ /// </remarks>
+ private StringBuilder(StringBuilder from)
+ {
+ m_ChunkLength = from.m_ChunkLength;
+ m_ChunkOffset = from.m_ChunkOffset;
+ m_ChunkChars = from.m_ChunkChars;
+ m_ChunkPrevious = from.m_ChunkPrevious;
+ m_MaxCapacity = from.m_MaxCapacity;
+
+ AssertInvariants();
+ }
+
+ /// <summary>
+ /// Creates a gap at a logical index with the specified count.
+ /// </summary>
+ /// <param name="index">The logical index in this builder.</param>
+ /// <param name="count">The number of characters in the gap.</param>
+ /// <param name="chunk">Receives the chunk containing the gap.</param>
+ /// <param name="indexInChunk">The index in <paramref name="chunk"/> that points to the gap.</param>
+ /// <param name="doNotMoveFollowingChars">
+ /// - If <c>true</c>, then room must be made by inserting a chunk before the current chunk.
+ /// - If <c>false</c>, then room can be made by shifting characters ahead of <paramref name="index"/>
+ /// in this block forward by <paramref name="count"/> provided the characters will still fit in
+ /// the current chunk after being shifted.
+ /// - Providing <c>false</c> does not make a difference most of the time, but it can matter when someone
+ /// inserts lots of small strings at a position in the buffer.
+ /// </param>
+ /// <remarks>
+ /// <para>
+ /// Since chunks do not contain references to their successors, it is not always possible for us to make room
+ /// by inserting space after <paramref name="index"/> in case this chunk runs out of space. Thus, we make room
+ /// by inserting space before the specified index, and having logical indices refer to new locations by the end
+ /// of this method.
+ /// </para>
+ /// <para>
+ /// <see cref="ReplaceInPlaceAtChunk"/> can be used in conjunction with this method to fill in the newly created gap.
+ /// </para>
+ /// </remarks>
+ private void MakeRoom(int index, int count, out StringBuilder chunk, out int indexInChunk, bool doNotMoveFollowingChars)
+ {
+ AssertInvariants();
+ Debug.Assert(count > 0);
+ Debug.Assert(index >= 0);
+
+ if (count + Length > m_MaxCapacity || count + Length < count)
+ {
+ throw new ArgumentOutOfRangeException("requiredLength", SR.ArgumentOutOfRange_SmallCapacity);
+ }
+
+ chunk = this;
+ while (chunk.m_ChunkOffset > index)
+ {
+ chunk.m_ChunkOffset += count;
+ chunk = chunk.m_ChunkPrevious;
+ }
+ indexInChunk = index - chunk.m_ChunkOffset;
+
+ // Cool, we have some space in this block, and we don't have to copy much to get at it, so go ahead and use it.
+ // This typically happens when someone repeatedly inserts small strings at a spot (usually the absolute front) of the buffer.
+ if (!doNotMoveFollowingChars && chunk.m_ChunkLength <= DefaultCapacity * 2 && chunk.m_ChunkChars.Length - chunk.m_ChunkLength >= count)
+ {
+ for (int i = chunk.m_ChunkLength; i > indexInChunk; )
+ {
+ --i;
+ chunk.m_ChunkChars[i + count] = chunk.m_ChunkChars[i];
+ }
+ chunk.m_ChunkLength += count;
+ return;
+ }
+
+ // Allocate space for the new chunk, which will go before the current one.
+ StringBuilder newChunk = new StringBuilder(Math.Max(count, DefaultCapacity), chunk.m_MaxCapacity, chunk.m_ChunkPrevious);
+ newChunk.m_ChunkLength = count;
+
+ // Copy the head of the current buffer to the new buffer.
+ int copyCount1 = Math.Min(count, indexInChunk);
+ if (copyCount1 > 0)
+ {
+ unsafe
+ {
+ fixed (char* chunkCharsPtr = &chunk.m_ChunkChars[0])
+ {
+ ThreadSafeCopy(chunkCharsPtr, newChunk.m_ChunkChars, 0, copyCount1);
+
+ // Slide characters over in the current buffer to make room.
+ int copyCount2 = indexInChunk - copyCount1;
+ if (copyCount2 >= 0)
+ {
+ ThreadSafeCopy(chunkCharsPtr + copyCount1, chunk.m_ChunkChars, 0, copyCount2);
+ indexInChunk = copyCount2;
+ }
+ }
+ }
+ }
+
+ // Wire in the new chunk.
+ chunk.m_ChunkPrevious = newChunk;
+ chunk.m_ChunkOffset += count;
+ if (copyCount1 < count)
+ {
+ chunk = newChunk;
+ indexInChunk = copyCount1;
+ }
+
+ AssertInvariants();
+ }
+
+ /// <summary>
+ /// Used by <see cref="MakeRoom"/> to allocate another chunk.
+ /// </summary>
+ /// <param name="size">The size of the character buffer for this chunk.</param>
+ /// <param name="maxCapacity">The maximum capacity, to be stored in this chunk.</param>
+ /// <param name="previousBlock">The predecessor of this chunk.</param>
+ private StringBuilder(int size, int maxCapacity, StringBuilder previousBlock)
+ {
+ Debug.Assert(size > 0);
+ Debug.Assert(maxCapacity > 0);
+
+ m_ChunkChars = new char[size];
+ m_MaxCapacity = maxCapacity;
+ m_ChunkPrevious = previousBlock;
+ if (previousBlock != null)
+ {
+ m_ChunkOffset = previousBlock.m_ChunkOffset + previousBlock.m_ChunkLength;
+ }
+
+ AssertInvariants();
+ }
+
+ /// <summary>
+ /// Removes a specified number of characters beginning at a logical index in this builder.
+ /// </summary>
+ /// <param name="startIndex">The logical index in this builder to start removing characters.</param>
+ /// <param name="count">The number of characters to remove.</param>
+ /// <param name="chunk">Receives the new chunk containing the logical index.</param>
+ /// <param name="indexInChunk">
+ /// Receives the new index in <paramref name="chunk"/> that is associated with the logical index.
+ /// </param>
+ private void Remove(int startIndex, int count, out StringBuilder chunk, out int indexInChunk)
+ {
+ AssertInvariants();
+ Debug.Assert(startIndex >= 0 && startIndex < Length);
+
+ int endIndex = startIndex + count;
+
+ // Find the chunks for the start and end of the block to delete.
+ chunk = this;
+ StringBuilder endChunk = null;
+ int endIndexInChunk = 0;
+ for (;;)
+ {
+ if (endIndex - chunk.m_ChunkOffset >= 0)
+ {
+ if (endChunk == null)
+ {
+ endChunk = chunk;
+ endIndexInChunk = endIndex - endChunk.m_ChunkOffset;
+ }
+ if (startIndex - chunk.m_ChunkOffset >= 0)
+ {
+ indexInChunk = startIndex - chunk.m_ChunkOffset;
+ break;
+ }
+ }
+ else
+ {
+ chunk.m_ChunkOffset -= count;
+ }
+ chunk = chunk.m_ChunkPrevious;
+ }
+ Debug.Assert(chunk != null, "We fell off the beginning of the string!");
+
+ int copyTargetIndexInChunk = indexInChunk;
+ int copyCount = endChunk.m_ChunkLength - endIndexInChunk;
+ if (endChunk != chunk)
+ {
+ copyTargetIndexInChunk = 0;
+ // Remove the characters after `startIndex` to the end of the chunk.
+ chunk.m_ChunkLength = indexInChunk;
+
+ // Remove the characters in chunks between the start and the end chunk.
+ endChunk.m_ChunkPrevious = chunk;
+ endChunk.m_ChunkOffset = chunk.m_ChunkOffset + chunk.m_ChunkLength;
+
+ // If the start is 0, then we can throw away the whole start chunk.
+ if (indexInChunk == 0)
+ {
+ endChunk.m_ChunkPrevious = chunk.m_ChunkPrevious;
+ chunk = endChunk;
+ }
+ }
+ endChunk.m_ChunkLength -= (endIndexInChunk - copyTargetIndexInChunk);
+
+ // SafeCritical: We ensure that `endIndexInChunk + copyCount` is within range of `m_ChunkChars`, and
+ // also ensure that `copyTargetIndexInChunk + copyCount` is within the chunk.
+
+ // Remove any characters in the end chunk, by sliding the characters down.
+ if (copyTargetIndexInChunk != endIndexInChunk) // Sometimes no move is necessary
+ {
+ ThreadSafeCopy(endChunk.m_ChunkChars, endIndexInChunk, endChunk.m_ChunkChars, copyTargetIndexInChunk, copyCount);
+ }
+
+ Debug.Assert(chunk != null, "We fell off the beginning of the string!");
+ AssertInvariants();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/UTF32Encoding.cs b/src/System.Private.CoreLib/shared/System/Text/UTF32Encoding.cs
new file mode 100644
index 0000000000..7828775ea0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/UTF32Encoding.cs
@@ -0,0 +1,1213 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// Don't override IsAlwaysNormalized because it is just a Unicode Transformation and could be confused.
+//
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ // Encodes text into and out of UTF-32. UTF-32 is a way of writing
+ // Unicode characters with a single storage unit (32 bits) per character,
+ //
+ // The UTF-32 byte order mark is simply the Unicode byte order mark
+ // (0x00FEFF) written in UTF-32 (0x0000FEFF or 0xFFFE0000). The byte order
+ // mark is used mostly to distinguish UTF-32 text from other encodings, and doesn't
+ // switch the byte orderings.
+
+ public sealed class UTF32Encoding : Encoding
+ {
+ /*
+ words bits UTF-32 representation
+ ----- ---- -----------------------------------
+ 1 16 00000000 00000000 xxxxxxxx xxxxxxxx
+ 2 21 00000000 000xxxxx hhhhhhll llllllll
+ ----- ---- -----------------------------------
+
+ Surrogate:
+ Real Unicode value = (HighSurrogate - 0xD800) * 0x400 + (LowSurrogate - 0xDC00) + 0x10000
+ */
+
+ // Used by Encoding.UTF32/BigEndianUTF32 for lazy initialization
+ // The initialization code will not be run until a static member of the class is referenced
+ internal static readonly UTF32Encoding s_default = new UTF32Encoding(bigEndian: false, byteOrderMark: true);
+ internal static readonly UTF32Encoding s_bigEndianDefault = new UTF32Encoding(bigEndian: true, byteOrderMark: true);
+
+ private static readonly byte[] s_bigEndianPreamble = new byte[4] { 0x00, 0x00, 0xFE, 0xFF };
+ private static readonly byte[] s_littleEndianPreamble = new byte[4] { 0xFF, 0xFE, 0x00, 0x00 };
+
+ private bool _emitUTF32ByteOrderMark = false;
+ private bool _isThrowException = false;
+ private bool _bigEndian = false;
+
+
+ public UTF32Encoding() : this(false, true, false)
+ {
+ }
+
+
+ public UTF32Encoding(bool bigEndian, bool byteOrderMark) :
+ this(bigEndian, byteOrderMark, false)
+ {
+ }
+
+
+ public UTF32Encoding(bool bigEndian, bool byteOrderMark, bool throwOnInvalidCharacters) :
+ base(bigEndian ? 12001 : 12000)
+ {
+ _bigEndian = bigEndian;
+ _emitUTF32ByteOrderMark = byteOrderMark;
+ _isThrowException = throwOnInvalidCharacters;
+
+ // Encoding constructor already did this, but it'll be wrong if we're throwing exceptions
+ if (_isThrowException)
+ SetDefaultFallbacks();
+ }
+
+ internal override void SetDefaultFallbacks()
+ {
+ // For UTF-X encodings, we use a replacement fallback with an empty string
+ if (_isThrowException)
+ {
+ this.encoderFallback = EncoderFallback.ExceptionFallback;
+ this.decoderFallback = DecoderFallback.ExceptionFallback;
+ }
+ else
+ {
+ this.encoderFallback = new EncoderReplacementFallback("\xFFFD");
+ this.decoderFallback = new DecoderReplacementFallback("\xFFFD");
+ }
+ }
+
+
+ // The following methods are copied from EncodingNLS.cs.
+ // Unfortunately EncodingNLS.cs is internal and we're public, so we have to re-implement them here.
+ // These should be kept in sync for the following classes:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ // Returns the number of bytes required to encode a range of characters in
+ // a character array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(char[] chars, int index, int count)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - index < count)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input, return 0, avoid fixed empty array problem
+ if (count == 0)
+ return 0;
+
+ // Just call the pointer version
+ fixed (char* pChars = chars)
+ return GetByteCount(pChars + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(String s)
+ {
+ // Validate input
+ if (s==null)
+ throw new ArgumentNullException("s");
+
+ fixed (char* pChars = s)
+ return GetByteCount(pChars, s.Length, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetByteCount(char* chars, int count)
+ {
+ // Validate Parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Call it with empty encoder
+ return GetByteCount(chars, count, null);
+ }
+
+ // Parent method is safe.
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ public override unsafe int GetBytes(String s, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ if (s == null || bytes == null)
+ throw new ArgumentNullException((s == null ? "s" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (s.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("s", SR.ArgumentOutOfRange_IndexCount);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = s) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. An exception occurs if the byte array is not large
+ // enough to hold the complete encoding of the characters. The
+ // GetByteCount method can be used to determine the exact number of
+ // bytes that will be produced for a given range of characters.
+ // Alternatively, the GetMaxByteCount method can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? "chars" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ // If nothing to encode return 0, avoid fixed problem
+ if (charCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = chars) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ // Remember that byteCount is # to decode, not size of array.
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetBytes(chars, charCount, bytes, byteCount, null);
+ }
+
+ // Returns the number of characters produced by decoding a range of bytes
+ // in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetCharCount(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input just return 0, fixed doesn't like 0 length arrays.
+ if (count == 0)
+ return 0;
+
+ // Just call pointer version
+ fixed (byte* pBytes = bytes)
+ return GetCharCount(pBytes + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetCharCount(byte* bytes, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetCharCount(bytes, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? "byteIndex" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if ( bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (charIndex < 0 || charIndex > chars.Length)
+ throw new ArgumentOutOfRangeException("charIndex", SR.ArgumentOutOfRange_Index);
+
+ // If no input, return 0 & avoid fixed problem
+ if (byteCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int charCount = chars.Length - charIndex;
+
+ fixed (byte* pBytes = bytes) fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ // Remember that charCount is # to decode, not size of array
+ return GetChars(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public unsafe override int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetChars(bytes, byteCount, chars, charCount, null);
+ }
+
+ // Returns a string containing the decoded representation of a range of
+ // bytes in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe String GetString(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Avoid problems with empty input buffer
+ if (count == 0) return String.Empty;
+
+ fixed (byte* pBytes = bytes)
+ return String.CreateStringFromEncoding(
+ pBytes + index, count, this);
+ }
+
+ //
+ // End of standard methods copied from EncodingNLS.cs
+ //
+
+ internal override unsafe int GetByteCount(char* chars, int count, EncoderNLS encoder)
+ {
+ Debug.Assert(chars != null, "[UTF32Encoding.GetByteCount]chars!=null");
+ Debug.Assert(count >= 0, "[UTF32Encoding.GetByteCount]count >=0");
+
+ char* end = chars + count;
+ char* charStart = chars;
+ int byteCount = 0;
+
+ char highSurrogate = '\0';
+
+ // For fallback we may need a fallback buffer
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ if (encoder != null)
+ {
+ highSurrogate = encoder._charLeftOver;
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // We mustn't have left over fallback data when counting
+ if (fallbackBuffer.Remaining > 0)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType()));
+ }
+ else
+ {
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ }
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, end, encoder, false);
+
+ char ch;
+ TryAgain:
+
+ while (((ch = fallbackBuffer.InternalGetNextChar()) != 0) || chars < end)
+ {
+ // First unwind any fallback
+ if (ch == 0)
+ {
+ // No fallback, just get next char
+ ch = *chars;
+ chars++;
+ }
+
+ // Do we need a low surrogate?
+ if (highSurrogate != '\0')
+ {
+ //
+ // In previous char, we encounter a high surrogate, so we are expecting a low surrogate here.
+ //
+ if (Char.IsLowSurrogate(ch))
+ {
+ // They're all legal
+ highSurrogate = '\0';
+
+ //
+ // One surrogate pair will be translated into 4 bytes UTF32.
+ //
+
+ byteCount += 4;
+ continue;
+ }
+
+ // We are missing our low surrogate, decrement chars and fallback the high surrogate
+ // The high surrogate may have come from the encoder, but nothing else did.
+ Debug.Assert(chars > charStart,
+ "[UTF32Encoding.GetByteCount]Expected chars to have advanced if no low surrogate");
+ chars--;
+
+ // Do the fallback
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(highSurrogate, ref charsForFallback);
+ chars = charsForFallback;
+
+ // We're going to fallback the old high surrogate.
+ highSurrogate = '\0';
+ continue;
+ }
+
+ // Do we have another high surrogate?
+ if (Char.IsHighSurrogate(ch))
+ {
+ //
+ // We'll have a high surrogate to check next time.
+ //
+ highSurrogate = ch;
+ continue;
+ }
+
+ // Check for illegal characters
+ if (Char.IsLowSurrogate(ch))
+ {
+ // We have a leading low surrogate, do the fallback
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(ch, ref charsForFallback);
+ chars = charsForFallback;
+
+ // Try again with fallback buffer
+ continue;
+ }
+
+ // We get to add the character (4 bytes UTF32)
+ byteCount += 4;
+ }
+
+ // May have to do our last surrogate
+ if ((encoder == null || encoder.MustFlush) && highSurrogate > 0)
+ {
+ // We have to do the fallback for the lonely high surrogate
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(highSurrogate, ref charsForFallback);
+ chars = charsForFallback;
+
+ highSurrogate = (char)0;
+ goto TryAgain;
+ }
+
+ // Check for overflows.
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_GetByteCountOverflow);
+
+ // Shouldn't have anything in fallback buffer for GetByteCount
+ // (don't have to check _throwOnOverflow for count)
+ Debug.Assert(fallbackBuffer.Remaining == 0,
+ "[UTF32Encoding.GetByteCount]Expected empty fallback buffer at end");
+
+ // Return our count
+ return byteCount;
+ }
+
+ internal override unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, EncoderNLS encoder)
+ {
+ Debug.Assert(chars != null, "[UTF32Encoding.GetBytes]chars!=null");
+ Debug.Assert(bytes != null, "[UTF32Encoding.GetBytes]bytes!=null");
+ Debug.Assert(byteCount >= 0, "[UTF32Encoding.GetBytes]byteCount >=0");
+ Debug.Assert(charCount >= 0, "[UTF32Encoding.GetBytes]charCount >=0");
+
+ char* charStart = chars;
+ char* charEnd = chars + charCount;
+ byte* byteStart = bytes;
+ byte* byteEnd = bytes + byteCount;
+
+ char highSurrogate = '\0';
+
+ // For fallback we may need a fallback buffer
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ if (encoder != null)
+ {
+ highSurrogate = encoder._charLeftOver;
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // We mustn't have left over fallback data when not converting
+ if (encoder._throwOnOverflow && fallbackBuffer.Remaining > 0)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType()));
+ }
+ else
+ {
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ }
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, true);
+
+ char ch;
+ TryAgain:
+
+ while (((ch = fallbackBuffer.InternalGetNextChar()) != 0) || chars < charEnd)
+ {
+ // First unwind any fallback
+ if (ch == 0)
+ {
+ // No fallback, just get next char
+ ch = *chars;
+ chars++;
+ }
+
+ // Do we need a low surrogate?
+ if (highSurrogate != '\0')
+ {
+ //
+ // In previous char, we encountered a high surrogate, so we are expecting a low surrogate here.
+ //
+ if (Char.IsLowSurrogate(ch))
+ {
+ // Is it a legal one?
+ uint iTemp = GetSurrogate(highSurrogate, ch);
+ highSurrogate = '\0';
+
+ //
+ // One surrogate pair will be translated into 4 bytes UTF32.
+ //
+ if (bytes + 3 >= byteEnd)
+ {
+ // Don't have 4 bytes
+ if (fallbackBuffer.bFallingBack)
+ {
+ fallbackBuffer.MovePrevious(); // Aren't using these 2 fallback chars
+ fallbackBuffer.MovePrevious();
+ }
+ else
+ {
+ // If we don't have enough room, then either we should've advanced a while
+ // or we should have bytes==byteStart and throw below
+ Debug.Assert(chars > charStart + 1 || bytes == byteStart,
+ "[UnicodeEncoding.GetBytes]Expected chars to have when no room to add surrogate pair");
+ chars -= 2; // Aren't using those 2 chars
+ }
+ ThrowBytesOverflow(encoder, bytes == byteStart); // Throw maybe (if no bytes written)
+ highSurrogate = (char)0; // Nothing left over (we backed up to start of pair if supplimentary)
+ break;
+ }
+
+ if (_bigEndian)
+ {
+ *(bytes++) = (byte)(0x00);
+ *(bytes++) = (byte)(iTemp >> 16); // Implies & 0xFF, which isn't needed cause high are all 0
+ *(bytes++) = (byte)(iTemp >> 8); // Implies & 0xFF
+ *(bytes++) = (byte)(iTemp); // Implies & 0xFF
+ }
+ else
+ {
+ *(bytes++) = (byte)(iTemp); // Implies & 0xFF
+ *(bytes++) = (byte)(iTemp >> 8); // Implies & 0xFF
+ *(bytes++) = (byte)(iTemp >> 16); // Implies & 0xFF, which isn't needed cause high are all 0
+ *(bytes++) = (byte)(0x00);
+ }
+ continue;
+ }
+
+ // We are missing our low surrogate, decrement chars and fallback the high surrogate
+ // The high surrogate may have come from the encoder, but nothing else did.
+ Debug.Assert(chars > charStart,
+ "[UTF32Encoding.GetBytes]Expected chars to have advanced if no low surrogate");
+ chars--;
+
+ // Do the fallback
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(highSurrogate, ref charsForFallback);
+ chars = charsForFallback;
+
+ // We're going to fallback the old high surrogate.
+ highSurrogate = '\0';
+ continue;
+ }
+
+ // Do we have another high surrogate?, if so remember it
+ if (Char.IsHighSurrogate(ch))
+ {
+ //
+ // We'll have a high surrogate to check next time.
+ //
+ highSurrogate = ch;
+ continue;
+ }
+
+ // Check for illegal characters (low surrogate)
+ if (Char.IsLowSurrogate(ch))
+ {
+ // We have a leading low surrogate, do the fallback
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(ch, ref charsForFallback);
+ chars = charsForFallback;
+
+ // Try again with fallback buffer
+ continue;
+ }
+
+ // We get to add the character, yippee.
+ if (bytes + 3 >= byteEnd)
+ {
+ // Don't have 4 bytes
+ if (fallbackBuffer.bFallingBack)
+ fallbackBuffer.MovePrevious(); // Aren't using this fallback char
+ else
+ {
+ // Must've advanced already
+ Debug.Assert(chars > charStart,
+ "[UTF32Encoding.GetBytes]Expected chars to have advanced if normal character");
+ chars--; // Aren't using this char
+ }
+ ThrowBytesOverflow(encoder, bytes == byteStart); // Throw maybe (if no bytes written)
+ break; // Didn't throw, stop
+ }
+
+ if (_bigEndian)
+ {
+ *(bytes++) = (byte)(0x00);
+ *(bytes++) = (byte)(0x00);
+ *(bytes++) = (byte)((uint)ch >> 8); // Implies & 0xFF
+ *(bytes++) = (byte)(ch); // Implies & 0xFF
+ }
+ else
+ {
+ *(bytes++) = (byte)(ch); // Implies & 0xFF
+ *(bytes++) = (byte)((uint)ch >> 8); // Implies & 0xFF
+ *(bytes++) = (byte)(0x00);
+ *(bytes++) = (byte)(0x00);
+ }
+ }
+
+ // May have to do our last surrogate
+ if ((encoder == null || encoder.MustFlush) && highSurrogate > 0)
+ {
+ // We have to do the fallback for the lonely high surrogate
+ charsForFallback = chars;
+ fallbackBuffer.InternalFallback(highSurrogate, ref charsForFallback);
+ chars = charsForFallback;
+
+ highSurrogate = (char)0;
+ goto TryAgain;
+ }
+
+ // Fix our encoder if we have one
+ Debug.Assert(highSurrogate == 0 || (encoder != null && !encoder.MustFlush),
+ "[UTF32Encoding.GetBytes]Expected encoder to be flushed.");
+
+ if (encoder != null)
+ {
+ // Remember our left over surrogate (or 0 if flushing)
+ encoder._charLeftOver = highSurrogate;
+
+ // Need # chars used
+ encoder._charsUsed = (int)(chars - charStart);
+ }
+
+ // return the new length
+ return (int)(bytes - byteStart);
+ }
+
+ internal override unsafe int GetCharCount(byte* bytes, int count, DecoderNLS baseDecoder)
+ {
+ Debug.Assert(bytes != null, "[UTF32Encoding.GetCharCount]bytes!=null");
+ Debug.Assert(count >= 0, "[UTF32Encoding.GetCharCount]count >=0");
+
+ UTF32Decoder decoder = (UTF32Decoder)baseDecoder;
+
+ // None so far!
+ int charCount = 0;
+ byte* end = bytes + count;
+ byte* byteStart = bytes;
+
+ // Set up decoder
+ int readCount = 0;
+ uint iChar = 0;
+
+ // For fallback we may need a fallback buffer
+ DecoderFallbackBuffer fallbackBuffer = null;
+
+ // See if there's anything in our decoder
+ if (decoder != null)
+ {
+ readCount = decoder.readByteCount;
+ iChar = (uint)decoder.iChar;
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Shouldn't have anything in fallback buffer for GetCharCount
+ // (don't have to check _throwOnOverflow for chars or count)
+ Debug.Assert(fallbackBuffer.Remaining == 0,
+ "[UTF32Encoding.GetCharCount]Expected empty fallback buffer at start");
+ }
+ else
+ {
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ }
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, null);
+
+ // Loop through our input, 4 characters at a time!
+ while (bytes < end && charCount >= 0)
+ {
+ // Get our next character
+ if (_bigEndian)
+ {
+ // Scoot left and add it to the bottom
+ iChar <<= 8;
+ iChar += *(bytes++);
+ }
+ else
+ {
+ // Scoot right and add it to the top
+ iChar >>= 8;
+ iChar += (uint)(*(bytes++)) << 24;
+ }
+
+ readCount++;
+
+ // See if we have all the bytes yet
+ if (readCount < 4)
+ continue;
+
+ // Have the bytes
+ readCount = 0;
+
+ // See if its valid to encode
+ if (iChar > 0x10FFFF || (iChar >= 0xD800 && iChar <= 0xDFFF))
+ {
+ // Need to fall back these 4 bytes
+ byte[] fallbackBytes;
+ if (_bigEndian)
+ {
+ fallbackBytes = new byte[] {
+ unchecked((byte)(iChar>>24)), unchecked((byte)(iChar>>16)),
+ unchecked((byte)(iChar>>8)), unchecked((byte)(iChar)) };
+ }
+ else
+ {
+ fallbackBytes = new byte[] {
+ unchecked((byte)(iChar)), unchecked((byte)(iChar>>8)),
+ unchecked((byte)(iChar>>16)), unchecked((byte)(iChar>>24)) };
+ }
+
+ charCount += fallbackBuffer.InternalFallback(fallbackBytes, bytes);
+
+ // Ignore the illegal character
+ iChar = 0;
+ continue;
+ }
+
+ // Ok, we have something we can add to our output
+ if (iChar >= 0x10000)
+ {
+ // Surrogates take 2
+ charCount++;
+ }
+
+ // Add the rest of the surrogate or our normal character
+ charCount++;
+
+ // iChar is back to 0
+ iChar = 0;
+ }
+
+ // See if we have something left over that has to be decoded
+ if (readCount > 0 && (decoder == null || decoder.MustFlush))
+ {
+ // Oops, there's something left over with no place to go.
+ byte[] fallbackBytes = new byte[readCount];
+ if (_bigEndian)
+ {
+ while (readCount > 0)
+ {
+ fallbackBytes[--readCount] = unchecked((byte)iChar);
+ iChar >>= 8;
+ }
+ }
+ else
+ {
+ while (readCount > 0)
+ {
+ fallbackBytes[--readCount] = unchecked((byte)(iChar >> 24));
+ iChar <<= 8;
+ }
+ }
+
+ charCount += fallbackBuffer.InternalFallback(fallbackBytes, bytes);
+ }
+
+ // Check for overflows.
+ if (charCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_GetByteCountOverflow);
+
+ // Shouldn't have anything in fallback buffer for GetCharCount
+ // (don't have to check _throwOnOverflow for chars or count)
+ Debug.Assert(fallbackBuffer.Remaining == 0,
+ "[UTF32Encoding.GetCharCount]Expected empty fallback buffer at end");
+
+ // Return our count
+ return charCount;
+ }
+
+ internal override unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, DecoderNLS baseDecoder)
+ {
+ Debug.Assert(chars != null, "[UTF32Encoding.GetChars]chars!=null");
+ Debug.Assert(bytes != null, "[UTF32Encoding.GetChars]bytes!=null");
+ Debug.Assert(byteCount >= 0, "[UTF32Encoding.GetChars]byteCount >=0");
+ Debug.Assert(charCount >= 0, "[UTF32Encoding.GetChars]charCount >=0");
+
+ UTF32Decoder decoder = (UTF32Decoder)baseDecoder;
+
+ // None so far!
+ char* charStart = chars;
+ char* charEnd = chars + charCount;
+
+ byte* byteStart = bytes;
+ byte* byteEnd = bytes + byteCount;
+
+ // See if there's anything in our decoder (but don't clear it yet)
+ int readCount = 0;
+ uint iChar = 0;
+
+ // For fallback we may need a fallback buffer
+ DecoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ // See if there's anything in our decoder
+ if (decoder != null)
+ {
+ readCount = decoder.readByteCount;
+ iChar = (uint)decoder.iChar;
+ fallbackBuffer = baseDecoder.FallbackBuffer;
+
+ // Shouldn't have anything in fallback buffer for GetChars
+ // (don't have to check _throwOnOverflow for chars)
+ Debug.Assert(fallbackBuffer.Remaining == 0,
+ "[UTF32Encoding.GetChars]Expected empty fallback buffer at start");
+ }
+ else
+ {
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ }
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(bytes, chars + charCount);
+
+ // Loop through our input, 4 characters at a time!
+ while (bytes < byteEnd)
+ {
+ // Get our next character
+ if (_bigEndian)
+ {
+ // Scoot left and add it to the bottom
+ iChar <<= 8;
+ iChar += *(bytes++);
+ }
+ else
+ {
+ // Scoot right and add it to the top
+ iChar >>= 8;
+ iChar += (uint)(*(bytes++)) << 24;
+ }
+
+ readCount++;
+
+ // See if we have all the bytes yet
+ if (readCount < 4)
+ continue;
+
+ // Have the bytes
+ readCount = 0;
+
+ // See if its valid to encode
+ if (iChar > 0x10FFFF || (iChar >= 0xD800 && iChar <= 0xDFFF))
+ {
+ // Need to fall back these 4 bytes
+ byte[] fallbackBytes;
+ if (_bigEndian)
+ {
+ fallbackBytes = new byte[] {
+ unchecked((byte)(iChar>>24)), unchecked((byte)(iChar>>16)),
+ unchecked((byte)(iChar>>8)), unchecked((byte)(iChar)) };
+ }
+ else
+ {
+ fallbackBytes = new byte[] {
+ unchecked((byte)(iChar)), unchecked((byte)(iChar>>8)),
+ unchecked((byte)(iChar>>16)), unchecked((byte)(iChar>>24)) };
+ }
+
+ // Chars won't be updated unless this works.
+ charsForFallback = chars;
+ bool fallbackResult = fallbackBuffer.InternalFallback(fallbackBytes, bytes, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (!fallbackResult)
+ {
+ // Couldn't fallback, throw or wait til next time
+ // We either read enough bytes for bytes-=4 to work, or we're
+ // going to throw in ThrowCharsOverflow because chars == charStart
+ Debug.Assert(bytes >= byteStart + 4 || chars == charStart,
+ "[UTF32Encoding.GetChars]Expected to have consumed bytes or throw (bad surrogate)");
+ bytes -= 4; // get back to where we were
+ iChar = 0; // Remembering nothing
+ fallbackBuffer.InternalReset();
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ break; // Stop here, didn't throw
+ }
+
+ // Ignore the illegal character
+ iChar = 0;
+ continue;
+ }
+
+
+ // Ok, we have something we can add to our output
+ if (iChar >= 0x10000)
+ {
+ // Surrogates take 2
+ if (chars >= charEnd - 1)
+ {
+ // Throwing or stopping
+ // We either read enough bytes for bytes-=4 to work, or we're
+ // going to throw in ThrowCharsOverflow because chars == charStart
+ Debug.Assert(bytes >= byteStart + 4 || chars == charStart,
+ "[UTF32Encoding.GetChars]Expected to have consumed bytes or throw (surrogate)");
+ bytes -= 4; // get back to where we were
+ iChar = 0; // Remembering nothing
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ break; // Stop here, didn't throw
+ }
+
+ *(chars++) = GetHighSurrogate(iChar);
+ iChar = GetLowSurrogate(iChar);
+ }
+ // Bounds check for normal character
+ else if (chars >= charEnd)
+ {
+ // Throwing or stopping
+ // We either read enough bytes for bytes-=4 to work, or we're
+ // going to throw in ThrowCharsOverflow because chars == charStart
+ Debug.Assert(bytes >= byteStart + 4 || chars == charStart,
+ "[UTF32Encoding.GetChars]Expected to have consumed bytes or throw (normal char)");
+ bytes -= 4; // get back to where we were
+ iChar = 0; // Remembering nothing
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ break; // Stop here, didn't throw
+ }
+
+ // Add the rest of the surrogate or our normal character
+ *(chars++) = (char)iChar;
+
+ // iChar is back to 0
+ iChar = 0;
+ }
+
+ // See if we have something left over that has to be decoded
+ if (readCount > 0 && (decoder == null || decoder.MustFlush))
+ {
+ // Oops, there's something left over with no place to go.
+ byte[] fallbackBytes = new byte[readCount];
+ int tempCount = readCount;
+ if (_bigEndian)
+ {
+ while (tempCount > 0)
+ {
+ fallbackBytes[--tempCount] = unchecked((byte)iChar);
+ iChar >>= 8;
+ }
+ }
+ else
+ {
+ while (tempCount > 0)
+ {
+ fallbackBytes[--tempCount] = unchecked((byte)(iChar >> 24));
+ iChar <<= 8;
+ }
+ }
+
+ charsForFallback = chars;
+ bool fallbackResult = fallbackBuffer.InternalFallback(fallbackBytes, bytes, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (!fallbackResult)
+ {
+ // Couldn't fallback.
+ fallbackBuffer.InternalReset();
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ // Stop here, didn't throw, backed up, so still nothing in buffer
+ }
+ else
+ {
+ // Don't clear our decoder unless we could fall it back.
+ // If we caught the if above, then we're a convert() and will catch this next time.
+ readCount = 0;
+ iChar = 0;
+ }
+ }
+
+ // Remember any left over stuff, clearing buffer as well for MustFlush
+ if (decoder != null)
+ {
+ decoder.iChar = (int)iChar;
+ decoder.readByteCount = readCount;
+ decoder._bytesUsed = (int)(bytes - byteStart);
+ }
+
+ // Shouldn't have anything in fallback buffer for GetChars
+ // (don't have to check _throwOnOverflow for chars)
+ Debug.Assert(fallbackBuffer.Remaining == 0,
+ "[UTF32Encoding.GetChars]Expected empty fallback buffer at end");
+
+ // Return our count
+ return (int)(chars - charStart);
+ }
+
+
+ private uint GetSurrogate(char cHigh, char cLow)
+ {
+ return (((uint)cHigh - 0xD800) * 0x400) + ((uint)cLow - 0xDC00) + 0x10000;
+ }
+
+ private char GetHighSurrogate(uint iChar)
+ {
+ return (char)((iChar - 0x10000) / 0x400 + 0xD800);
+ }
+
+ private char GetLowSurrogate(uint iChar)
+ {
+ return (char)((iChar - 0x10000) % 0x400 + 0xDC00);
+ }
+
+
+ public override Decoder GetDecoder()
+ {
+ return new UTF32Decoder(this);
+ }
+
+
+ public override Encoder GetEncoder()
+ {
+ return new EncoderNLS(this);
+ }
+
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ if (charCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(charCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Characters would be # of characters + 1 in case left over high surrogate is ? * max fallback
+ long byteCount = (long)charCount + 1;
+
+ if (EncoderFallback.MaxCharCount > 1)
+ byteCount *= EncoderFallback.MaxCharCount;
+
+ // 4 bytes per char
+ byteCount *= 4;
+
+ if (byteCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GetByteCountOverflow);
+
+ return (int)byteCount;
+ }
+
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(byteCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // A supplementary character becomes 2 surrogate characters, so 4 input bytes becomes 2 chars,
+ // plus we may have 1 surrogate char left over if the decoder has 3 bytes in it already for a non-bmp char.
+ // Have to add another one because 1/2 == 0, but 3 bytes left over could be 2 char surrogate pair
+ int charCount = (byteCount / 2) + 2;
+
+ // Also consider fallback because our input bytes could be out of range of unicode.
+ // Since fallback would fallback 4 bytes at a time, we'll only fall back 1/2 of MaxCharCount.
+ if (DecoderFallback.MaxCharCount > 2)
+ {
+ // Multiply time fallback size
+ charCount *= DecoderFallback.MaxCharCount;
+
+ // We were already figuring 2 chars per 4 bytes, but fallback will be different #
+ charCount /= 2;
+ }
+
+ if (charCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(byteCount), SR.ArgumentOutOfRange_GetCharCountOverflow);
+
+ return (int)charCount;
+ }
+
+
+ public override byte[] GetPreamble()
+ {
+ if (_emitUTF32ByteOrderMark)
+ {
+ // Allocate new array to prevent users from modifying it.
+ if (_bigEndian)
+ {
+ return new byte[4] { 0x00, 0x00, 0xFE, 0xFF };
+ }
+ else
+ {
+ return new byte[4] { 0xFF, 0xFE, 0x00, 0x00 }; // 00 00 FE FF
+ }
+ }
+ else
+ return Array.Empty<byte>();
+ }
+
+ public override ReadOnlySpan<byte> Preamble =>
+ GetType() != typeof(UTF32Encoding) ? GetPreamble() : // in case a derived UTF32Encoding overrode GetPreamble
+ _emitUTF32ByteOrderMark ? (_bigEndian ? s_bigEndianPreamble : s_littleEndianPreamble) :
+ Array.Empty<byte>();
+
+ public override bool Equals(Object value)
+ {
+ UTF32Encoding that = value as UTF32Encoding;
+ if (that != null)
+ {
+ return (_emitUTF32ByteOrderMark == that._emitUTF32ByteOrderMark) &&
+ (_bigEndian == that._bigEndian) &&
+ (EncoderFallback.Equals(that.EncoderFallback)) &&
+ (DecoderFallback.Equals(that.DecoderFallback));
+ }
+ return (false);
+ }
+
+
+ public override int GetHashCode()
+ {
+ //Not great distribution, but this is relatively unlikely to be used as the key in a hashtable.
+ return this.EncoderFallback.GetHashCode() + this.DecoderFallback.GetHashCode() +
+ CodePage + (_emitUTF32ByteOrderMark ? 4 : 0) + (_bigEndian ? 8 : 0);
+ }
+
+ private sealed class UTF32Decoder : DecoderNLS
+ {
+ // Need a place to store any extra bytes we may have picked up
+ internal int iChar = 0;
+ internal int readByteCount = 0;
+
+ public UTF32Decoder(UTF32Encoding encoding) : base(encoding)
+ {
+ // base calls reset
+ }
+
+ public override void Reset()
+ {
+ this.iChar = 0;
+ this.readByteCount = 0;
+ if (_fallbackBuffer != null)
+ _fallbackBuffer.Reset();
+ }
+
+ // Anything left in our decoder?
+ internal override bool HasState
+ {
+ get
+ {
+ // ReadByteCount is our flag. (iChar==0 doesn't mean much).
+ return (this.readByteCount != 0);
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/UTF7Encoding.cs b/src/System.Private.CoreLib/shared/System/Text/UTF7Encoding.cs
new file mode 100644
index 0000000000..0246c28915
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/UTF7Encoding.cs
@@ -0,0 +1,978 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// Don't override IsAlwaysNormalized because it is just a Unicode Transformation and could be confused.
+//
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ public class UTF7Encoding : Encoding
+ {
+ private const String base64Chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ // 0123456789111111111122222222223333333333444444444455555555556666
+ // 012345678901234567890123456789012345678901234567890123
+
+ // These are the characters that can be directly encoded in UTF7.
+ private const String directChars =
+ "\t\n\r '(),-./0123456789:?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+ // These are the characters that can be optionally directly encoded in UTF7.
+ private const String optionalChars =
+ "!\"#$%&*;<=>@[]^_`{|}";
+
+ // Used by Encoding.UTF7 for lazy initialization
+ // The initialization code will not be run until a static member of the class is referenced
+ internal static readonly UTF7Encoding s_default = new UTF7Encoding();
+
+ // The set of base 64 characters.
+ private byte[] _base64Bytes;
+ // The decoded bits for every base64 values. This array has a size of 128 elements.
+ // The index is the code point value of the base 64 characters. The value is -1 if
+ // the code point is not a valid base 64 character. Otherwise, the value is a value
+ // from 0 ~ 63.
+ private sbyte[] _base64Values;
+ // The array to decide if a Unicode code point below 0x80 can be directly encoded in UTF7.
+ // This array has a size of 128.
+ private bool[] _directEncode;
+
+ private bool _allowOptionals;
+
+ private const int UTF7_CODEPAGE = 65000;
+
+
+ public UTF7Encoding()
+ : this(false)
+ {
+ }
+
+ public UTF7Encoding(bool allowOptionals)
+ : base(UTF7_CODEPAGE) //Set the data item.
+ {
+ // Allowing optionals?
+ _allowOptionals = allowOptionals;
+
+ // Make our tables
+ MakeTables();
+ }
+
+ private void MakeTables()
+ {
+ // Build our tables
+ _base64Bytes = new byte[64];
+ for (int i = 0; i < 64; i++) _base64Bytes[i] = (byte)base64Chars[i];
+ _base64Values = new sbyte[128];
+ for (int i = 0; i < 128; i++) _base64Values[i] = -1;
+ for (int i = 0; i < 64; i++) _base64Values[_base64Bytes[i]] = (sbyte)i;
+ _directEncode = new bool[128];
+ int count = directChars.Length;
+ for (int i = 0; i < count; i++)
+ {
+ _directEncode[directChars[i]] = true;
+ }
+
+ if (_allowOptionals)
+ {
+ count = optionalChars.Length;
+ for (int i = 0; i < count; i++)
+ {
+ _directEncode[optionalChars[i]] = true;
+ }
+ }
+ }
+
+ // We go ahead and set this because Encoding expects it, however nothing can fall back in UTF7.
+ internal override void SetDefaultFallbacks()
+ {
+ // UTF7 had an odd decoderFallback behavior, and the Encoder fallback
+ // is irrelevant because we encode surrogates individually and never check for unmatched ones
+ // (so nothing can fallback during encoding)
+ this.encoderFallback = new EncoderReplacementFallback(String.Empty);
+ this.decoderFallback = new DecoderUTF7Fallback();
+ }
+
+ public override bool Equals(Object value)
+ {
+ UTF7Encoding that = value as UTF7Encoding;
+ if (that != null)
+ {
+ return (_allowOptionals == that._allowOptionals) &&
+ (EncoderFallback.Equals(that.EncoderFallback)) &&
+ (DecoderFallback.Equals(that.DecoderFallback));
+ }
+ return (false);
+ }
+
+ // Compared to all the other encodings, variations of UTF7 are unlikely
+
+ public override int GetHashCode()
+ {
+ return this.CodePage + this.EncoderFallback.GetHashCode() + this.DecoderFallback.GetHashCode();
+ }
+
+ // The following methods are copied from EncodingNLS.cs.
+ // Unfortunately EncodingNLS.cs is internal and we're public, so we have to re-implement them here.
+ // These should be kept in sync for the following classes:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ // Returns the number of bytes required to encode a range of characters in
+ // a character array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(char[] chars, int index, int count)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - index < count)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input, return 0, avoid fixed empty array problem
+ if (count == 0)
+ return 0;
+
+ // Just call the pointer version
+ fixed (char* pChars = chars)
+ return GetByteCount(pChars + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(string s)
+ {
+ // Validate input
+ if (s==null)
+ throw new ArgumentNullException("s");
+
+ fixed (char* pChars = s)
+ return GetByteCount(pChars, s.Length, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetByteCount(char* chars, int count)
+ {
+ // Validate Parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Call it with empty encoder
+ return GetByteCount(chars, count, null);
+ }
+
+ // Parent method is safe.
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ public override unsafe int GetBytes(string s, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ if (s == null || bytes == null)
+ throw new ArgumentNullException((s == null ? "s" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (s.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("s", SR.ArgumentOutOfRange_IndexCount);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = s) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. An exception occurs if the byte array is not large
+ // enough to hold the complete encoding of the characters. The
+ // GetByteCount method can be used to determine the exact number of
+ // bytes that will be produced for a given range of characters.
+ // Alternatively, the GetMaxByteCount method can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? "chars" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ // If nothing to encode return 0, avoid fixed problem
+ if (charCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = chars) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ // Remember that byteCount is # to decode, not size of array.
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetBytes(chars, charCount, bytes, byteCount, null);
+ }
+
+ // Returns the number of characters produced by decoding a range of bytes
+ // in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetCharCount(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input just return 0, fixed doesn't like 0 length arrays.
+ if (count == 0)
+ return 0;
+
+ // Just call pointer version
+ fixed (byte* pBytes = bytes)
+ return GetCharCount(pBytes + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetCharCount(byte* bytes, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetCharCount(bytes, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? "byteIndex" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if ( bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (charIndex < 0 || charIndex > chars.Length)
+ throw new ArgumentOutOfRangeException("charIndex", SR.ArgumentOutOfRange_Index);
+
+ // If no input, return 0 & avoid fixed problem
+ if (byteCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int charCount = chars.Length - charIndex;
+
+ fixed (byte* pBytes = bytes) fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ // Remember that charCount is # to decode, not size of array
+ return GetChars(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public unsafe override int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetChars(bytes, byteCount, chars, charCount, null);
+ }
+
+ // Returns a string containing the decoded representation of a range of
+ // bytes in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe String GetString(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Avoid problems with empty input buffer
+ if (count == 0) return String.Empty;
+
+ fixed (byte* pBytes = bytes)
+ return String.CreateStringFromEncoding(
+ pBytes + index, count, this);
+ }
+
+ //
+ // End of standard methods copied from EncodingNLS.cs
+ //
+
+ internal override unsafe int GetByteCount(char* chars, int count, EncoderNLS baseEncoder)
+ {
+ Debug.Assert(chars != null, "[UTF7Encoding.GetByteCount]chars!=null");
+ Debug.Assert(count >= 0, "[UTF7Encoding.GetByteCount]count >=0");
+
+ // Just call GetBytes with bytes == null
+ return GetBytes(chars, count, null, 0, baseEncoder);
+ }
+
+ internal override unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, EncoderNLS baseEncoder)
+ {
+ Debug.Assert(byteCount >= 0, "[UTF7Encoding.GetBytes]byteCount >=0");
+ Debug.Assert(chars != null, "[UTF7Encoding.GetBytes]chars!=null");
+ Debug.Assert(charCount >= 0, "[UTF7Encoding.GetBytes]charCount >=0");
+
+ // Get encoder info
+ UTF7Encoding.Encoder encoder = (UTF7Encoding.Encoder)baseEncoder;
+
+ // Default bits & count
+ int bits = 0;
+ int bitCount = -1;
+
+ // prepare our helpers
+ Encoding.EncodingByteBuffer buffer = new Encoding.EncodingByteBuffer(
+ this, encoder, bytes, byteCount, chars, charCount);
+
+ if (encoder != null)
+ {
+ bits = encoder.bits;
+ bitCount = encoder.bitCount;
+
+ // May have had too many left over
+ while (bitCount >= 6)
+ {
+ bitCount -= 6;
+ // If we fail we'll never really have enough room
+ if (!buffer.AddByte(_base64Bytes[(bits >> bitCount) & 0x3F]))
+ ThrowBytesOverflow(encoder, buffer.Count == 0);
+ }
+ }
+
+ while (buffer.MoreData)
+ {
+ char currentChar = buffer.GetNextChar();
+
+ if (currentChar < 0x80 && _directEncode[currentChar])
+ {
+ if (bitCount >= 0)
+ {
+ if (bitCount > 0)
+ {
+ // Try to add the next byte
+ if (!buffer.AddByte(_base64Bytes[bits << 6 - bitCount & 0x3F]))
+ break; // Stop here, didn't throw
+
+ bitCount = 0;
+ }
+
+ // Need to get emit '-' and our char, 2 bytes total
+ if (!buffer.AddByte((byte)'-'))
+ break; // Stop here, didn't throw
+
+ bitCount = -1;
+ }
+
+ // Need to emit our char
+ if (!buffer.AddByte((byte)currentChar))
+ break; // Stop here, didn't throw
+ }
+ else if (bitCount < 0 && currentChar == '+')
+ {
+ if (!buffer.AddByte((byte)'+', (byte)'-'))
+ break; // Stop here, didn't throw
+ }
+ else
+ {
+ if (bitCount < 0)
+ {
+ // Need to emit a + and 12 bits (3 bytes)
+ // Only 12 of the 16 bits will be emitted this time, the other 4 wait 'til next time
+ if (!buffer.AddByte((byte)'+'))
+ break; // Stop here, didn't throw
+
+ // We're now in bit mode, but haven't stored data yet
+ bitCount = 0;
+ }
+
+ // Add our bits
+ bits = bits << 16 | currentChar;
+ bitCount += 16;
+
+ while (bitCount >= 6)
+ {
+ bitCount -= 6;
+ if (!buffer.AddByte(_base64Bytes[(bits >> bitCount) & 0x3F]))
+ {
+ bitCount += 6; // We didn't use these bits
+ currentChar = buffer.GetNextChar(); // We're processing this char still, but AddByte
+ // --'d it when we ran out of space
+ break; // Stop here, not enough room for bytes
+ }
+ }
+
+ if (bitCount >= 6)
+ break; // Didn't have room to encode enough bits
+ }
+ }
+
+ // Now if we have bits left over we have to encode them.
+ // MustFlush may have been cleared by encoding.ThrowBytesOverflow earlier if converting
+ if (bitCount >= 0 && (encoder == null || encoder.MustFlush))
+ {
+ // Do we have bits we have to stick in?
+ if (bitCount > 0)
+ {
+ if (buffer.AddByte(_base64Bytes[(bits << (6 - bitCount)) & 0x3F]))
+ {
+ // Emitted spare bits, 0 bits left
+ bitCount = 0;
+ }
+ }
+
+ // If converting and failed bitCount above, then we'll fail this too
+ if (buffer.AddByte((byte)'-'))
+ {
+ // turned off bit mode';
+ bits = 0;
+ bitCount = -1;
+ }
+ else
+ // If not successful, convert will maintain state for next time, also
+ // AddByte will have decremented our char count, however we need it to remain the same
+ buffer.GetNextChar();
+ }
+
+ // Do we have an encoder we're allowed to use?
+ // bytes == null if counting, so don't use encoder then
+ if (bytes != null && encoder != null)
+ {
+ // We already cleared bits & bitcount for mustflush case
+ encoder.bits = bits;
+ encoder.bitCount = bitCount;
+ encoder._charsUsed = buffer.CharsUsed;
+ }
+
+ return buffer.Count;
+ }
+
+ internal override unsafe int GetCharCount(byte* bytes, int count, DecoderNLS baseDecoder)
+ {
+ Debug.Assert(count >= 0, "[UTF7Encoding.GetCharCount]count >=0");
+ Debug.Assert(bytes != null, "[UTF7Encoding.GetCharCount]bytes!=null");
+
+ // Just call GetChars with null char* to do counting
+ return GetChars(bytes, count, null, 0, baseDecoder);
+ }
+
+ internal override unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, DecoderNLS baseDecoder)
+ {
+ Debug.Assert(byteCount >= 0, "[UTF7Encoding.GetChars]byteCount >=0");
+ Debug.Assert(bytes != null, "[UTF7Encoding.GetChars]bytes!=null");
+ Debug.Assert(charCount >= 0, "[UTF7Encoding.GetChars]charCount >=0");
+
+ // Might use a decoder
+ UTF7Encoding.Decoder decoder = (UTF7Encoding.Decoder)baseDecoder;
+
+ // Get our output buffer info.
+ Encoding.EncodingCharBuffer buffer = new Encoding.EncodingCharBuffer(
+ this, decoder, chars, charCount, bytes, byteCount);
+
+ // Get decoder info
+ int bits = 0;
+ int bitCount = -1;
+ bool firstByte = false;
+ if (decoder != null)
+ {
+ bits = decoder.bits;
+ bitCount = decoder.bitCount;
+ firstByte = decoder.firstByte;
+
+ Debug.Assert(firstByte == false || decoder.bitCount <= 0,
+ "[UTF7Encoding.GetChars]If remembered bits, then first byte flag shouldn't be set");
+ }
+
+ // We may have had bits in the decoder that we couldn't output last time, so do so now
+ if (bitCount >= 16)
+ {
+ // Check our decoder buffer
+ if (!buffer.AddChar((char)((bits >> (bitCount - 16)) & 0xFFFF)))
+ ThrowCharsOverflow(decoder, true); // Always throw, they need at least 1 char even in Convert
+
+ // Used this one, clean up extra bits
+ bitCount -= 16;
+ }
+
+ // Loop through the input
+ while (buffer.MoreData)
+ {
+ byte currentByte = buffer.GetNextByte();
+ int c;
+
+ if (bitCount >= 0)
+ {
+ //
+ // Modified base 64 encoding.
+ //
+ sbyte v;
+ if (currentByte < 0x80 && ((v = _base64Values[currentByte]) >= 0))
+ {
+ firstByte = false;
+ bits = (bits << 6) | ((byte)v);
+ bitCount += 6;
+ if (bitCount >= 16)
+ {
+ c = (bits >> (bitCount - 16)) & 0xFFFF;
+ bitCount -= 16;
+ }
+ // If not enough bits just continue
+ else continue;
+ }
+ else
+ {
+ // If it wasn't a base 64 byte, everything's going to turn off base 64 mode
+ bitCount = -1;
+
+ if (currentByte != '-')
+ {
+ // >= 0x80 (because of 1st if statemtn)
+ // We need this check since the _base64Values[b] check below need b <= 0x7f.
+ // This is not a valid base 64 byte. Terminate the shifted-sequence and
+ // emit this byte.
+
+ // not in base 64 table
+ // According to the RFC 1642 and the example code of UTF-7
+ // in Unicode 2.0, we should just zero-extend the invalid UTF7 byte
+
+ // Chars won't be updated unless this works, try to fallback
+ if (!buffer.Fallback(currentByte))
+ break; // Stop here, didn't throw
+
+ // Used that byte, we're done with it
+ continue;
+ }
+
+ //
+ // The encoding for '+' is "+-".
+ //
+ if (firstByte) c = '+';
+ // We just turn it off if not emitting a +, so we're done.
+ else continue;
+ }
+ //
+ // End of modified base 64 encoding block.
+ //
+ }
+ else if (currentByte == '+')
+ {
+ //
+ // Found the start of a modified base 64 encoding block or a plus sign.
+ //
+ bitCount = 0;
+ firstByte = true;
+ continue;
+ }
+ else
+ {
+ // Normal character
+ if (currentByte >= 0x80)
+ {
+ // Try to fallback
+ if (!buffer.Fallback(currentByte))
+ break; // Stop here, didn't throw
+
+ // Done falling back
+ continue;
+ }
+
+ // Use the normal character
+ c = currentByte;
+ }
+
+ if (c >= 0)
+ {
+ // Check our buffer
+ if (!buffer.AddChar((char)c))
+ {
+ // No room. If it was a plain char we'll try again later.
+ // Note, we'll consume this byte and stick it in decoder, even if we can't output it
+ if (bitCount >= 0) // Can we rememmber this byte (char)
+ {
+ buffer.AdjustBytes(+1); // Need to readd the byte that AddChar subtracted when it failed
+ bitCount += 16; // We'll still need that char we have in our bits
+ }
+ break; // didn't throw, stop
+ }
+ }
+ }
+
+ // Stick stuff in the decoder if we can (chars == null if counting, so don't store decoder)
+ if (chars != null && decoder != null)
+ {
+ // MustFlush? (Could've been cleared by ThrowCharsOverflow if Convert & didn't reach end of buffer)
+ if (decoder.MustFlush)
+ {
+ // RFC doesn't specify what would happen if we have non-0 leftover bits, we just drop them
+ decoder.bits = 0;
+ decoder.bitCount = -1;
+ decoder.firstByte = false;
+ }
+ else
+ {
+ decoder.bits = bits;
+ decoder.bitCount = bitCount;
+ decoder.firstByte = firstByte;
+ }
+ decoder._bytesUsed = buffer.BytesUsed;
+ }
+ // else ignore any hanging bits.
+
+ // Return our count
+ return buffer.Count;
+ }
+
+
+ public override System.Text.Decoder GetDecoder()
+ {
+ return new UTF7Encoding.Decoder(this);
+ }
+
+
+ public override System.Text.Encoder GetEncoder()
+ {
+ return new UTF7Encoding.Encoder(this);
+ }
+
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ if (charCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(charCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Suppose that every char can not be direct-encoded, we know that
+ // a byte can encode 6 bits of the Unicode character. And we will
+ // also need two extra bytes for the shift-in ('+') and shift-out ('-') mark.
+ // Therefore, the max byte should be:
+ // byteCount = 2 + Math.Ceiling((double)charCount * 16 / 6);
+ // That is always <= 2 + 3 * charCount;
+ // Longest case is alternating encoded, direct, encoded data for 5 + 1 + 5... bytes per char.
+ // UTF7 doesn't have left over surrogates, but if no input we may need an output - to turn off
+ // encoding if MustFlush is true.
+
+ // Its easiest to think of this as 2 bytes to turn on/off the base64 mode, then 3 bytes per char.
+ // 3 bytes is 18 bits of encoding, which is more than we need, but if its direct encoded then 3
+ // bytes allows us to turn off and then back on base64 mode if necessary.
+
+ // Note that UTF7 encoded surrogates individually and isn't worried about mismatches, so all
+ // code points are encodable int UTF7.
+ long byteCount = (long)charCount * 3 + 2;
+
+ // check for overflow
+ if (byteCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GetByteCountOverflow);
+
+ return (int)byteCount;
+ }
+
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(byteCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Worst case is 1 char per byte. Minimum 1 for left over bits in case decoder is being flushed
+ // Also note that we ignore extra bits (per spec), so UTF7 doesn't have unknown in this direction.
+ int charCount = byteCount;
+ if (charCount == 0) charCount = 1;
+
+ return charCount;
+ }
+
+ // Of all the amazing things... This MUST be Decoder so that our com name
+ // for System.Text.Decoder doesn't change
+ private sealed class Decoder : DecoderNLS
+ {
+ /*private*/
+ internal int bits;
+ /*private*/
+ internal int bitCount;
+ /*private*/
+ internal bool firstByte;
+
+ public Decoder(UTF7Encoding encoding) : base(encoding)
+ {
+ // base calls reset
+ }
+
+ public override void Reset()
+ {
+ this.bits = 0;
+ this.bitCount = -1;
+ this.firstByte = false;
+ if (_fallbackBuffer != null)
+ _fallbackBuffer.Reset();
+ }
+
+ // Anything left in our encoder?
+ internal override bool HasState
+ {
+ get
+ {
+ // NOTE: This forces the last -, which some encoder might not encode. If we
+ // don't see it we don't think we're done reading.
+ return (this.bitCount != -1);
+ }
+ }
+ }
+
+ // Of all the amazing things... This MUST be Encoder so that our com name
+ // for System.Text.Encoder doesn't change
+ private sealed class Encoder : EncoderNLS
+ {
+ /*private*/
+ internal int bits;
+ /*private*/
+ internal int bitCount;
+
+ public Encoder(UTF7Encoding encoding) : base(encoding)
+ {
+ // base calls reset
+ }
+
+ public override void Reset()
+ {
+ this.bitCount = -1;
+ this.bits = 0;
+ if (_fallbackBuffer != null)
+ _fallbackBuffer.Reset();
+ }
+
+ // Anything left in our encoder?
+ internal override bool HasState
+ {
+ get
+ {
+ return (this.bits != 0 || this.bitCount != -1);
+ }
+ }
+ }
+
+ // Preexisting UTF7 behavior for bad bytes was just to spit out the byte as the next char
+ // and turn off base64 mode if it was in that mode. We still exit the mode, but now we fallback.
+ private sealed class DecoderUTF7Fallback : DecoderFallback
+ {
+ // Construction. Default replacement fallback uses no best fit and ? replacement string
+ public DecoderUTF7Fallback()
+ {
+ }
+
+ public override DecoderFallbackBuffer CreateFallbackBuffer()
+ {
+ return new DecoderUTF7FallbackBuffer(this);
+ }
+
+ // Maximum number of characters that this instance of this fallback could return
+ public override int MaxCharCount
+ {
+ get
+ {
+ // returns 1 char per bad byte
+ return 1;
+ }
+ }
+
+ public override bool Equals(Object value)
+ {
+ DecoderUTF7Fallback that = value as DecoderUTF7Fallback;
+ if (that != null)
+ {
+ return true;
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return 984;
+ }
+ }
+
+ private sealed class DecoderUTF7FallbackBuffer : DecoderFallbackBuffer
+ {
+ // Store our default string
+ private char cFallback = (char)0;
+ private int iCount = -1;
+ private int iSize;
+
+ // Construction
+ public DecoderUTF7FallbackBuffer(DecoderUTF7Fallback fallback)
+ {
+ }
+
+ // Fallback Methods
+ public override bool Fallback(byte[] bytesUnknown, int index)
+ {
+ // We expect no previous fallback in our buffer
+ Debug.Assert(iCount < 0, "[DecoderUTF7FallbackBuffer.Fallback] Can't have recursive fallbacks");
+ Debug.Assert(bytesUnknown.Length == 1, "[DecoderUTF7FallbackBuffer.Fallback] Only possible fallback case should be 1 unknown byte");
+
+ // Go ahead and get our fallback
+ cFallback = (char)bytesUnknown[0];
+
+ // Any of the fallback characters can be handled except for 0
+ if (cFallback == 0)
+ {
+ return false;
+ }
+
+ iCount = iSize = 1;
+
+ return true;
+ }
+
+ public override char GetNextChar()
+ {
+ if (iCount-- > 0)
+ return cFallback;
+
+ // Note: this means that 0 in UTF7 stream will never be emitted.
+ return (char)0;
+ }
+
+ public override bool MovePrevious()
+ {
+ if (iCount >= 0)
+ {
+ iCount++;
+ }
+
+ // return true if we were allowed to do this
+ return (iCount >= 0 && iCount <= iSize);
+ }
+
+ // Return # of chars left in this fallback
+ public override int Remaining
+ {
+ get
+ {
+ return (iCount > 0) ? iCount : 0;
+ }
+ }
+
+ // Clear the buffer
+ public override unsafe void Reset()
+ {
+ iCount = -1;
+ byteStart = null;
+ }
+
+ // This version just counts the fallback and doesn't actually copy anything.
+ internal unsafe override int InternalFallback(byte[] bytes, byte* pBytes)
+ // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
+ // array, and we might need the index, hence the byte*
+ {
+ // We expect no previous fallback in our buffer
+ Debug.Assert(iCount < 0, "[DecoderUTF7FallbackBuffer.InternalFallback] Can't have recursive fallbacks");
+ if (bytes.Length != 1)
+ {
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
+ }
+
+ // Can't fallback a byte 0, so return for that case, 1 otherwise.
+ return bytes[0] == 0 ? 0 : 1;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/UTF8Encoding.cs b/src/System.Private.CoreLib/shared/System/Text/UTF8Encoding.cs
new file mode 100644
index 0000000000..2ea5096243
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/UTF8Encoding.cs
@@ -0,0 +1,2561 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// The worker functions in this file was optimized for performance. If you make changes
+// you should use care to consider all of the interesting cases.
+
+// The code of all worker functions in this file is written twice: Once as as a slow loop, and the
+// second time as a fast loop. The slow loops handles all special cases, throws exceptions, etc.
+// The fast loops attempts to blaze through as fast as possible with optimistic range checks,
+// processing multiple characters at a time, and falling back to the slow loop for all special cases.
+
+// This define can be used to turn off the fast loops. Useful for finding whether
+// the problem is fastloop-specific.
+#define FASTLOOP
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ // Encodes text into and out of UTF-8. UTF-8 is a way of writing
+ // Unicode characters with variable numbers of bytes per character,
+ // optimized for the lower 127 ASCII characters. It's an efficient way
+ // of encoding US English in an internationalizable way.
+ //
+ // Don't override IsAlwaysNormalized because it is just a Unicode Transformation and could be confused.
+ //
+ // The UTF-8 byte order mark is simply the Unicode byte order mark
+ // (0xFEFF) written in UTF-8 (0xEF 0xBB 0xBF). The byte order mark is
+ // used mostly to distinguish UTF-8 text from other encodings, and doesn't
+ // switch the byte orderings.
+
+ public class UTF8Encoding : Encoding
+ {
+ /*
+ bytes bits UTF-8 representation
+ ----- ---- -----------------------------------
+ 1 7 0vvvvvvv
+ 2 11 110vvvvv 10vvvvvv
+ 3 16 1110vvvv 10vvvvvv 10vvvvvv
+ 4 21 11110vvv 10vvvvvv 10vvvvvv 10vvvvvv
+ ----- ---- -----------------------------------
+
+ Surrogate:
+ Real Unicode value = (HighSurrogate - 0xD800) * 0x400 + (LowSurrogate - 0xDC00) + 0x10000
+ */
+
+ private const int UTF8_CODEPAGE = 65001;
+
+ // Allow for de-virtualization (see https://github.com/dotnet/coreclr/pull/9230)
+ internal sealed class UTF8EncodingSealed : UTF8Encoding
+ {
+ public UTF8EncodingSealed(bool encoderShouldEmitUTF8Identifier) : base(encoderShouldEmitUTF8Identifier) { }
+
+ public override ReadOnlySpan<byte> Preamble => _emitUTF8Identifier ? s_preamble : Array.Empty<byte>();
+ }
+
+ // Used by Encoding.UTF8 for lazy initialization
+ // The initialization code will not be run until a static member of the class is referenced
+ internal static readonly UTF8EncodingSealed s_default = new UTF8EncodingSealed(encoderShouldEmitUTF8Identifier: true);
+
+ internal static readonly byte[] s_preamble = new byte[3] { 0xEF, 0xBB, 0xBF };
+
+ // Yes, the idea of emitting U+FEFF as a UTF-8 identifier has made it into
+ // the standard.
+ internal readonly bool _emitUTF8Identifier = false;
+
+ private bool _isThrowException = false;
+
+
+ public UTF8Encoding() : this(false)
+ {
+ }
+
+
+ public UTF8Encoding(bool encoderShouldEmitUTF8Identifier) :
+ this(encoderShouldEmitUTF8Identifier, false)
+ {
+ }
+
+
+ public UTF8Encoding(bool encoderShouldEmitUTF8Identifier, bool throwOnInvalidBytes) :
+ base(UTF8_CODEPAGE)
+ {
+ _emitUTF8Identifier = encoderShouldEmitUTF8Identifier;
+ _isThrowException = throwOnInvalidBytes;
+
+ // Encoding's constructor already did this, but it'll be wrong if we're throwing exceptions
+ if (_isThrowException)
+ SetDefaultFallbacks();
+ }
+
+ internal override void SetDefaultFallbacks()
+ {
+ // For UTF-X encodings, we use a replacement fallback with an empty string
+ if (_isThrowException)
+ {
+ this.encoderFallback = EncoderFallback.ExceptionFallback;
+ this.decoderFallback = DecoderFallback.ExceptionFallback;
+ }
+ else
+ {
+ this.encoderFallback = new EncoderReplacementFallback("\xFFFD");
+ this.decoderFallback = new DecoderReplacementFallback("\xFFFD");
+ }
+ }
+
+
+ // WARNING: GetByteCount(string chars)
+ // WARNING: has different variable names than EncodingNLS.cs, so this can't just be cut & pasted,
+ // WARNING: otherwise it'll break VB's way of declaring these.
+ //
+ // The following methods are copied from EncodingNLS.cs.
+ // Unfortunately EncodingNLS.cs is internal and we're public, so we have to re-implement them here.
+ // These should be kept in sync for the following classes:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ // Returns the number of bytes required to encode a range of characters in
+ // a character array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(char[] chars, int index, int count)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - index < count)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input, return 0, avoid fixed empty array problem
+ if (count == 0)
+ return 0;
+
+ // Just call the pointer version
+ fixed (char* pChars = chars)
+ return GetByteCount(pChars + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(String chars)
+ {
+ // Validate input
+ if (chars==null)
+ throw new ArgumentNullException("s");
+
+ fixed (char* pChars = chars)
+ return GetByteCount(pChars, chars.Length, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetByteCount(char* chars, int count)
+ {
+ // Validate Parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Call it with empty encoder
+ return GetByteCount(chars, count, null);
+ }
+
+ // Parent method is safe.
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ public override unsafe int GetBytes(String s, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ if (s == null || bytes == null)
+ throw new ArgumentNullException((s == null ? "s" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (s.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("s", SR.ArgumentOutOfRange_IndexCount);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = s) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. An exception occurs if the byte array is not large
+ // enough to hold the complete encoding of the characters. The
+ // GetByteCount method can be used to determine the exact number of
+ // bytes that will be produced for a given range of characters.
+ // Alternatively, the GetMaxByteCount method can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? "chars" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ // If nothing to encode return 0, avoid fixed problem
+ if (charCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = chars) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ // Remember that byteCount is # to decode, not size of array.
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetBytes(chars, charCount, bytes, byteCount, null);
+ }
+
+ // Returns the number of characters produced by decoding a range of bytes
+ // in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetCharCount(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input just return 0, fixed doesn't like 0 length arrays.
+ if (count == 0)
+ return 0;
+
+ // Just call pointer version
+ fixed (byte* pBytes = bytes)
+ return GetCharCount(pBytes + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetCharCount(byte* bytes, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetCharCount(bytes, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? "byteIndex" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if ( bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (charIndex < 0 || charIndex > chars.Length)
+ throw new ArgumentOutOfRangeException("charIndex", SR.ArgumentOutOfRange_Index);
+
+ // If no input, return 0 & avoid fixed problem
+ if (byteCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int charCount = chars.Length - charIndex;
+
+ fixed (byte* pBytes = bytes) fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ // Remember that charCount is # to decode, not size of array
+ return GetChars(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public unsafe override int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetChars(bytes, byteCount, chars, charCount, null);
+ }
+
+ // Returns a string containing the decoded representation of a range of
+ // bytes in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe String GetString(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Avoid problems with empty input buffer
+ if (count == 0) return String.Empty;
+
+ fixed (byte* pBytes = bytes)
+ return String.CreateStringFromEncoding(
+ pBytes + index, count, this);
+ }
+
+ //
+ // End of standard methods copied from EncodingNLS.cs
+ //
+
+ // To simplify maintenance, the structure of GetByteCount and GetBytes should be
+ // kept the same as much as possible
+ internal override unsafe int GetByteCount(char* chars, int count, EncoderNLS baseEncoder)
+ {
+ // For fallback we may need a fallback buffer.
+ // We wait to initialize it though in case we don't have any broken input unicode
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* pSrcForFallback;
+
+ char* pSrc = chars;
+ char* pEnd = pSrc + count;
+
+ // Start by assuming we have as many as count
+ int byteCount = count;
+
+ int ch = 0;
+
+ if (baseEncoder != null)
+ {
+ UTF8Encoder encoder = (UTF8Encoder)baseEncoder;
+ ch = encoder.surrogateChar;
+
+ // We mustn't have left over fallback data when counting
+ if (encoder.InternalHasFallbackBuffer)
+ {
+ fallbackBuffer = encoder.FallbackBuffer;
+ if (fallbackBuffer.Remaining > 0)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType()));
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(chars, pEnd, encoder, false);
+ }
+ }
+
+ for (;;)
+ {
+ // SLOWLOOP: does all range checks, handles all special cases, but it is slow
+ if (pSrc >= pEnd)
+ {
+ if (ch == 0)
+ {
+ // Unroll any fallback that happens at the end
+ ch = fallbackBuffer != null ? fallbackBuffer.InternalGetNextChar() : 0;
+ if (ch > 0)
+ {
+ byteCount++;
+ goto ProcessChar;
+ }
+ }
+ else
+ {
+ // Case of surrogates in the fallback.
+ if (fallbackBuffer != null && fallbackBuffer.bFallingBack)
+ {
+ Debug.Assert(ch >= 0xD800 && ch <= 0xDBFF,
+ "[UTF8Encoding.GetBytes]expected high surrogate, not 0x" + ((int)ch).ToString("X4", CultureInfo.InvariantCulture));
+
+ ch = fallbackBuffer.InternalGetNextChar();
+ byteCount++;
+
+ if (InRange(ch, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ ch = 0xfffd;
+ byteCount++;
+ goto EncodeChar;
+ }
+ else if (ch > 0)
+ {
+ goto ProcessChar;
+ }
+ else
+ {
+ byteCount--; // ignore last one.
+ break;
+ }
+ }
+ }
+
+ if (ch <= 0)
+ {
+ break;
+ }
+ if (baseEncoder != null && !baseEncoder.MustFlush)
+ {
+ break;
+ }
+
+ // attempt to encode the partial surrogate (will fallback or ignore it), it'll also subtract 1.
+ byteCount++;
+ goto EncodeChar;
+ }
+
+ if (ch > 0)
+ {
+ Debug.Assert(ch >= 0xD800 && ch <= 0xDBFF,
+ "[UTF8Encoding.GetBytes]expected high surrogate, not 0x" + ((int)ch).ToString("X4", CultureInfo.InvariantCulture));
+
+ // use separate helper variables for local contexts so that the jit optimizations
+ // won't get confused about the variable lifetimes
+ int cha = *pSrc;
+
+ // count the pending surrogate
+ byteCount++;
+
+ // In previous byte, we encountered a high surrogate, so we are expecting a low surrogate here.
+ // if (IsLowSurrogate(cha)) {
+ if (InRange(cha, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ // Don't need a real # because we're just counting, anything > 0x7ff ('cept surrogate) will do.
+ ch = 0xfffd;
+ // ch = cha + (ch << 10) +
+ // (0x10000
+ // - CharUnicodeInfo.LOW_SURROGATE_START
+ // - (CharUnicodeInfo.HIGH_SURROGATE_START << 10) );
+
+ // Use this next char
+ pSrc++;
+ }
+ // else ch is still high surrogate and encoding will fail (so don't add count)
+
+ // attempt to encode the surrogate or partial surrogate
+ goto EncodeChar;
+ }
+
+ // If we've used a fallback, then we have to check for it
+ if (fallbackBuffer != null)
+ {
+ ch = fallbackBuffer.InternalGetNextChar();
+ if (ch > 0)
+ {
+ // We have an extra byte we weren't expecting.
+ byteCount++;
+ goto ProcessChar;
+ }
+ }
+
+ // read next char. The JIT optimization seems to be getting confused when
+ // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead
+ ch = *pSrc;
+ pSrc++;
+
+ ProcessChar:
+ // if (IsHighSurrogate(ch)) {
+ if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.HIGH_SURROGATE_END))
+ {
+ // we will count this surrogate next time around
+ byteCount--;
+ continue;
+ }
+ // either good char or partial surrogate
+
+ EncodeChar:
+ // throw exception on partial surrogate if necessary
+ // if (IsLowSurrogate(ch) || IsHighSurrogate(ch))
+ if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ // Lone surrogates aren't allowed
+ // Have to make a fallback buffer if we don't have one
+ if (fallbackBuffer == null)
+ {
+ // wait on fallbacks if we can
+ // For fallback we may need a fallback buffer
+ if (baseEncoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = baseEncoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(chars, chars + count, baseEncoder, false);
+ }
+
+ // Do our fallback. Actually we already know its a mixed up surrogate,
+ // so the ref pSrc isn't gonna do anything.
+ pSrcForFallback = pSrc; // Avoid passing pSrc by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(unchecked((char)ch), ref pSrcForFallback);
+ pSrc = pSrcForFallback;
+
+ // Ignore it if we don't throw (we had preallocated this ch)
+ byteCount--;
+ ch = 0;
+ continue;
+ }
+
+ // Count them
+ if (ch > 0x7F)
+ {
+ if (ch > 0x7FF)
+ {
+ // the extra surrogate byte was compensated by the second surrogate character
+ // (2 surrogates make 4 bytes. We've already counted 2 bytes, 1 per char)
+ byteCount++;
+ }
+ byteCount++;
+ }
+
+#if BIT64
+ // check for overflow
+ if (byteCount < 0)
+ {
+ break;
+ }
+#endif
+
+#if FASTLOOP
+ // If still have fallback don't do fast loop
+ if (fallbackBuffer != null && (ch = fallbackBuffer.InternalGetNextChar()) != 0)
+ {
+ // We're reserving 1 byte for each char by default
+ byteCount++;
+ goto ProcessChar;
+ }
+
+ int availableChars = PtrDiff(pEnd, pSrc);
+
+ // don't fall into the fast decoding loop if we don't have enough characters
+ if (availableChars <= 13)
+ {
+ // try to get over the remainder of the ascii characters fast though
+ char* pLocalEnd = pEnd; // hint to get pLocalEnd en-registered
+ while (pSrc < pLocalEnd)
+ {
+ ch = *pSrc;
+ pSrc++;
+ if (ch > 0x7F)
+ goto ProcessChar;
+ }
+
+ // we are done
+ break;
+ }
+
+#if BIT64
+ // make sure that we won't get a silent overflow inside the fast loop
+ // (Fall out to slow loop if we have this many characters)
+ availableChars &= 0x0FFFFFFF;
+#endif
+
+ // To compute the upper bound, assume that all characters are ASCII characters at this point,
+ // the boundary will be decreased for every non-ASCII character we encounter
+ // Also, we need 3 + 4 chars reserve for the unrolled ansi decoding loop and for decoding of surrogates
+ char* pStop = pSrc + availableChars - (3 + 4);
+
+ while (pSrc < pStop)
+ {
+ ch = *pSrc;
+ pSrc++;
+
+ if (ch > 0x7F) // Not ASCII
+ {
+ if (ch > 0x7FF) // Not 2 Byte
+ {
+ if ((ch & 0xF800) == 0xD800) // See if its a Surrogate
+ goto LongCode;
+ byteCount++;
+ }
+ byteCount++;
+ }
+
+ // get pSrc aligned
+ if ((unchecked((int)pSrc) & 0x2) != 0)
+ {
+ ch = *pSrc;
+ pSrc++;
+ if (ch > 0x7F) // Not ASCII
+ {
+ if (ch > 0x7FF) // Not 2 Byte
+ {
+ if ((ch & 0xF800) == 0xD800) // See if its a Surrogate
+ goto LongCode;
+ byteCount++;
+ }
+ byteCount++;
+ }
+ }
+
+ // Run 2 * 4 characters at a time!
+ while (pSrc < pStop)
+ {
+ ch = *(int*)pSrc;
+ int chc = *(int*)(pSrc + 2);
+ if (((ch | chc) & unchecked((int)0xFF80FF80)) != 0) // See if not ASCII
+ {
+ if (((ch | chc) & unchecked((int)0xF800F800)) != 0) // See if not 2 Byte
+ {
+ goto LongCodeWithMask;
+ }
+
+
+ if ((ch & unchecked((int)0xFF800000)) != 0) // Actually 0x07800780 is all we care about (4 bits)
+ byteCount++;
+ if ((ch & unchecked((int)0xFF80)) != 0)
+ byteCount++;
+ if ((chc & unchecked((int)0xFF800000)) != 0)
+ byteCount++;
+ if ((chc & unchecked((int)0xFF80)) != 0)
+ byteCount++;
+ }
+ pSrc += 4;
+
+ ch = *(int*)pSrc;
+ chc = *(int*)(pSrc + 2);
+ if (((ch | chc) & unchecked((int)0xFF80FF80)) != 0) // See if not ASCII
+ {
+ if (((ch | chc) & unchecked((int)0xF800F800)) != 0) // See if not 2 Byte
+ {
+ goto LongCodeWithMask;
+ }
+
+ if ((ch & unchecked((int)0xFF800000)) != 0)
+ byteCount++;
+ if ((ch & unchecked((int)0xFF80)) != 0)
+ byteCount++;
+ if ((chc & unchecked((int)0xFF800000)) != 0)
+ byteCount++;
+ if ((chc & unchecked((int)0xFF80)) != 0)
+ byteCount++;
+ }
+ pSrc += 4;
+ }
+ break;
+
+ LongCodeWithMask:
+#if BIGENDIAN
+ // be careful about the sign extension
+ ch = (int)(((uint)ch) >> 16);
+#else // BIGENDIAN
+ ch = (char)ch;
+#endif // BIGENDIAN
+ pSrc++;
+
+ if (ch <= 0x7F)
+ {
+ continue;
+ }
+
+ LongCode:
+ // use separate helper variables for slow and fast loop so that the jit optimizations
+ // won't get confused about the variable lifetimes
+ if (ch > 0x7FF)
+ {
+ // if (IsLowSurrogate(ch) || IsHighSurrogate(ch))
+ if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ // 4 byte encoding - high surrogate + low surrogate
+
+ int chd = *pSrc;
+ if (
+ // !IsHighSurrogate(ch) // low without high -> bad
+ ch > CharUnicodeInfo.HIGH_SURROGATE_END ||
+ // !IsLowSurrogate(chd) // high not followed by low -> bad
+ !InRange(chd, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ // Back up and drop out to slow loop to figure out error
+ pSrc--;
+ break;
+ }
+ pSrc++;
+
+ // byteCount - this byte is compensated by the second surrogate character
+ }
+ byteCount++;
+ }
+ byteCount++;
+
+ // byteCount - the last byte is already included
+ }
+#endif // FASTLOOP
+
+ // no pending char at this point
+ ch = 0;
+ }
+
+#if BIT64
+ // check for overflow
+ if (byteCount < 0)
+ {
+ throw new ArgumentException(
+ SR.Argument_ConversionOverflow);
+ }
+#endif
+
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[UTF8Encoding.GetByteCount]Expected Empty fallback buffer");
+
+ return byteCount;
+ }
+
+ // diffs two char pointers using unsigned arithmetic. The unsigned arithmetic
+ // is good enough for us, and it tends to generate better code than the signed
+ // arithmetic generated by default
+ private static unsafe int PtrDiff(char* a, char* b)
+ {
+ return (int)(((uint)((byte*)a - (byte*)b)) >> 1);
+ }
+
+ // byte* flavor just for parity
+ private static unsafe int PtrDiff(byte* a, byte* b)
+ {
+ return (int)(a - b);
+ }
+
+ private static bool InRange(int ch, int start, int end)
+ {
+ return (uint)(ch - start) <= (uint)(end - start);
+ }
+
+ // Our workhorse
+ // Note: We ignore mismatched surrogates, unless the exception flag is set in which case we throw
+ internal override unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, EncoderNLS baseEncoder)
+ {
+ Debug.Assert(chars != null, "[UTF8Encoding.GetBytes]chars!=null");
+ Debug.Assert(byteCount >= 0, "[UTF8Encoding.GetBytes]byteCount >=0");
+ Debug.Assert(charCount >= 0, "[UTF8Encoding.GetBytes]charCount >=0");
+ Debug.Assert(bytes != null, "[UTF8Encoding.GetBytes]bytes!=null");
+
+ UTF8Encoder encoder = null;
+
+ // For fallback we may need a fallback buffer.
+ // We wait to initialize it though in case we don't have any broken input unicode
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* pSrcForFallback;
+
+ char* pSrc = chars;
+ byte* pTarget = bytes;
+
+ char* pEnd = pSrc + charCount;
+ byte* pAllocatedBufferEnd = pTarget + byteCount;
+
+ int ch = 0;
+
+ // assume that JIT will en-register pSrc, pTarget and ch
+
+ if (baseEncoder != null)
+ {
+ encoder = (UTF8Encoder)baseEncoder;
+ ch = encoder.surrogateChar;
+
+ // We mustn't have left over fallback data when counting
+ if (encoder.InternalHasFallbackBuffer)
+ {
+ // We always need the fallback buffer in get bytes so we can flush any remaining ones if necessary
+ fallbackBuffer = encoder.FallbackBuffer;
+ if (fallbackBuffer.Remaining > 0 && encoder._throwOnOverflow)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType()));
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(chars, pEnd, encoder, true);
+ }
+ }
+
+ for (;;)
+ {
+ // SLOWLOOP: does all range checks, handles all special cases, but it is slow
+
+ if (pSrc >= pEnd)
+ {
+ if (ch == 0)
+ {
+ // Check if there's anything left to get out of the fallback buffer
+ ch = fallbackBuffer != null ? fallbackBuffer.InternalGetNextChar() : 0;
+ if (ch > 0)
+ {
+ goto ProcessChar;
+ }
+ }
+ else
+ {
+ // Case of leftover surrogates in the fallback buffer
+ if (fallbackBuffer != null && fallbackBuffer.bFallingBack)
+ {
+ Debug.Assert(ch >= 0xD800 && ch <= 0xDBFF,
+ "[UTF8Encoding.GetBytes]expected high surrogate, not 0x" + ((int)ch).ToString("X4", CultureInfo.InvariantCulture));
+
+ int cha = ch;
+
+ ch = fallbackBuffer.InternalGetNextChar();
+
+ if (InRange(ch, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ ch = ch + (cha << 10) + (0x10000 - CharUnicodeInfo.LOW_SURROGATE_START - (CharUnicodeInfo.HIGH_SURROGATE_START << 10));
+ goto EncodeChar;
+ }
+ else if (ch > 0)
+ {
+ goto ProcessChar;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ // attempt to encode the partial surrogate (will fail or ignore)
+ if (ch > 0 && (encoder == null || encoder.MustFlush))
+ goto EncodeChar;
+
+ // We're done
+ break;
+ }
+
+ if (ch > 0)
+ {
+ // We have a high surrogate left over from a previous loop.
+ Debug.Assert(ch >= 0xD800 && ch <= 0xDBFF,
+ "[UTF8Encoding.GetBytes]expected high surrogate, not 0x" + ((int)ch).ToString("X4", CultureInfo.InvariantCulture));
+
+ // use separate helper variables for local contexts so that the jit optimizations
+ // won't get confused about the variable lifetimes
+ int cha = *pSrc;
+
+ // In previous byte, we encountered a high surrogate, so we are expecting a low surrogate here.
+ // if (IsLowSurrogate(cha)) {
+ if (InRange(cha, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ ch = cha + (ch << 10) +
+ (0x10000
+ - CharUnicodeInfo.LOW_SURROGATE_START
+ - (CharUnicodeInfo.HIGH_SURROGATE_START << 10));
+
+ pSrc++;
+ }
+ // else ch is still high surrogate and encoding will fail
+
+ // attempt to encode the surrogate or partial surrogate
+ goto EncodeChar;
+ }
+
+ // If we've used a fallback, then we have to check for it
+ if (fallbackBuffer != null)
+ {
+ ch = fallbackBuffer.InternalGetNextChar();
+ if (ch > 0) goto ProcessChar;
+ }
+
+ // read next char. The JIT optimization seems to be getting confused when
+ // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead
+ ch = *pSrc;
+ pSrc++;
+
+ ProcessChar:
+ // if (IsHighSurrogate(ch)) {
+ if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.HIGH_SURROGATE_END))
+ {
+ continue;
+ }
+ // either good char or partial surrogate
+
+ EncodeChar:
+ // throw exception on partial surrogate if necessary
+ // if (IsLowSurrogate(ch) || IsHighSurrogate(ch))
+ if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ // Lone surrogates aren't allowed, we have to do fallback for them
+ // Have to make a fallback buffer if we don't have one
+ if (fallbackBuffer == null)
+ {
+ // wait on fallbacks if we can
+ // For fallback we may need a fallback buffer
+ if (baseEncoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = baseEncoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(chars, pEnd, baseEncoder, true);
+ }
+
+ // Do our fallback. Actually we already know its a mixed up surrogate,
+ // so the ref pSrc isn't gonna do anything.
+ pSrcForFallback = pSrc; // Avoid passing pSrc by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(unchecked((char)ch), ref pSrcForFallback);
+ pSrc = pSrcForFallback;
+
+ // Ignore it if we don't throw
+ ch = 0;
+ continue;
+ }
+
+ // Count bytes needed
+ int bytesNeeded = 1;
+ if (ch > 0x7F)
+ {
+ if (ch > 0x7FF)
+ {
+ if (ch > 0xFFFF)
+ {
+ bytesNeeded++; // 4 bytes (surrogate pair)
+ }
+ bytesNeeded++; // 3 bytes (800-FFFF)
+ }
+ bytesNeeded++; // 2 bytes (80-7FF)
+ }
+
+ if (pTarget > pAllocatedBufferEnd - bytesNeeded)
+ {
+ // Left over surrogate from last time will cause pSrc == chars, so we'll throw
+ if (fallbackBuffer != null && fallbackBuffer.bFallingBack)
+ {
+ fallbackBuffer.MovePrevious(); // Didn't use this fallback char
+ if (ch > 0xFFFF)
+ fallbackBuffer.MovePrevious(); // Was surrogate, didn't use 2nd part either
+ }
+ else
+ {
+ pSrc--; // Didn't use this char
+ if (ch > 0xFFFF)
+ pSrc--; // Was surrogate, didn't use 2nd part either
+ }
+ Debug.Assert(pSrc >= chars || pTarget == bytes,
+ "[UTF8Encoding.GetBytes]Expected pSrc to be within buffer or to throw with insufficient room.");
+ ThrowBytesOverflow(encoder, pTarget == bytes); // Throw if we must
+ ch = 0; // Nothing left over (we backed up to start of pair if supplementary)
+ break;
+ }
+
+ if (ch <= 0x7F)
+ {
+ *pTarget = (byte)ch;
+ }
+ else
+ {
+ // use separate helper variables for local contexts so that the jit optimizations
+ // won't get confused about the variable lifetimes
+ int chb;
+ if (ch <= 0x7FF)
+ {
+ // 2 byte encoding
+ chb = (byte)(unchecked((sbyte)0xC0) | (ch >> 6));
+ }
+ else
+ {
+ if (ch <= 0xFFFF)
+ {
+ chb = (byte)(unchecked((sbyte)0xE0) | (ch >> 12));
+ }
+ else
+ {
+ *pTarget = (byte)(unchecked((sbyte)0xF0) | (ch >> 18));
+ pTarget++;
+
+ chb = unchecked((sbyte)0x80) | (ch >> 12) & 0x3F;
+ }
+ *pTarget = (byte)chb;
+ pTarget++;
+
+ chb = unchecked((sbyte)0x80) | (ch >> 6) & 0x3F;
+ }
+ *pTarget = (byte)chb;
+ pTarget++;
+
+ *pTarget = (byte)(unchecked((sbyte)0x80) | ch & 0x3F);
+ }
+ pTarget++;
+
+
+#if FASTLOOP
+ // If still have fallback don't do fast loop
+ if (fallbackBuffer != null && (ch = fallbackBuffer.InternalGetNextChar()) != 0)
+ goto ProcessChar;
+
+ int availableChars = PtrDiff(pEnd, pSrc);
+ int availableBytes = PtrDiff(pAllocatedBufferEnd, pTarget);
+
+ // don't fall into the fast decoding loop if we don't have enough characters
+ // Note that if we don't have enough bytes, pStop will prevent us from entering the fast loop.
+ if (availableChars <= 13)
+ {
+ // we are hoping for 1 byte per char
+ if (availableBytes < availableChars)
+ {
+ // not enough output room. no pending bits at this point
+ ch = 0;
+ continue;
+ }
+
+ // try to get over the remainder of the ascii characters fast though
+ char* pLocalEnd = pEnd; // hint to get pLocalEnd en-registered
+ while (pSrc < pLocalEnd)
+ {
+ ch = *pSrc;
+ pSrc++;
+
+ // Not ASCII, need more than 1 byte per char
+ if (ch > 0x7F)
+ goto ProcessChar;
+
+ *pTarget = (byte)ch;
+ pTarget++;
+ }
+ // we are done, let ch be 0 to clear encoder
+ ch = 0;
+ break;
+ }
+
+ // we need at least 1 byte per character, but Convert might allow us to convert
+ // only part of the input, so try as much as we can. Reduce charCount if necessary
+ if (availableBytes < availableChars)
+ {
+ availableChars = availableBytes;
+ }
+
+ // FASTLOOP:
+ // - optimistic range checks
+ // - fallbacks to the slow loop for all special cases, exception throwing, etc.
+
+ // To compute the upper bound, assume that all characters are ASCII characters at this point,
+ // the boundary will be decreased for every non-ASCII character we encounter
+ // Also, we need 5 chars reserve for the unrolled ansi decoding loop and for decoding of surrogates
+ // If there aren't enough bytes for the output, then pStop will be <= pSrc and will bypass the loop.
+ char* pStop = pSrc + availableChars - 5;
+
+ while (pSrc < pStop)
+ {
+ ch = *pSrc;
+ pSrc++;
+
+ if (ch > 0x7F)
+ {
+ goto LongCode;
+ }
+ *pTarget = (byte)ch;
+ pTarget++;
+
+ // get pSrc aligned
+ if ((unchecked((int)pSrc) & 0x2) != 0)
+ {
+ ch = *pSrc;
+ pSrc++;
+ if (ch > 0x7F)
+ {
+ goto LongCode;
+ }
+ *pTarget = (byte)ch;
+ pTarget++;
+ }
+
+ // Run 4 characters at a time!
+ while (pSrc < pStop)
+ {
+ ch = *(int*)pSrc;
+ int chc = *(int*)(pSrc + 2);
+ if (((ch | chc) & unchecked((int)0xFF80FF80)) != 0)
+ {
+ goto LongCodeWithMask;
+ }
+
+ // Unfortunately, this is endianess sensitive
+#if BIGENDIAN
+ *pTarget = (byte)(ch>>16);
+ *(pTarget+1) = (byte)ch;
+ pSrc += 4;
+ *(pTarget+2) = (byte)(chc>>16);
+ *(pTarget+3) = (byte)chc;
+ pTarget += 4;
+#else // BIGENDIAN
+ *pTarget = (byte)ch;
+ *(pTarget + 1) = (byte)(ch >> 16);
+ pSrc += 4;
+ *(pTarget + 2) = (byte)chc;
+ *(pTarget + 3) = (byte)(chc >> 16);
+ pTarget += 4;
+#endif // BIGENDIAN
+ }
+ continue;
+
+ LongCodeWithMask:
+#if BIGENDIAN
+ // be careful about the sign extension
+ ch = (int)(((uint)ch) >> 16);
+#else // BIGENDIAN
+ ch = (char)ch;
+#endif // BIGENDIAN
+ pSrc++;
+
+ if (ch > 0x7F)
+ {
+ goto LongCode;
+ }
+ *pTarget = (byte)ch;
+ pTarget++;
+ continue;
+
+ LongCode:
+ // use separate helper variables for slow and fast loop so that the jit optimizations
+ // won't get confused about the variable lifetimes
+ int chd;
+ if (ch <= 0x7FF)
+ {
+ // 2 byte encoding
+ chd = unchecked((sbyte)0xC0) | (ch >> 6);
+ }
+ else
+ {
+ // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch))
+ if (!InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ // 3 byte encoding
+ chd = unchecked((sbyte)0xE0) | (ch >> 12);
+ }
+ else
+ {
+ // 4 byte encoding - high surrogate + low surrogate
+ // if (!IsHighSurrogate(ch))
+ if (ch > CharUnicodeInfo.HIGH_SURROGATE_END)
+ {
+ // low without high -> bad, try again in slow loop
+ pSrc -= 1;
+ break;
+ }
+
+ chd = *pSrc;
+ pSrc++;
+
+ // if (!IsLowSurrogate(chd)) {
+ if (!InRange(chd, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END))
+ {
+ // high not followed by low -> bad, try again in slow loop
+ pSrc -= 2;
+ break;
+ }
+
+ ch = chd + (ch << 10) +
+ (0x10000
+ - CharUnicodeInfo.LOW_SURROGATE_START
+ - (CharUnicodeInfo.HIGH_SURROGATE_START << 10));
+
+ *pTarget = (byte)(unchecked((sbyte)0xF0) | (ch >> 18));
+ // pStop - this byte is compensated by the second surrogate character
+ // 2 input chars require 4 output bytes. 2 have been anticipated already
+ // and 2 more will be accounted for by the 2 pStop-- calls below.
+ pTarget++;
+
+ chd = unchecked((sbyte)0x80) | (ch >> 12) & 0x3F;
+ }
+ *pTarget = (byte)chd;
+ pStop--; // 3 byte sequence for 1 char, so need pStop-- and the one below too.
+ pTarget++;
+
+ chd = unchecked((sbyte)0x80) | (ch >> 6) & 0x3F;
+ }
+ *pTarget = (byte)chd;
+ pStop--; // 2 byte sequence for 1 char so need pStop--.
+ pTarget++;
+
+ *pTarget = (byte)(unchecked((sbyte)0x80) | ch & 0x3F);
+ // pStop - this byte is already included
+ pTarget++;
+ }
+
+ Debug.Assert(pTarget <= pAllocatedBufferEnd, "[UTF8Encoding.GetBytes]pTarget <= pAllocatedBufferEnd");
+
+#endif // FASTLOOP
+
+ // no pending char at this point
+ ch = 0;
+ }
+
+ // Do we have to set the encoder bytes?
+ if (encoder != null)
+ {
+ Debug.Assert(!encoder.MustFlush || ch == 0,
+ "[UTF8Encoding.GetBytes] Expected no mustflush or 0 leftover ch " + ch.ToString("X2", CultureInfo.InvariantCulture));
+
+ encoder.surrogateChar = ch;
+ encoder._charsUsed = (int)(pSrc - chars);
+ }
+
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0 ||
+ baseEncoder == null || !baseEncoder._throwOnOverflow,
+ "[UTF8Encoding.GetBytes]Expected empty fallback buffer if not converting");
+
+ return (int)(pTarget - bytes);
+ }
+
+
+ // These are bitmasks used to maintain the state in the decoder. They occupy the higher bits
+ // while the actual character is being built in the lower bits. They are shifted together
+ // with the actual bits of the character.
+
+ // bits 30 & 31 are used for pending bits fixup
+ private const int FinalByte = 1 << 29;
+ private const int SupplimentarySeq = 1 << 28;
+ private const int ThreeByteSeq = 1 << 27;
+
+ // Note: We throw exceptions on individually encoded surrogates and other non-shortest forms.
+ // If exceptions aren't turned on, then we drop all non-shortest &individual surrogates.
+ //
+ // To simplify maintenance, the structure of GetCharCount and GetChars should be
+ // kept the same as much as possible
+ internal override unsafe int GetCharCount(byte* bytes, int count, DecoderNLS baseDecoder)
+ {
+ Debug.Assert(count >= 0, "[UTF8Encoding.GetCharCount]count >=0");
+ Debug.Assert(bytes != null, "[UTF8Encoding.GetCharCount]bytes!=null");
+
+ // Initialize stuff
+ byte* pSrc = bytes;
+ byte* pEnd = pSrc + count;
+
+ // Start by assuming we have as many as count, charCount always includes the adjustment
+ // for the character being decoded
+ int charCount = count;
+ int ch = 0;
+ DecoderFallbackBuffer fallback = null;
+
+ if (baseDecoder != null)
+ {
+ UTF8Decoder decoder = (UTF8Decoder)baseDecoder;
+ ch = decoder.bits;
+ charCount -= (ch >> 30); // Adjust char count for # of expected bytes and expected output chars.
+
+ // Shouldn't have anything in fallback buffer for GetCharCount
+ // (don't have to check _throwOnOverflow for count)
+ Debug.Assert(!decoder.InternalHasFallbackBuffer || decoder.FallbackBuffer.Remaining == 0,
+ "[UTF8Encoding.GetCharCount]Expected empty fallback buffer at start");
+ }
+
+ for (;;)
+ {
+ // SLOWLOOP: does all range checks, handles all special cases, but it is slow
+
+ if (pSrc >= pEnd)
+ {
+ break;
+ }
+
+ if (ch == 0)
+ {
+ // no pending bits
+ goto ReadChar;
+ }
+
+ // read next byte. The JIT optimization seems to be getting confused when
+ // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead
+ int cha = *pSrc;
+ pSrc++;
+
+ // we are expecting to see trailing bytes like 10vvvvvv
+ if ((cha & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ // This can be a valid starting byte for another UTF8 byte sequence, so let's put
+ // the current byte back, and try to see if this is a valid byte for another UTF8 byte sequence
+ pSrc--;
+ charCount += (ch >> 30);
+ goto InvalidByteSequence;
+ }
+
+ // fold in the new byte
+ ch = (ch << 6) | (cha & 0x3F);
+
+ if ((ch & FinalByte) == 0)
+ {
+ Debug.Assert((ch & (SupplimentarySeq | ThreeByteSeq)) != 0,
+ "[UTF8Encoding.GetChars]Invariant volation");
+
+ if ((ch & SupplimentarySeq) != 0)
+ {
+ if ((ch & (FinalByte >> 6)) != 0)
+ {
+ // this is 3rd byte (of 4 byte supplementary) - nothing to do
+ continue;
+ }
+
+ // 2nd byte, check for non-shortest form of supplementary char and the valid
+ // supplementary characters in range 0x010000 - 0x10FFFF at the same time
+ if (!InRange(ch & 0x1F0, 0x10, 0x100))
+ {
+ goto InvalidByteSequence;
+ }
+ }
+ else
+ {
+ // Must be 2nd byte of a 3-byte sequence
+ // check for non-shortest form of 3 byte seq
+ if ((ch & (0x1F << 5)) == 0 || // non-shortest form
+ (ch & (0xF800 >> 6)) == (0xD800 >> 6)) // illegal individually encoded surrogate
+ {
+ goto InvalidByteSequence;
+ }
+ }
+ continue;
+ }
+
+ // ready to punch
+
+ // adjust for surrogates in non-shortest form
+ if ((ch & (SupplimentarySeq | 0x1F0000)) == SupplimentarySeq)
+ {
+ charCount--;
+ }
+ goto EncodeChar;
+
+ InvalidByteSequence:
+ // this code fragment should be close to the goto referencing it
+ // Have to do fallback for invalid bytes
+ if (fallback == null)
+ {
+ if (baseDecoder == null)
+ fallback = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallback = baseDecoder.FallbackBuffer;
+ fallback.InternalInitialize(bytes, null);
+ }
+ charCount += FallbackInvalidByteSequence(pSrc, ch, fallback);
+
+ ch = 0;
+ continue;
+
+ ReadChar:
+ ch = *pSrc;
+ pSrc++;
+
+ ProcessChar:
+ if (ch > 0x7F)
+ {
+ // If its > 0x7F, its start of a new multi-byte sequence
+
+ // Long sequence, so unreserve our char.
+ charCount--;
+
+ // bit 6 has to be non-zero for start of multibyte chars.
+ if ((ch & 0x40) == 0)
+ {
+ // Unexpected trail byte
+ goto InvalidByteSequence;
+ }
+
+ // start a new long code
+ if ((ch & 0x20) != 0)
+ {
+ if ((ch & 0x10) != 0)
+ {
+ // 4 byte encoding - supplimentary character (2 surrogates)
+
+ ch &= 0x0F;
+
+ // check that bit 4 is zero and the valid supplimentary character
+ // range 0x000000 - 0x10FFFF at the same time
+ if (ch > 0x04)
+ {
+ ch |= 0xf0;
+ goto InvalidByteSequence;
+ }
+
+ // Add bit flags so that when we check new characters & rotate we'll be flagged correctly.
+ // Final byte flag, count fix if we don't make final byte & supplimentary sequence flag.
+ ch |= (FinalByte >> 3 * 6) | // Final byte is 3 more bytes from now
+ (1 << 30) | // If it dies on next byte we'll need an extra char
+ (3 << (30 - 2 * 6)) | // If it dies on last byte we'll need to subtract a char
+ (SupplimentarySeq) | (SupplimentarySeq >> 6) |
+ (SupplimentarySeq >> 2 * 6) | (SupplimentarySeq >> 3 * 6);
+
+ // Our character count will be 2 characters for these 4 bytes, so subtract another char
+ charCount--;
+ }
+ else
+ {
+ // 3 byte encoding
+ // Add bit flags so that when we check new characters & rotate we'll be flagged correctly.
+ ch = (ch & 0x0F) | ((FinalByte >> 2 * 6) | (1 << 30) |
+ (ThreeByteSeq) | (ThreeByteSeq >> 6) | (ThreeByteSeq >> 2 * 6));
+
+ // We'll expect 1 character for these 3 bytes, so subtract another char.
+ charCount--;
+ }
+ }
+ else
+ {
+ // 2 byte encoding
+
+ ch &= 0x1F;
+
+ // check for non-shortest form
+ if (ch <= 1)
+ {
+ ch |= 0xc0;
+ goto InvalidByteSequence;
+ }
+
+ // Add bit flags so we'll be flagged correctly
+ ch |= (FinalByte >> 6);
+ }
+ continue;
+ }
+
+ EncodeChar:
+
+#if FASTLOOP
+ int availableBytes = PtrDiff(pEnd, pSrc);
+
+ // don't fall into the fast decoding loop if we don't have enough bytes
+ if (availableBytes <= 13)
+ {
+ // try to get over the remainder of the ascii characters fast though
+ byte* pLocalEnd = pEnd; // hint to get pLocalEnd en-registered
+ while (pSrc < pLocalEnd)
+ {
+ ch = *pSrc;
+ pSrc++;
+
+ if (ch > 0x7F)
+ goto ProcessChar;
+ }
+ // we are done
+ ch = 0;
+ break;
+ }
+
+ // To compute the upper bound, assume that all characters are ASCII characters at this point,
+ // the boundary will be decreased for every non-ASCII character we encounter
+ // Also, we need 7 chars reserve for the unrolled ansi decoding loop and for decoding of multibyte sequences
+ byte* pStop = pSrc + availableBytes - 7;
+
+ while (pSrc < pStop)
+ {
+ ch = *pSrc;
+ pSrc++;
+
+ if (ch > 0x7F)
+ {
+ goto LongCode;
+ }
+
+ // get pSrc 2-byte aligned
+ if ((unchecked((int)pSrc) & 0x1) != 0)
+ {
+ ch = *pSrc;
+ pSrc++;
+ if (ch > 0x7F)
+ {
+ goto LongCode;
+ }
+ }
+
+ // get pSrc 4-byte aligned
+ if ((unchecked((int)pSrc) & 0x2) != 0)
+ {
+ ch = *(ushort*)pSrc;
+ if ((ch & 0x8080) != 0)
+ {
+ goto LongCodeWithMask16;
+ }
+ pSrc += 2;
+ }
+
+ // Run 8 + 8 characters at a time!
+ while (pSrc < pStop)
+ {
+ ch = *(int*)pSrc;
+ int chb = *(int*)(pSrc + 4);
+ if (((ch | chb) & unchecked((int)0x80808080)) != 0)
+ {
+ goto LongCodeWithMask32;
+ }
+ pSrc += 8;
+
+ // This is a really small loop - unroll it
+ if (pSrc >= pStop)
+ break;
+
+ ch = *(int*)pSrc;
+ chb = *(int*)(pSrc + 4);
+ if (((ch | chb) & unchecked((int)0x80808080)) != 0)
+ {
+ goto LongCodeWithMask32;
+ }
+ pSrc += 8;
+ }
+ break;
+
+#if BIGENDIAN
+ LongCodeWithMask32:
+ // be careful about the sign extension
+ ch = (int)(((uint)ch) >> 16);
+ LongCodeWithMask16:
+ ch = (int)(((uint)ch) >> 8);
+#else // BIGENDIAN
+ LongCodeWithMask32:
+ LongCodeWithMask16:
+ ch &= 0xFF;
+#endif // BIGENDIAN
+ pSrc++;
+ if (ch <= 0x7F)
+ {
+ continue;
+ }
+
+ LongCode:
+ int chc = *pSrc;
+ pSrc++;
+
+ if (
+ // bit 6 has to be zero
+ (ch & 0x40) == 0 ||
+ // we are expecting to see trailing bytes like 10vvvvvv
+ (chc & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ goto BadLongCode;
+ }
+
+ chc &= 0x3F;
+
+ // start a new long code
+ if ((ch & 0x20) != 0)
+ {
+ // fold the first two bytes together
+ chc |= (ch & 0x0F) << 6;
+
+ if ((ch & 0x10) != 0)
+ {
+ // 4 byte encoding - surrogate
+ ch = *pSrc;
+ if (
+ // check that bit 4 is zero, the non-shortest form of surrogate
+ // and the valid surrogate range 0x000000 - 0x10FFFF at the same time
+ !InRange(chc >> 4, 0x01, 0x10) ||
+ // we are expecting to see trailing bytes like 10vvvvvv
+ (ch & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ goto BadLongCode;
+ }
+
+ chc = (chc << 6) | (ch & 0x3F);
+
+ ch = *(pSrc + 1);
+ // we are expecting to see trailing bytes like 10vvvvvv
+ if ((ch & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ goto BadLongCode;
+ }
+ pSrc += 2;
+
+ // extra byte
+ charCount--;
+ }
+ else
+ {
+ // 3 byte encoding
+ ch = *pSrc;
+ if (
+ // check for non-shortest form of 3 byte seq
+ (chc & (0x1F << 5)) == 0 ||
+ // Can't have surrogates here.
+ (chc & (0xF800 >> 6)) == (0xD800 >> 6) ||
+ // we are expecting to see trailing bytes like 10vvvvvv
+ (ch & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ goto BadLongCode;
+ }
+ pSrc++;
+
+ // extra byte
+ charCount--;
+ }
+ }
+ else
+ {
+ // 2 byte encoding
+
+ // check for non-shortest form
+ if ((ch & 0x1E) == 0)
+ {
+ goto BadLongCode;
+ }
+ }
+
+ // extra byte
+ charCount--;
+ }
+#endif // FASTLOOP
+
+ // no pending bits at this point
+ ch = 0;
+ continue;
+
+ BadLongCode:
+ pSrc -= 2;
+ ch = 0;
+ continue;
+ }
+
+ // May have a problem if we have to flush
+ if (ch != 0)
+ {
+ // We were already adjusting for these, so need to un-adjust
+ charCount += (ch >> 30);
+ if (baseDecoder == null || baseDecoder.MustFlush)
+ {
+ // Have to do fallback for invalid bytes
+ if (fallback == null)
+ {
+ if (baseDecoder == null)
+ fallback = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallback = baseDecoder.FallbackBuffer;
+ fallback.InternalInitialize(bytes, null);
+ }
+ charCount += FallbackInvalidByteSequence(pSrc, ch, fallback);
+ }
+ }
+
+ // Shouldn't have anything in fallback buffer for GetCharCount
+ // (don't have to check _throwOnOverflow for count)
+ Debug.Assert(fallback == null || fallback.Remaining == 0,
+ "[UTF8Encoding.GetCharCount]Expected empty fallback buffer at end");
+
+ return charCount;
+ }
+
+ // WARNING: If we throw an error, then System.Resources.ResourceReader calls this method.
+ // So if we're really broken, then that could also throw an error... recursively.
+ // So try to make sure GetChars can at least process all uses by
+ // System.Resources.ResourceReader!
+ //
+ // Note: We throw exceptions on individually encoded surrogates and other non-shortest forms.
+ // If exceptions aren't turned on, then we drop all non-shortest &individual surrogates.
+ //
+ // To simplify maintenance, the structure of GetCharCount and GetChars should be
+ // kept the same as much as possible
+ internal override unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, DecoderNLS baseDecoder)
+ {
+ Debug.Assert(chars != null, "[UTF8Encoding.GetChars]chars!=null");
+ Debug.Assert(byteCount >= 0, "[UTF8Encoding.GetChars]count >=0");
+ Debug.Assert(charCount >= 0, "[UTF8Encoding.GetChars]charCount >=0");
+ Debug.Assert(bytes != null, "[UTF8Encoding.GetChars]bytes!=null");
+
+ byte* pSrc = bytes;
+ char* pTarget = chars;
+
+ byte* pEnd = pSrc + byteCount;
+ char* pAllocatedBufferEnd = pTarget + charCount;
+
+ int ch = 0;
+
+ DecoderFallbackBuffer fallback = null;
+ byte* pSrcForFallback;
+ char* pTargetForFallback;
+ if (baseDecoder != null)
+ {
+ UTF8Decoder decoder = (UTF8Decoder)baseDecoder;
+ ch = decoder.bits;
+
+ // Shouldn't have anything in fallback buffer for GetChars
+ // (don't have to check _throwOnOverflow for chars, we always use all or none so always should be empty)
+ Debug.Assert(!decoder.InternalHasFallbackBuffer || decoder.FallbackBuffer.Remaining == 0,
+ "[UTF8Encoding.GetChars]Expected empty fallback buffer at start");
+ }
+
+ for (;;)
+ {
+ // SLOWLOOP: does all range checks, handles all special cases, but it is slow
+
+ if (pSrc >= pEnd)
+ {
+ break;
+ }
+
+ if (ch == 0)
+ {
+ // no pending bits
+ goto ReadChar;
+ }
+
+ // read next byte. The JIT optimization seems to be getting confused when
+ // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead
+ int cha = *pSrc;
+ pSrc++;
+
+ // we are expecting to see trailing bytes like 10vvvvvv
+ if ((cha & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ // This can be a valid starting byte for another UTF8 byte sequence, so let's put
+ // the current byte back, and try to see if this is a valid byte for another UTF8 byte sequence
+ pSrc--;
+ goto InvalidByteSequence;
+ }
+
+ // fold in the new byte
+ ch = (ch << 6) | (cha & 0x3F);
+
+ if ((ch & FinalByte) == 0)
+ {
+ // Not at last byte yet
+ Debug.Assert((ch & (SupplimentarySeq | ThreeByteSeq)) != 0,
+ "[UTF8Encoding.GetChars]Invariant volation");
+
+ if ((ch & SupplimentarySeq) != 0)
+ {
+ // Its a 4-byte supplimentary sequence
+ if ((ch & (FinalByte >> 6)) != 0)
+ {
+ // this is 3rd byte of 4 byte sequence - nothing to do
+ continue;
+ }
+
+ // 2nd byte of 4 bytes
+ // check for non-shortest form of surrogate and the valid surrogate
+ // range 0x000000 - 0x10FFFF at the same time
+ if (!InRange(ch & 0x1F0, 0x10, 0x100))
+ {
+ goto InvalidByteSequence;
+ }
+ }
+ else
+ {
+ // Must be 2nd byte of a 3-byte sequence
+ // check for non-shortest form of 3 byte seq
+ if ((ch & (0x1F << 5)) == 0 || // non-shortest form
+ (ch & (0xF800 >> 6)) == (0xD800 >> 6)) // illegal individually encoded surrogate
+ {
+ goto InvalidByteSequence;
+ }
+ }
+ continue;
+ }
+
+ // ready to punch
+
+ // surrogate in shortest form?
+ // Might be possible to get rid of this? Already did non-shortest check for 4-byte sequence when reading 2nd byte?
+ if ((ch & (SupplimentarySeq | 0x1F0000)) > SupplimentarySeq)
+ {
+ // let the range check for the second char throw the exception
+ if (pTarget < pAllocatedBufferEnd)
+ {
+ *pTarget = (char)(((ch >> 10) & 0x7FF) +
+ unchecked((short)((CharUnicodeInfo.HIGH_SURROGATE_START - (0x10000 >> 10)))));
+ pTarget++;
+
+ ch = (ch & 0x3FF) +
+ unchecked((int)(CharUnicodeInfo.LOW_SURROGATE_START));
+ }
+ }
+
+ goto EncodeChar;
+
+ InvalidByteSequence:
+ // this code fragment should be close to the gotos referencing it
+ // Have to do fallback for invalid bytes
+ if (fallback == null)
+ {
+ if (baseDecoder == null)
+ fallback = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallback = baseDecoder.FallbackBuffer;
+ fallback.InternalInitialize(bytes, pAllocatedBufferEnd);
+ }
+ // That'll back us up the appropriate # of bytes if we didn't get anywhere
+ pSrcForFallback = pSrc; // Avoid passing pSrc by reference to allow it to be en-registered
+ pTargetForFallback = pTarget; // Avoid passing pTarget by reference to allow it to be en-registered
+ bool fallbackResult = FallbackInvalidByteSequence(ref pSrcForFallback, ch, fallback, ref pTargetForFallback);
+ pSrc = pSrcForFallback;
+ pTarget = pTargetForFallback;
+
+ if (!fallbackResult)
+ {
+ // Ran out of buffer space
+ // Need to throw an exception?
+ Debug.Assert(pSrc >= bytes || pTarget == chars,
+ "[UTF8Encoding.GetChars]Expected to throw or remain in byte buffer after fallback");
+ fallback.InternalReset();
+ ThrowCharsOverflow(baseDecoder, pTarget == chars);
+ ch = 0;
+ break;
+ }
+ Debug.Assert(pSrc >= bytes,
+ "[UTF8Encoding.GetChars]Expected invalid byte sequence to have remained within the byte array");
+ ch = 0;
+ continue;
+
+ ReadChar:
+ ch = *pSrc;
+ pSrc++;
+
+ ProcessChar:
+ if (ch > 0x7F)
+ {
+ // If its > 0x7F, its start of a new multi-byte sequence
+
+ // bit 6 has to be non-zero
+ if ((ch & 0x40) == 0)
+ {
+ goto InvalidByteSequence;
+ }
+
+ // start a new long code
+ if ((ch & 0x20) != 0)
+ {
+ if ((ch & 0x10) != 0)
+ {
+ // 4 byte encoding - supplimentary character (2 surrogates)
+
+ ch &= 0x0F;
+
+ // check that bit 4 is zero and the valid supplimentary character
+ // range 0x000000 - 0x10FFFF at the same time
+ if (ch > 0x04)
+ {
+ ch |= 0xf0;
+ goto InvalidByteSequence;
+ }
+
+ ch |= (FinalByte >> 3 * 6) | (1 << 30) | (3 << (30 - 2 * 6)) |
+ (SupplimentarySeq) | (SupplimentarySeq >> 6) |
+ (SupplimentarySeq >> 2 * 6) | (SupplimentarySeq >> 3 * 6);
+ }
+ else
+ {
+ // 3 byte encoding
+ ch = (ch & 0x0F) | ((FinalByte >> 2 * 6) | (1 << 30) |
+ (ThreeByteSeq) | (ThreeByteSeq >> 6) | (ThreeByteSeq >> 2 * 6));
+ }
+ }
+ else
+ {
+ // 2 byte encoding
+
+ ch &= 0x1F;
+
+ // check for non-shortest form
+ if (ch <= 1)
+ {
+ ch |= 0xc0;
+ goto InvalidByteSequence;
+ }
+
+ ch |= (FinalByte >> 6);
+ }
+ continue;
+ }
+
+ EncodeChar:
+ // write the pending character
+ if (pTarget >= pAllocatedBufferEnd)
+ {
+ // Fix chars so we make sure to throw if we didn't output anything
+ ch &= 0x1fffff;
+ if (ch > 0x7f)
+ {
+ if (ch > 0x7ff)
+ {
+ if (ch >= CharUnicodeInfo.LOW_SURROGATE_START &&
+ ch <= CharUnicodeInfo.LOW_SURROGATE_END)
+ {
+ pSrc--; // It was 4 bytes
+ pTarget--; // 1 was stored already, but we can't remember 1/2, so back up
+ }
+ else if (ch > 0xffff)
+ {
+ pSrc--; // It was 4 bytes, nothing was stored
+ }
+ pSrc--; // It was at least 3 bytes
+ }
+ pSrc--; // It was at least 2 bytes
+ }
+ pSrc--;
+
+ // Throw that we don't have enough room (pSrc could be < chars if we had started to process
+ // a 4 byte sequence already)
+ Debug.Assert(pSrc >= bytes || pTarget == chars,
+ "[UTF8Encoding.GetChars]Expected pSrc to be within input buffer or throw due to no output]");
+ ThrowCharsOverflow(baseDecoder, pTarget == chars);
+
+ // Don't store ch in decoder, we already backed up to its start
+ ch = 0;
+
+ // Didn't throw, just use this buffer size.
+ break;
+ }
+ *pTarget = (char)ch;
+ pTarget++;
+
+#if FASTLOOP
+ int availableChars = PtrDiff(pAllocatedBufferEnd, pTarget);
+ int availableBytes = PtrDiff(pEnd, pSrc);
+
+ // don't fall into the fast decoding loop if we don't have enough bytes
+ // Test for availableChars is done because pStop would be <= pTarget.
+ if (availableBytes <= 13)
+ {
+ // we may need as many as 1 character per byte
+ if (availableChars < availableBytes)
+ {
+ // not enough output room. no pending bits at this point
+ ch = 0;
+ continue;
+ }
+
+ // try to get over the remainder of the ascii characters fast though
+ byte* pLocalEnd = pEnd; // hint to get pLocalEnd enregistered
+ while (pSrc < pLocalEnd)
+ {
+ ch = *pSrc;
+ pSrc++;
+
+ if (ch > 0x7F)
+ goto ProcessChar;
+
+ *pTarget = (char)ch;
+ pTarget++;
+ }
+ // we are done
+ ch = 0;
+ break;
+ }
+
+ // we may need as many as 1 character per byte, so reduce the byte count if necessary.
+ // If availableChars is too small, pStop will be before pTarget and we won't do fast loop.
+ if (availableChars < availableBytes)
+ {
+ availableBytes = availableChars;
+ }
+
+ // To compute the upper bound, assume that all characters are ASCII characters at this point,
+ // the boundary will be decreased for every non-ASCII character we encounter
+ // Also, we need 7 chars reserve for the unrolled ansi decoding loop and for decoding of multibyte sequences
+ char* pStop = pTarget + availableBytes - 7;
+
+ while (pTarget < pStop)
+ {
+ ch = *pSrc;
+ pSrc++;
+
+ if (ch > 0x7F)
+ {
+ goto LongCode;
+ }
+ *pTarget = (char)ch;
+ pTarget++;
+
+ // get pSrc to be 2-byte aligned
+ if ((unchecked((int)pSrc) & 0x1) != 0)
+ {
+ ch = *pSrc;
+ pSrc++;
+ if (ch > 0x7F)
+ {
+ goto LongCode;
+ }
+ *pTarget = (char)ch;
+ pTarget++;
+ }
+
+ // get pSrc to be 4-byte aligned
+ if ((unchecked((int)pSrc) & 0x2) != 0)
+ {
+ ch = *(ushort*)pSrc;
+ if ((ch & 0x8080) != 0)
+ {
+ goto LongCodeWithMask16;
+ }
+
+ // Unfortunately, this is endianess sensitive
+#if BIGENDIAN
+ *pTarget = (char)((ch >> 8) & 0x7F);
+ pSrc += 2;
+ *(pTarget+1) = (char)(ch & 0x7F);
+ pTarget += 2;
+#else // BIGENDIAN
+ *pTarget = (char)(ch & 0x7F);
+ pSrc += 2;
+ *(pTarget + 1) = (char)((ch >> 8) & 0x7F);
+ pTarget += 2;
+#endif // BIGENDIAN
+ }
+
+ // Run 8 characters at a time!
+ while (pTarget < pStop)
+ {
+ ch = *(int*)pSrc;
+ int chb = *(int*)(pSrc + 4);
+ if (((ch | chb) & unchecked((int)0x80808080)) != 0)
+ {
+ goto LongCodeWithMask32;
+ }
+
+ // Unfortunately, this is endianess sensitive
+#if BIGENDIAN
+ *pTarget = (char)((ch >> 24) & 0x7F);
+ *(pTarget+1) = (char)((ch >> 16) & 0x7F);
+ *(pTarget+2) = (char)((ch >> 8) & 0x7F);
+ *(pTarget+3) = (char)(ch & 0x7F);
+ pSrc += 8;
+ *(pTarget+4) = (char)((chb >> 24) & 0x7F);
+ *(pTarget+5) = (char)((chb >> 16) & 0x7F);
+ *(pTarget+6) = (char)((chb >> 8) & 0x7F);
+ *(pTarget+7) = (char)(chb & 0x7F);
+ pTarget += 8;
+#else // BIGENDIAN
+ *pTarget = (char)(ch & 0x7F);
+ *(pTarget + 1) = (char)((ch >> 8) & 0x7F);
+ *(pTarget + 2) = (char)((ch >> 16) & 0x7F);
+ *(pTarget + 3) = (char)((ch >> 24) & 0x7F);
+ pSrc += 8;
+ *(pTarget + 4) = (char)(chb & 0x7F);
+ *(pTarget + 5) = (char)((chb >> 8) & 0x7F);
+ *(pTarget + 6) = (char)((chb >> 16) & 0x7F);
+ *(pTarget + 7) = (char)((chb >> 24) & 0x7F);
+ pTarget += 8;
+#endif // BIGENDIAN
+ }
+ break;
+
+#if BIGENDIAN
+ LongCodeWithMask32:
+ // be careful about the sign extension
+ ch = (int)(((uint)ch) >> 16);
+ LongCodeWithMask16:
+ ch = (int)(((uint)ch) >> 8);
+#else // BIGENDIAN
+ LongCodeWithMask32:
+ LongCodeWithMask16:
+ ch &= 0xFF;
+#endif // BIGENDIAN
+ pSrc++;
+ if (ch <= 0x7F)
+ {
+ *pTarget = (char)ch;
+ pTarget++;
+ continue;
+ }
+
+ LongCode:
+ int chc = *pSrc;
+ pSrc++;
+
+ if (
+ // bit 6 has to be zero
+ (ch & 0x40) == 0 ||
+ // we are expecting to see trailing bytes like 10vvvvvv
+ (chc & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ goto BadLongCode;
+ }
+
+ chc &= 0x3F;
+
+ // start a new long code
+ if ((ch & 0x20) != 0)
+ {
+ // fold the first two bytes together
+ chc |= (ch & 0x0F) << 6;
+
+ if ((ch & 0x10) != 0)
+ {
+ // 4 byte encoding - surrogate
+ ch = *pSrc;
+ if (
+ // check that bit 4 is zero, the non-shortest form of surrogate
+ // and the valid surrogate range 0x000000 - 0x10FFFF at the same time
+ !InRange(chc >> 4, 0x01, 0x10) ||
+ // we are expecting to see trailing bytes like 10vvvvvv
+ (ch & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ goto BadLongCode;
+ }
+
+ chc = (chc << 6) | (ch & 0x3F);
+
+ ch = *(pSrc + 1);
+ // we are expecting to see trailing bytes like 10vvvvvv
+ if ((ch & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ goto BadLongCode;
+ }
+ pSrc += 2;
+
+ ch = (chc << 6) | (ch & 0x3F);
+
+ *pTarget = (char)(((ch >> 10) & 0x7FF) +
+ unchecked((short)(CharUnicodeInfo.HIGH_SURROGATE_START - (0x10000 >> 10))));
+ pTarget++;
+
+ ch = (ch & 0x3FF) +
+ unchecked((short)(CharUnicodeInfo.LOW_SURROGATE_START));
+
+ // extra byte, we're already planning 2 chars for 2 of these bytes,
+ // but the big loop is testing the target against pStop, so we need
+ // to subtract 2 more or we risk overrunning the input. Subtract
+ // one here and one below.
+ pStop--;
+ }
+ else
+ {
+ // 3 byte encoding
+ ch = *pSrc;
+ if (
+ // check for non-shortest form of 3 byte seq
+ (chc & (0x1F << 5)) == 0 ||
+ // Can't have surrogates here.
+ (chc & (0xF800 >> 6)) == (0xD800 >> 6) ||
+ // we are expecting to see trailing bytes like 10vvvvvv
+ (ch & unchecked((sbyte)0xC0)) != 0x80)
+ {
+ goto BadLongCode;
+ }
+ pSrc++;
+
+ ch = (chc << 6) | (ch & 0x3F);
+
+ // extra byte, we're only expecting 1 char for each of these 3 bytes,
+ // but the loop is testing the target (not source) against pStop, so
+ // we need to subtract 2 more or we risk overrunning the input.
+ // Subtract 1 here and one more below
+ pStop--;
+ }
+ }
+ else
+ {
+ // 2 byte encoding
+
+ ch &= 0x1F;
+
+ // check for non-shortest form
+ if (ch <= 1)
+ {
+ goto BadLongCode;
+ }
+ ch = (ch << 6) | chc;
+ }
+
+ *pTarget = (char)ch;
+ pTarget++;
+
+ // extra byte, we're only expecting 1 char for each of these 2 bytes,
+ // but the loop is testing the target (not source) against pStop.
+ // subtract an extra count from pStop so that we don't overrun the input.
+ pStop--;
+ }
+#endif // FASTLOOP
+
+ Debug.Assert(pTarget <= pAllocatedBufferEnd, "[UTF8Encoding.GetChars]pTarget <= pAllocatedBufferEnd");
+
+ // no pending bits at this point
+ ch = 0;
+ continue;
+
+ BadLongCode:
+ pSrc -= 2;
+ ch = 0;
+ continue;
+ }
+
+ if (ch != 0 && (baseDecoder == null || baseDecoder.MustFlush))
+ {
+ // Have to do fallback for invalid bytes
+ if (fallback == null)
+ {
+ if (baseDecoder == null)
+ fallback = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallback = baseDecoder.FallbackBuffer;
+ fallback.InternalInitialize(bytes, pAllocatedBufferEnd);
+ }
+
+ // That'll back us up the appropriate # of bytes if we didn't get anywhere
+ pSrcForFallback = pSrc; // Avoid passing pSrc by reference to allow it to be en-registered
+ pTargetForFallback = pTarget; // Avoid passing pTarget by reference to allow it to be en-registered
+ bool fallbackResult = FallbackInvalidByteSequence(ref pSrcForFallback, ch, fallback, ref pTargetForFallback);
+ pSrc = pSrcForFallback;
+ pTarget = pTargetForFallback;
+
+ if (!fallbackResult)
+ {
+ Debug.Assert(pSrc >= bytes || pTarget == chars,
+ "[UTF8Encoding.GetChars]Expected to throw or remain in byte buffer while flushing");
+
+ // Ran out of buffer space
+ // Need to throw an exception?
+ fallback.InternalReset();
+ ThrowCharsOverflow(baseDecoder, pTarget == chars);
+ }
+ Debug.Assert(pSrc >= bytes,
+ "[UTF8Encoding.GetChars]Expected flushing invalid byte sequence to have remained within the byte array");
+ ch = 0;
+ }
+
+ if (baseDecoder != null)
+ {
+ UTF8Decoder decoder = (UTF8Decoder)baseDecoder;
+
+ // If we're storing flush data we expect all bits to be used or else
+ // we're stuck in the middle of a conversion
+ Debug.Assert(!baseDecoder.MustFlush || ch == 0 || !baseDecoder._throwOnOverflow,
+ "[UTF8Encoding.GetChars]Expected no must flush or no left over bits or no throw on overflow.");
+
+ // Remember our leftover bits.
+ decoder.bits = ch;
+
+ baseDecoder._bytesUsed = (int)(pSrc - bytes);
+ }
+
+ // Shouldn't have anything in fallback buffer for GetChars
+ // (don't have to check _throwOnOverflow for chars)
+ Debug.Assert(fallback == null || fallback.Remaining == 0,
+ "[UTF8Encoding.GetChars]Expected empty fallback buffer at end");
+
+ return PtrDiff(pTarget, chars);
+ }
+
+ // During GetChars we had an invalid byte sequence
+ // pSrc is backed up to the start of the bad sequence if we didn't have room to
+ // fall it back. Otherwise pSrc remains where it is.
+ private unsafe bool FallbackInvalidByteSequence(
+ ref byte* pSrc, int ch, DecoderFallbackBuffer fallback, ref char* pTarget)
+ {
+ // Get our byte[]
+ byte* pStart = pSrc;
+ byte[] bytesUnknown = GetBytesUnknown(ref pStart, ch);
+
+ // Do the actual fallback
+ if (!fallback.InternalFallback(bytesUnknown, pSrc, ref pTarget))
+ {
+ // Oops, it failed, back up to pStart
+ pSrc = pStart;
+ return false;
+ }
+
+ // It worked
+ return true;
+ }
+
+ // During GetCharCount we had an invalid byte sequence
+ // pSrc is used to find the index that points to the invalid bytes,
+ // however the byte[] contains the fallback bytes (in case the index is -1)
+ private unsafe int FallbackInvalidByteSequence(
+ byte* pSrc, int ch, DecoderFallbackBuffer fallback)
+ {
+ // Get our byte[]
+ byte[] bytesUnknown = GetBytesUnknown(ref pSrc, ch);
+
+ // Do the actual fallback
+ int count = fallback.InternalFallback(bytesUnknown, pSrc);
+
+ // # of fallback chars expected.
+ // Note that we only get here for "long" sequences, and have already unreserved
+ // the count that we prereserved for the input bytes
+ return count;
+ }
+
+ // Note that some of these bytes may have come from a previous fallback, so we cannot
+ // just decrement the pointer and use the values we read. In those cases we have
+ // to regenerate the original values.
+ private unsafe byte[] GetBytesUnknown(ref byte* pSrc, int ch)
+ {
+ // Get our byte[]
+ byte[] bytesUnknown = null;
+
+ // See if it was a plain char
+ // (have to check >= 0 because we have all sorts of wierd bit flags)
+ if (ch < 0x100 && ch >= 0)
+ {
+ pSrc--;
+ bytesUnknown = new byte[] { unchecked((byte)ch) };
+ }
+ // See if its an unfinished 2 byte sequence
+ else if ((ch & (SupplimentarySeq | ThreeByteSeq)) == 0)
+ {
+ pSrc--;
+ bytesUnknown = new byte[] { unchecked((byte)((ch & 0x1F) | 0xc0)) };
+ }
+ // So now we're either 2nd byte of 3 or 4 byte sequence or
+ // we hit a non-trail byte or we ran out of space for 3rd byte of 4 byte sequence
+ // 1st check if its a 4 byte sequence
+ else if ((ch & SupplimentarySeq) != 0)
+ {
+ // 3rd byte of 4 byte sequence?
+ if ((ch & (FinalByte >> 6)) != 0)
+ {
+ // 3rd byte of 4 byte sequence
+ pSrc -= 3;
+ bytesUnknown = new byte[] {
+ unchecked((byte)(((ch >> 12) & 0x07) | 0xF0)),
+ unchecked((byte)(((ch >> 6) & 0x3F) | 0x80)),
+ unchecked((byte)(((ch) & 0x3F) | 0x80)) };
+ }
+ else if ((ch & (FinalByte >> 12)) != 0)
+ {
+ // 2nd byte of a 4 byte sequence
+ pSrc -= 2;
+ bytesUnknown = new byte[] {
+ unchecked((byte)(((ch >> 6) & 0x07) | 0xF0)),
+ unchecked((byte)(((ch) & 0x3F) | 0x80)) };
+ }
+ else
+ {
+ // 4th byte of a 4 byte sequence
+ pSrc--;
+ bytesUnknown = new byte[] { unchecked((byte)(((ch) & 0x07) | 0xF0)) };
+ }
+ }
+ else
+ {
+ // 2nd byte of 3 byte sequence?
+ if ((ch & (FinalByte >> 6)) != 0)
+ {
+ // So its 2nd byte of a 3 byte sequence
+ pSrc -= 2;
+ bytesUnknown = new byte[] {
+ unchecked((byte)(((ch >> 6) & 0x0F) | 0xE0)), unchecked ((byte)(((ch) & 0x3F) | 0x80)) };
+ }
+ else
+ {
+ // 1st byte of a 3 byte sequence
+ pSrc--;
+ bytesUnknown = new byte[] { unchecked((byte)(((ch) & 0x0F) | 0xE0)) };
+ }
+ }
+
+ return bytesUnknown;
+ }
+
+
+ public override Decoder GetDecoder()
+ {
+ return new UTF8Decoder(this);
+ }
+
+
+ public override Encoder GetEncoder()
+ {
+ return new UTF8Encoder(this);
+ }
+
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ if (charCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(charCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Characters would be # of characters + 1 in case left over high surrogate is ? * max fallback
+ long byteCount = (long)charCount + 1;
+
+ if (EncoderFallback.MaxCharCount > 1)
+ byteCount *= EncoderFallback.MaxCharCount;
+
+ // Max 3 bytes per char. (4 bytes per 2 chars for surrogates)
+ byteCount *= 3;
+
+ if (byteCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GetByteCountOverflow);
+
+ return (int)byteCount;
+ }
+
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(byteCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Figure out our length, 1 char per input byte + 1 char if 1st byte is last byte of 4 byte surrogate pair
+ long charCount = ((long)byteCount + 1);
+
+ // Non-shortest form would fall back, so get max count from fallback.
+ // So would 11... followed by 11..., so you could fall back every byte
+ if (DecoderFallback.MaxCharCount > 1)
+ {
+ charCount *= DecoderFallback.MaxCharCount;
+ }
+
+ if (charCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(byteCount), SR.ArgumentOutOfRange_GetCharCountOverflow);
+
+ return (int)charCount;
+ }
+
+
+ public override byte[] GetPreamble()
+ {
+ if (_emitUTF8Identifier)
+ {
+ // Allocate new array to prevent users from modifying it.
+ return new byte[3] { 0xEF, 0xBB, 0xBF };
+ }
+ else
+ return Array.Empty<byte>();
+ }
+
+ public override ReadOnlySpan<byte> Preamble =>
+ GetType() != typeof(UTF8Encoding) ? GetPreamble() : // in case a derived UTF8Encoding overrode GetPreamble
+ _emitUTF8Identifier ? s_preamble :
+ Array.Empty<byte>();
+
+ public override bool Equals(Object value)
+ {
+ UTF8Encoding that = value as UTF8Encoding;
+ if (that != null)
+ {
+ return (_emitUTF8Identifier == that._emitUTF8Identifier) &&
+ (EncoderFallback.Equals(that.EncoderFallback)) &&
+ (DecoderFallback.Equals(that.DecoderFallback));
+ }
+ return (false);
+ }
+
+
+ public override int GetHashCode()
+ {
+ //Not great distribution, but this is relatively unlikely to be used as the key in a hashtable.
+ return this.EncoderFallback.GetHashCode() + this.DecoderFallback.GetHashCode() +
+ UTF8_CODEPAGE + (_emitUTF8Identifier ? 1 : 0);
+ }
+
+ private sealed class UTF8Encoder : EncoderNLS
+ {
+ // We must save a high surrogate value until the next call, looking
+ // for a low surrogate value.
+ internal int surrogateChar;
+
+ public UTF8Encoder(UTF8Encoding encoding) : base(encoding)
+ {
+ // base calls reset
+ }
+
+ public override void Reset()
+
+ {
+ this.surrogateChar = 0;
+ if (_fallbackBuffer != null)
+ _fallbackBuffer.Reset();
+ }
+
+ // Anything left in our encoder?
+ internal override bool HasState
+ {
+ get
+ {
+ return (this.surrogateChar != 0);
+ }
+ }
+ }
+
+ private sealed class UTF8Decoder : DecoderNLS
+ {
+ // We'll need to remember the previous information. See the comments around definition
+ // of FinalByte for details.
+ internal int bits;
+
+ public UTF8Decoder(UTF8Encoding encoding) : base(encoding)
+ {
+ // base calls reset
+ }
+
+ public override void Reset()
+ {
+ this.bits = 0;
+ if (_fallbackBuffer != null)
+ _fallbackBuffer.Reset();
+ }
+
+ // Anything left in our decoder?
+ internal override bool HasState
+ {
+ get
+ {
+ return (this.bits != 0);
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Text/UnicodeEncoding.cs b/src/System.Private.CoreLib/shared/System/Text/UnicodeEncoding.cs
new file mode 100644
index 0000000000..342bf53d62
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/UnicodeEncoding.cs
@@ -0,0 +1,1985 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// Don't override IsAlwaysNormalized because it is just a Unicode Transformation and could be confused.
+//
+
+using System;
+using System.Globalization;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ public class UnicodeEncoding : Encoding
+ {
+ // Used by Encoding.BigEndianUnicode/Unicode for lazy initialization
+ // The initialization code will not be run until a static member of the class is referenced
+ internal static readonly UnicodeEncoding s_bigEndianDefault = new UnicodeEncoding(bigEndian: true, byteOrderMark: true);
+ internal static readonly UnicodeEncoding s_littleEndianDefault = new UnicodeEncoding(bigEndian: false, byteOrderMark: true);
+
+ private static readonly byte[] s_bigEndianPreamble = new byte[2] { 0xfe, 0xff };
+ private static readonly byte[] s_littleEndianPreamble = new byte[2] { 0xff, 0xfe };
+
+ internal bool isThrowException = false;
+
+ internal bool bigEndian = false;
+ internal bool byteOrderMark = true;
+
+ // Unicode version 2.0 character size in bytes
+ public const int CharSize = 2;
+
+
+ public UnicodeEncoding()
+ : this(false, true)
+ {
+ }
+
+
+ public UnicodeEncoding(bool bigEndian, bool byteOrderMark)
+ : this(bigEndian, byteOrderMark, false)
+ {
+ }
+
+
+ public UnicodeEncoding(bool bigEndian, bool byteOrderMark, bool throwOnInvalidBytes)
+ : base(bigEndian ? 1201 : 1200) //Set the data item.
+ {
+ this.isThrowException = throwOnInvalidBytes;
+ this.bigEndian = bigEndian;
+ this.byteOrderMark = byteOrderMark;
+
+ // Encoding constructor already did this, but it'll be wrong if we're throwing exceptions
+ if (this.isThrowException)
+ SetDefaultFallbacks();
+ }
+
+ internal override void SetDefaultFallbacks()
+ {
+ // For UTF-X encodings, we use a replacement fallback with an empty string
+ if (this.isThrowException)
+ {
+ this.encoderFallback = EncoderFallback.ExceptionFallback;
+ this.decoderFallback = DecoderFallback.ExceptionFallback;
+ }
+ else
+ {
+ this.encoderFallback = new EncoderReplacementFallback("\xFFFD");
+ this.decoderFallback = new DecoderReplacementFallback("\xFFFD");
+ }
+ }
+
+ // The following methods are copied from EncodingNLS.cs.
+ // Unfortunately EncodingNLS.cs is internal and we're public, so we have to re-implement them here.
+ // These should be kept in sync for the following classes:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ //
+
+ // Returns the number of bytes required to encode a range of characters in
+ // a character array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(char[] chars, int index, int count)
+ {
+ // Validate input parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - index < count)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input, return 0, avoid fixed empty array problem
+ if (count == 0)
+ return 0;
+
+ // Just call the pointer version
+ fixed (char* pChars = chars)
+ return GetByteCount(pChars + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetByteCount(String s)
+ {
+ // Validate input
+ if (s==null)
+ throw new ArgumentNullException("s");
+
+ fixed (char* pChars = s)
+ return GetByteCount(pChars, s.Length, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetByteCount(char* chars, int count)
+ {
+ // Validate Parameters
+ if (chars == null)
+ throw new ArgumentNullException("chars", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Call it with empty encoder
+ return GetByteCount(chars, count, null);
+ }
+
+ // Parent method is safe.
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ public override unsafe int GetBytes(String s, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ if (s == null || bytes == null)
+ throw new ArgumentNullException((s == null ? "s" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (s.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("s", SR.ArgumentOutOfRange_IndexCount);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = s) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // Encodes a range of characters in a character array into a range of bytes
+ // in a byte array. An exception occurs if the byte array is not large
+ // enough to hold the complete encoding of the characters. The
+ // GetByteCount method can be used to determine the exact number of
+ // bytes that will be produced for a given range of characters.
+ // Alternatively, the GetMaxByteCount method can be used to
+ // determine the maximum number of bytes that will be produced for a given
+ // number of characters, regardless of the actual character values.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
+ byte[] bytes, int byteIndex)
+ {
+ // Validate parameters
+ if (chars == null || bytes == null)
+ throw new ArgumentNullException((chars == null ? "chars" : "bytes"), SR.ArgumentNull_Array);
+
+ if (charIndex < 0 || charCount < 0)
+ throw new ArgumentOutOfRangeException((charIndex < 0 ? "charIndex" : "charCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (chars.Length - charIndex < charCount)
+ throw new ArgumentOutOfRangeException("chars", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (byteIndex < 0 || byteIndex > bytes.Length)
+ throw new ArgumentOutOfRangeException("byteIndex", SR.ArgumentOutOfRange_Index);
+
+ // If nothing to encode return 0, avoid fixed problem
+ if (charCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int byteCount = bytes.Length - byteIndex;
+
+ fixed (char* pChars = chars) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span<byte>)bytes))
+ // Remember that byteCount is # to decode, not size of array.
+ return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetBytes(chars, charCount, bytes, byteCount, null);
+ }
+
+ // Returns the number of characters produced by decoding a range of bytes
+ // in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetCharCount(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // If no input just return 0, fixed doesn't like 0 length arrays
+ if (count == 0)
+ return 0;
+
+ // Just call pointer version
+ fixed (byte* pBytes = bytes)
+ return GetCharCount(pBytes + index, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public override unsafe int GetCharCount(byte* bytes, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetCharCount(bytes, count, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
+ char[] chars, int charIndex)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (byteIndex < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((byteIndex < 0 ? "byteIndex" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if ( bytes.Length - byteIndex < byteCount)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ if (charIndex < 0 || charIndex > chars.Length)
+ throw new ArgumentOutOfRangeException("charIndex", SR.ArgumentOutOfRange_Index);
+
+ // If no input, return 0 & avoid fixed problem
+ if (byteCount == 0)
+ return 0;
+
+ // Just call pointer version
+ int charCount = chars.Length - charIndex;
+
+ fixed (byte* pBytes = bytes) fixed (char* pChars = &MemoryMarshal.GetReference((Span<char>)chars))
+ // Remember that charCount is # to decode, not size of array
+ return GetChars(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, null);
+ }
+
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+
+ [CLSCompliant(false)]
+ public unsafe override int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ // Validate Parameters
+ if (bytes == null || chars == null)
+ throw new ArgumentNullException(bytes == null ? "bytes" : "chars", SR.ArgumentNull_Array);
+
+ if (charCount < 0 || byteCount < 0)
+ throw new ArgumentOutOfRangeException((charCount < 0 ? "charCount" : "byteCount"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ return GetChars(bytes, byteCount, chars, charCount, null);
+ }
+
+ // Returns a string containing the decoded representation of a range of
+ // bytes in a byte array.
+ //
+ // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
+ // So if you fix this, fix the others. Currently those include:
+ // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
+ // parent method is safe
+
+ public override unsafe string GetString(byte[] bytes, int index, int count)
+ {
+ // Validate Parameters
+ if (bytes == null)
+ throw new ArgumentNullException("bytes", SR.ArgumentNull_Array);
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ if (bytes.Length - index < count)
+ throw new ArgumentOutOfRangeException("bytes", SR.ArgumentOutOfRange_IndexCountBuffer);
+
+ // Avoid problems with empty input buffer
+ if (count == 0) return String.Empty;
+
+ fixed (byte* pBytes = bytes)
+ return String.CreateStringFromEncoding(
+ pBytes + index, count, this);
+ }
+
+ //
+ // End of standard methods copied from EncodingNLS.cs
+ //
+
+ internal override unsafe int GetByteCount(char* chars, int count, EncoderNLS encoder)
+ {
+ Debug.Assert(chars != null, "[UnicodeEncoding.GetByteCount]chars!=null");
+ Debug.Assert(count >= 0, "[UnicodeEncoding.GetByteCount]count >=0");
+
+ // Start by assuming each char gets 2 bytes
+ int byteCount = count << 1;
+
+ // Check for overflow in byteCount
+ // (If they were all invalid chars, this would actually be wrong,
+ // but that's a ridiculously large # so we're not concerned about that case)
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_GetByteCountOverflow);
+
+ char* charStart = chars;
+ char* charEnd = chars + count;
+ char charLeftOver = (char)0;
+
+ bool wasHereBefore = false;
+
+ // Need -1 to check 2 at a time. If we have an even #, longChars will go
+ // from longEnd - 1/2 long to longEnd + 1/2 long. If we're odd, longChars
+ // will go from longEnd - 1 long to longEnd. (Might not get to use this)
+ ulong* longEnd = (ulong*)(charEnd - 3);
+
+ // For fallback we may need a fallback buffer
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ if (encoder != null)
+ {
+ charLeftOver = encoder._charLeftOver;
+
+ // Assume extra bytes to encode charLeftOver if it existed
+ if (charLeftOver > 0)
+ byteCount += 2;
+
+ // We mustn't have left over fallback data when counting
+ if (encoder.InternalHasFallbackBuffer)
+ {
+ fallbackBuffer = encoder.FallbackBuffer;
+ if (fallbackBuffer.Remaining > 0)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType()));
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, false);
+ }
+ }
+
+ char ch;
+ TryAgain:
+
+ while (((ch = (fallbackBuffer == null) ? (char)0 : fallbackBuffer.InternalGetNextChar()) != 0) || chars < charEnd)
+ {
+ // First unwind any fallback
+ if (ch == 0)
+ {
+ // No fallback, maybe we can do it fast
+#if !NO_FAST_UNICODE_LOOP
+#if BIGENDIAN // If endianess is backwards then each pair of bytes would be backwards.
+ if ( bigEndian &&
+#else
+ if (!bigEndian &&
+#endif // BIGENDIAN
+
+#if BIT64 // 64 bit CPU needs to be long aligned for this to work.
+ charLeftOver == 0 && (unchecked((long)chars) & 7) == 0)
+#else
+ charLeftOver == 0 && (unchecked((int)chars) & 3) == 0)
+#endif
+ {
+ // Need new char* so we can check 4 at a time
+ ulong* longChars = (ulong*)chars;
+
+ while (longChars < longEnd)
+ {
+ // See if we potentially have surrogates (0x8000 bit set)
+ // (We're either big endian on a big endian machine or little endian on
+ // a little endian machine so that'll work)
+ if ((0x8000800080008000 & *longChars) != 0)
+ {
+ // See if any of these are high or low surrogates (0xd800 - 0xdfff). If the high
+ // 5 bits looks like 11011, then its a high or low surrogate.
+ // We do the & f800 to filter the 5 bits, then ^ d800 to ensure the 0 isn't set.
+ // Note that we expect BMP characters to be more common than surrogates
+ // & each char with 11111... then ^ with 11011. Zeroes then indicate surrogates
+ ulong uTemp = (0xf800f800f800f800 & *longChars) ^ 0xd800d800d800d800;
+
+ // Check each of the 4 chars. 0 for those 16 bits means it was a surrogate
+ // but no clue if they're high or low.
+ // If each of the 4 characters are non-zero, then none are surrogates.
+ if ((uTemp & 0xFFFF000000000000) == 0 ||
+ (uTemp & 0x0000FFFF00000000) == 0 ||
+ (uTemp & 0x00000000FFFF0000) == 0 ||
+ (uTemp & 0x000000000000FFFF) == 0)
+ {
+ // It has at least 1 surrogate, but we don't know if they're high or low surrogates,
+ // or if there's 1 or 4 surrogates
+
+ // If they happen to be high/low/high/low, we may as well continue. Check the next
+ // bit to see if its set (low) or not (high) in the right pattern
+#if BIGENDIAN
+ if (((0xfc00fc00fc00fc00 & *longChars) ^ 0xd800dc00d800dc00) != 0)
+#else
+ if (((0xfc00fc00fc00fc00 & *longChars) ^ 0xdc00d800dc00d800) != 0)
+#endif
+ {
+ // Either there weren't 4 surrogates, or the 0x0400 bit was set when a high
+ // was hoped for or the 0x0400 bit wasn't set where a low was hoped for.
+
+ // Drop out to the slow loop to resolve the surrogates
+ break;
+ }
+ // else they are all surrogates in High/Low/High/Low order, so we can use them.
+ }
+ // else none are surrogates, so we can use them.
+ }
+ // else all < 0x8000 so we can use them
+
+ // We already counted these four chars, go to next long.
+ longChars++;
+ }
+
+ chars = (char*)longChars;
+
+ if (chars >= charEnd)
+ break;
+ }
+#endif // !NO_FAST_UNICODE_LOOP
+
+ // No fallback, just get next char
+ ch = *chars;
+ chars++;
+ }
+ else
+ {
+ // We weren't preallocating fallback space.
+ byteCount += 2;
+ }
+
+ // Check for high or low surrogates
+ if (ch >= 0xd800 && ch <= 0xdfff)
+ {
+ // Was it a high surrogate?
+ if (ch <= 0xdbff)
+ {
+ // Its a high surrogate, if we already had a high surrogate do its fallback
+ if (charLeftOver > 0)
+ {
+ // Unwind the current character, this should be safe because we
+ // don't have leftover data in the fallback, so chars must have
+ // advanced already.
+ Debug.Assert(chars > charStart,
+ "[UnicodeEncoding.GetByteCount]Expected chars to have advanced in unexpected high surrogate");
+ chars--;
+
+ // If previous high surrogate deallocate 2 bytes
+ byteCount -= 2;
+
+ // Fallback the previous surrogate
+ // Need to initialize fallback buffer?
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, false);
+ }
+
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be enregistered
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+
+ // Now no high surrogate left over
+ charLeftOver = (char)0;
+ continue;
+ }
+
+ // Remember this high surrogate
+ charLeftOver = ch;
+ continue;
+ }
+
+
+ // Its a low surrogate
+ if (charLeftOver == 0)
+ {
+ // Expected a previous high surrogate.
+ // Don't count this one (we'll count its fallback if necessary)
+ byteCount -= 2;
+
+ // fallback this one
+ // Need to initialize fallback buffer?
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, false);
+ }
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(ch, ref charsForFallback);
+ chars = charsForFallback;
+ continue;
+ }
+
+ // Valid surrogate pair, add our charLeftOver
+ charLeftOver = (char)0;
+ continue;
+ }
+ else if (charLeftOver > 0)
+ {
+ // Expected a low surrogate, but this char is normal
+
+ // Rewind the current character, fallback previous character.
+ // this should be safe because we don't have leftover data in the
+ // fallback, so chars must have advanced already.
+ Debug.Assert(chars > charStart,
+ "[UnicodeEncoding.GetByteCount]Expected chars to have advanced when expected low surrogate");
+ chars--;
+
+ // fallback previous chars
+ // Need to initialize fallback buffer?
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, false);
+ }
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+
+ // Ignore charLeftOver or throw
+ byteCount -= 2;
+ charLeftOver = (char)0;
+
+ continue;
+ }
+
+ // Ok we had something to add (already counted)
+ }
+
+ // Don't allocate space for left over char
+ if (charLeftOver > 0)
+ {
+ byteCount -= 2;
+
+ // If we have to flush, stick it in fallback and try again
+ if (encoder == null || encoder.MustFlush)
+ {
+ if (wasHereBefore)
+ {
+ // Throw it, using our complete character
+ throw new ArgumentException(
+ SR.Format(SR.Argument_RecursiveFallback, charLeftOver), nameof(chars));
+ }
+ else
+ {
+ // Need to initialize fallback buffer?
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, false);
+ }
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+ charLeftOver = (char)0;
+ wasHereBefore = true;
+ goto TryAgain;
+ }
+ }
+ }
+
+ // Shouldn't have anything in fallback buffer for GetByteCount
+ // (don't have to check _throwOnOverflow for count)
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[UnicodeEncoding.GetByteCount]Expected empty fallback buffer at end");
+
+ // Don't remember fallbackBuffer.encoder for counting
+ return byteCount;
+ }
+
+ internal override unsafe int GetBytes(char* chars, int charCount,
+ byte* bytes, int byteCount, EncoderNLS encoder)
+ {
+ Debug.Assert(chars != null, "[UnicodeEncoding.GetBytes]chars!=null");
+ Debug.Assert(byteCount >= 0, "[UnicodeEncoding.GetBytes]byteCount >=0");
+ Debug.Assert(charCount >= 0, "[UnicodeEncoding.GetBytes]charCount >=0");
+ Debug.Assert(bytes != null, "[UnicodeEncoding.GetBytes]bytes!=null");
+
+ char charLeftOver = (char)0;
+ char ch;
+ bool wasHereBefore = false;
+
+
+ byte* byteEnd = bytes + byteCount;
+ char* charEnd = chars + charCount;
+ byte* byteStart = bytes;
+ char* charStart = chars;
+
+ // For fallback we may need a fallback buffer
+ EncoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ // Get our encoder, but don't clear it yet.
+ if (encoder != null)
+ {
+ charLeftOver = encoder._charLeftOver;
+
+ // We mustn't have left over fallback data when counting
+ if (encoder.InternalHasFallbackBuffer)
+ {
+ // We always need the fallback buffer in get bytes so we can flush any remaining ones if necessary
+ fallbackBuffer = encoder.FallbackBuffer;
+ if (fallbackBuffer.Remaining > 0 && encoder._throwOnOverflow)
+ throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType()));
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, false);
+ }
+ }
+
+ TryAgain:
+ while (((ch = (fallbackBuffer == null) ?
+ (char)0 : fallbackBuffer.InternalGetNextChar()) != 0) ||
+ chars < charEnd)
+ {
+ // First unwind any fallback
+ if (ch == 0)
+ {
+ // No fallback, maybe we can do it fast
+#if !NO_FAST_UNICODE_LOOP
+#if BIGENDIAN // If endianess is backwards then each pair of bytes would be backwards.
+ if ( bigEndian &&
+#else
+ if (!bigEndian &&
+#endif // BIGENDIAN
+#if BIT64 // 64 bit CPU needs to be long aligned for this to work, 32 bit CPU needs to be 32 bit aligned
+ (unchecked((long)chars) & 7) == 0 && (unchecked((long)bytes) & 7) == 0 &&
+#else
+ (unchecked((int)chars) & 3) == 0 && (unchecked((int)bytes) & 3) == 0 &&
+#endif // BIT64
+ charLeftOver == 0)
+ {
+ // Need -1 to check 2 at a time. If we have an even #, longChars will go
+ // from longEnd - 1/2 long to longEnd + 1/2 long. If we're odd, longChars
+ // will go from longEnd - 1 long to longEnd. (Might not get to use this)
+ // We can only go iCount units (limited by shorter of char or byte buffers.
+ ulong* longEnd = (ulong*)(chars - 3 +
+ (((byteEnd - bytes) >> 1 < charEnd - chars) ?
+ (byteEnd - bytes) >> 1 : charEnd - chars));
+
+ // Need new char* so we can check 4 at a time
+ ulong* longChars = (ulong*)chars;
+ ulong* longBytes = (ulong*)bytes;
+
+ while (longChars < longEnd)
+ {
+ // See if we potentially have surrogates (0x8000 bit set)
+ // (We're either big endian on a big endian machine or little endian on
+ // a little endian machine so that'll work)
+ if ((0x8000800080008000 & *longChars) != 0)
+ {
+ // See if any of these are high or low surrogates (0xd800 - 0xdfff). If the high
+ // 5 bits looks like 11011, then its a high or low surrogate.
+ // We do the & f800 to filter the 5 bits, then ^ d800 to ensure the 0 isn't set.
+ // Note that we expect BMP characters to be more common than surrogates
+ // & each char with 11111... then ^ with 11011. Zeroes then indicate surrogates
+ ulong uTemp = (0xf800f800f800f800 & *longChars) ^ 0xd800d800d800d800;
+
+ // Check each of the 4 chars. 0 for those 16 bits means it was a surrogate
+ // but no clue if they're high or low.
+ // If each of the 4 characters are non-zero, then none are surrogates.
+ if ((uTemp & 0xFFFF000000000000) == 0 ||
+ (uTemp & 0x0000FFFF00000000) == 0 ||
+ (uTemp & 0x00000000FFFF0000) == 0 ||
+ (uTemp & 0x000000000000FFFF) == 0)
+ {
+ // It has at least 1 surrogate, but we don't know if they're high or low surrogates,
+ // or if there's 1 or 4 surrogates
+
+ // If they happen to be high/low/high/low, we may as well continue. Check the next
+ // bit to see if its set (low) or not (high) in the right pattern
+#if BIGENDIAN
+ if (((0xfc00fc00fc00fc00 & *longChars) ^ 0xd800dc00d800dc00) != 0)
+#else
+ if (((0xfc00fc00fc00fc00 & *longChars) ^ 0xdc00d800dc00d800) != 0)
+#endif
+ {
+ // Either there weren't 4 surrogates, or the 0x0400 bit was set when a high
+ // was hoped for or the 0x0400 bit wasn't set where a low was hoped for.
+
+ // Drop out to the slow loop to resolve the surrogates
+ break;
+ }
+ // else they are all surrogates in High/Low/High/Low order, so we can use them.
+ }
+ // else none are surrogates, so we can use them.
+ }
+ // else all < 0x8000 so we can use them
+
+ // We can use these 4 chars.
+ *longBytes = *longChars;
+ longChars++;
+ longBytes++;
+ }
+
+ chars = (char*)longChars;
+ bytes = (byte*)longBytes;
+
+ if (chars >= charEnd)
+ break;
+ }
+ // Not aligned, but maybe we can still be somewhat faster
+ // Also somehow this optimizes the above loop? It seems to cause something above
+ // to get enregistered, but I haven't figured out how to make that happen without this loop.
+ else if ((charLeftOver == 0) &&
+#if BIGENDIAN
+ bigEndian &&
+#else
+ !bigEndian &&
+#endif // BIGENDIAN
+
+#if BIT64
+ (unchecked((long)chars) & 7) != (unchecked((long)bytes) & 7) && // Only do this if chars & bytes are out of line, otherwise faster loop will be faster next time
+#else
+ (unchecked((int)chars) & 3) != (unchecked((int)bytes) & 3) && // Only do this if chars & bytes are out of line, otherwise faster loop will be faster next time
+#endif // BIT64
+ (unchecked((int)(bytes)) & 1) == 0)
+ {
+ // # to use
+ long iCount = ((byteEnd - bytes) >> 1 < charEnd - chars) ?
+ (byteEnd - bytes) >> 1 : charEnd - chars;
+
+ // Need new char*
+ char* charOut = ((char*)bytes); // a char* for our output
+ char* tempEnd = chars + iCount - 1; // Our end pointer
+
+ while (chars < tempEnd)
+ {
+ if (*chars >= (char)0xd800 && *chars <= (char)0xdfff)
+ {
+ // break for fallback for low surrogate
+ if (*chars >= 0xdc00)
+ break;
+
+ // break if next one's not a low surrogate (will do fallback)
+ if (*(chars + 1) < 0xdc00 || *(chars + 1) > 0xdfff)
+ break;
+
+ // They both exist, use them
+ }
+ // If 2nd char is surrogate & this one isn't then only add one
+ else if (*(chars + 1) >= (char)0xd800 && *(chars + 1) <= 0xdfff)
+ {
+ *charOut = *chars;
+ charOut++;
+ chars++;
+ continue;
+ }
+
+ *charOut = *chars;
+ *(charOut + 1) = *(chars + 1);
+ charOut += 2;
+ chars += 2;
+ }
+
+ bytes = (byte*)charOut;
+
+ if (chars >= charEnd)
+ break;
+ }
+#endif // !NO_FAST_UNICODE_LOOP
+
+ // No fallback, just get next char
+ ch = *chars;
+ chars++;
+ }
+
+ // Check for high or low surrogates
+ if (ch >= 0xd800 && ch <= 0xdfff)
+ {
+ // Was it a high surrogate?
+ if (ch <= 0xdbff)
+ {
+ // Its a high surrogate, see if we already had a high surrogate
+ if (charLeftOver > 0)
+ {
+ // Unwind the current character, this should be safe because we
+ // don't have leftover data in the fallback, so chars must have
+ // advanced already.
+ Debug.Assert(chars > charStart,
+ "[UnicodeEncoding.GetBytes]Expected chars to have advanced in unexpected high surrogate");
+ chars--;
+
+ // Fallback the previous surrogate
+ // Might need to create our fallback buffer
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, true);
+ }
+
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+
+ charLeftOver = (char)0;
+ continue;
+ }
+
+ // Remember this high surrogate
+ charLeftOver = ch;
+ continue;
+ }
+
+ // Its a low surrogate
+ if (charLeftOver == 0)
+ {
+ // We'll fall back this one
+ // Might need to create our fallback buffer
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, true);
+ }
+
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(ch, ref charsForFallback);
+ chars = charsForFallback;
+ continue;
+ }
+
+ // Valid surrogate pair, add our charLeftOver
+ if (bytes + 3 >= byteEnd)
+ {
+ // Not enough room to add this surrogate pair
+ if (fallbackBuffer != null && fallbackBuffer.bFallingBack)
+ {
+ // These must have both been from the fallbacks.
+ // Both of these MUST have been from a fallback because if the 1st wasn't
+ // from a fallback, then a high surrogate followed by an illegal char
+ // would've caused the high surrogate to fall back. If a high surrogate
+ // fell back, then it was consumed and both chars came from the fallback.
+ fallbackBuffer.MovePrevious(); // Didn't use either fallback surrogate
+ fallbackBuffer.MovePrevious();
+ }
+ else
+ {
+ // If we don't have enough room, then either we should've advanced a while
+ // or we should have bytes==byteStart and throw below
+ Debug.Assert(chars > charStart + 1 || bytes == byteStart,
+ "[UnicodeEncoding.GetBytes]Expected chars to have when no room to add surrogate pair");
+ chars -= 2; // Didn't use either surrogate
+ }
+ ThrowBytesOverflow(encoder, bytes == byteStart); // Throw maybe (if no bytes written)
+ charLeftOver = (char)0; // we'll retry it later
+ break; // Didn't throw, but stop 'til next time.
+ }
+
+ if (bigEndian)
+ {
+ *(bytes++) = (byte)(charLeftOver >> 8);
+ *(bytes++) = (byte)charLeftOver;
+ }
+ else
+ {
+ *(bytes++) = (byte)charLeftOver;
+ *(bytes++) = (byte)(charLeftOver >> 8);
+ }
+
+ charLeftOver = (char)0;
+ }
+ else if (charLeftOver > 0)
+ {
+ // Expected a low surrogate, but this char is normal
+
+ // Rewind the current character, fallback previous character.
+ // this should be safe because we don't have leftover data in the
+ // fallback, so chars must have advanced already.
+ Debug.Assert(chars > charStart,
+ "[UnicodeEncoding.GetBytes]Expected chars to have advanced after expecting low surrogate");
+ chars--;
+
+ // fallback previous chars
+ // Might need to create our fallback buffer
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, true);
+ }
+
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+
+ // Ignore charLeftOver or throw
+ charLeftOver = (char)0;
+ continue;
+ }
+
+ // Ok, we have a char to add
+ if (bytes + 1 >= byteEnd)
+ {
+ // Couldn't add this char
+ if (fallbackBuffer != null && fallbackBuffer.bFallingBack)
+ fallbackBuffer.MovePrevious(); // Not using this fallback char
+ else
+ {
+ // Lonely charLeftOver (from previous call) would've been caught up above,
+ // so this must be a case where we've already read an input char.
+ Debug.Assert(chars > charStart,
+ "[UnicodeEncoding.GetBytes]Expected chars to have advanced for failed fallback");
+ chars--; // Not using this char
+ }
+ ThrowBytesOverflow(encoder, bytes == byteStart); // Throw maybe (if no bytes written)
+ break; // didn't throw, just stop
+ }
+
+ if (bigEndian)
+ {
+ *(bytes++) = (byte)(ch >> 8);
+ *(bytes++) = (byte)ch;
+ }
+ else
+ {
+ *(bytes++) = (byte)ch;
+ *(bytes++) = (byte)(ch >> 8);
+ }
+ }
+
+ // Don't allocate space for left over char
+ if (charLeftOver > 0)
+ {
+ // If we aren't flushing we need to fall this back
+ if (encoder == null || encoder.MustFlush)
+ {
+ if (wasHereBefore)
+ {
+ // Throw it, using our complete character
+ throw new ArgumentException(
+ SR.Format(SR.Argument_RecursiveFallback, charLeftOver), nameof(chars));
+ }
+ else
+ {
+ // If we have to flush, stick it in fallback and try again
+ // Might need to create our fallback buffer
+ if (fallbackBuffer == null)
+ {
+ if (encoder == null)
+ fallbackBuffer = this.encoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = encoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(charStart, charEnd, encoder, true);
+ }
+
+ // If we're not flushing, that'll remember the left over character.
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ fallbackBuffer.InternalFallback(charLeftOver, ref charsForFallback);
+ chars = charsForFallback;
+
+ charLeftOver = (char)0;
+ wasHereBefore = true;
+ goto TryAgain;
+ }
+ }
+ }
+
+ // Not flushing, remember it in the encoder
+ if (encoder != null)
+ {
+ encoder._charLeftOver = charLeftOver;
+ encoder._charsUsed = (int)(chars - charStart);
+ }
+
+ // Remember charLeftOver if we must, or clear it if we're flushing
+ // (charLeftOver should be 0 if we're flushing)
+ Debug.Assert((encoder != null && !encoder.MustFlush) || charLeftOver == (char)0,
+ "[UnicodeEncoding.GetBytes] Expected no left over characters if flushing");
+
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0 ||
+ encoder == null || !encoder._throwOnOverflow,
+ "[UnicodeEncoding.GetBytes]Expected empty fallback buffer if not converting");
+
+ // We used to copy it fast, but this doesn't check for surrogates
+ // System.IO.__UnmanagedMemoryStream.memcpyimpl(bytes, (byte*)chars, usedByteCount);
+
+ return (int)(bytes - byteStart);
+ }
+
+ internal override unsafe int GetCharCount(byte* bytes, int count, DecoderNLS baseDecoder)
+ {
+ Debug.Assert(bytes != null, "[UnicodeEncoding.GetCharCount]bytes!=null");
+ Debug.Assert(count >= 0, "[UnicodeEncoding.GetCharCount]count >=0");
+
+ UnicodeEncoding.Decoder decoder = (UnicodeEncoding.Decoder)baseDecoder;
+
+ byte* byteEnd = bytes + count;
+ byte* byteStart = bytes;
+
+ // Need last vars
+ int lastByte = -1;
+ char lastChar = (char)0;
+
+ // Start by assuming same # of chars as bytes
+ int charCount = count >> 1;
+
+ // Need -1 to check 2 at a time. If we have an even #, longBytes will go
+ // from longEnd - 1/2 long to longEnd + 1/2 long. If we're odd, longBytes
+ // will go from longEnd - 1 long to longEnd. (Might not get to use this)
+ ulong* longEnd = (ulong*)(byteEnd - 7);
+
+ // For fallback we may need a fallback buffer
+ DecoderFallbackBuffer fallbackBuffer = null;
+
+ if (decoder != null)
+ {
+ lastByte = decoder.lastByte;
+ lastChar = decoder.lastChar;
+
+ // Assume extra char if last char was around
+ if (lastChar > 0)
+ charCount++;
+
+ // Assume extra char if extra last byte makes up odd # of input bytes
+ if (lastByte >= 0 && (count & 1) == 1)
+ {
+ charCount++;
+ }
+
+ // Shouldn't have anything in fallback buffer for GetCharCount
+ // (don't have to check _throwOnOverflow for count)
+ Debug.Assert(!decoder.InternalHasFallbackBuffer || decoder.FallbackBuffer.Remaining == 0,
+ "[UnicodeEncoding.GetCharCount]Expected empty fallback buffer at start");
+ }
+
+ while (bytes < byteEnd)
+ {
+ // If we're aligned then maybe we can do it fast
+ // That'll hurt if we're unaligned because we'll always test but never be aligned
+#if !NO_FAST_UNICODE_LOOP
+#if BIGENDIAN
+ if (bigEndian &&
+#else // BIGENDIAN
+ if (!bigEndian &&
+#endif // BIGENDIAN
+#if BIT64 // win64 has to be long aligned
+ (unchecked((long)bytes) & 7) == 0 &&
+#else
+ (unchecked((int)bytes) & 3) == 0 &&
+#endif // BIT64
+ lastByte == -1 && lastChar == 0)
+ {
+ // Need new char* so we can check 4 at a time
+ ulong* longBytes = (ulong*)bytes;
+
+ while (longBytes < longEnd)
+ {
+ // See if we potentially have surrogates (0x8000 bit set)
+ // (We're either big endian on a big endian machine or little endian on
+ // a little endian machine so that'll work)
+ if ((0x8000800080008000 & *longBytes) != 0)
+ {
+ // See if any of these are high or low surrogates (0xd800 - 0xdfff). If the high
+ // 5 bits looks like 11011, then its a high or low surrogate.
+ // We do the & f800 to filter the 5 bits, then ^ d800 to ensure the 0 isn't set.
+ // Note that we expect BMP characters to be more common than surrogates
+ // & each char with 11111... then ^ with 11011. Zeroes then indicate surrogates
+ ulong uTemp = (0xf800f800f800f800 & *longBytes) ^ 0xd800d800d800d800;
+
+ // Check each of the 4 chars. 0 for those 16 bits means it was a surrogate
+ // but no clue if they're high or low.
+ // If each of the 4 characters are non-zero, then none are surrogates.
+ if ((uTemp & 0xFFFF000000000000) == 0 ||
+ (uTemp & 0x0000FFFF00000000) == 0 ||
+ (uTemp & 0x00000000FFFF0000) == 0 ||
+ (uTemp & 0x000000000000FFFF) == 0)
+ {
+ // It has at least 1 surrogate, but we don't know if they're high or low surrogates,
+ // or if there's 1 or 4 surrogates
+
+ // If they happen to be high/low/high/low, we may as well continue. Check the next
+ // bit to see if its set (low) or not (high) in the right pattern
+#if BIGENDIAN
+ if (((0xfc00fc00fc00fc00 & *longBytes) ^ 0xd800dc00d800dc00) != 0)
+#else
+ if (((0xfc00fc00fc00fc00 & *longBytes) ^ 0xdc00d800dc00d800) != 0)
+#endif
+ {
+ // Either there weren't 4 surrogates, or the 0x0400 bit was set when a high
+ // was hoped for or the 0x0400 bit wasn't set where a low was hoped for.
+
+ // Drop out to the slow loop to resolve the surrogates
+ break;
+ }
+ // else they are all surrogates in High/Low/High/Low order, so we can use them.
+ }
+ // else none are surrogates, so we can use them.
+ }
+ // else all < 0x8000 so we can use them
+
+ // We can use these 4 chars.
+ longBytes++;
+ }
+
+ bytes = (byte*)longBytes;
+
+ if (bytes >= byteEnd)
+ break;
+ }
+#endif // !NO_FAST_UNICODE_LOOP
+
+ // Get 1st byte
+ if (lastByte < 0)
+ {
+ lastByte = *bytes++;
+ if (bytes >= byteEnd) break;
+ }
+
+ // Get full char
+ char ch;
+ if (bigEndian)
+ {
+ ch = (char)(lastByte << 8 | *(bytes++));
+ }
+ else
+ {
+ ch = (char)(*(bytes++) << 8 | lastByte);
+ }
+ lastByte = -1;
+
+ // See if the char's valid
+ if (ch >= 0xd800 && ch <= 0xdfff)
+ {
+ // Was it a high surrogate?
+ if (ch <= 0xdbff)
+ {
+ // Its a high surrogate, if we had one then do fallback for previous one
+ if (lastChar > 0)
+ {
+ // Ignore previous bad high surrogate
+ charCount--;
+
+ // Get fallback for previous high surrogate
+ // Note we have to reconstruct bytes because some may have been in decoder
+ byte[] byteBuffer = null;
+ if (bigEndian)
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)(lastChar >> 8)), unchecked((byte)lastChar) };
+ }
+ else
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)lastChar), unchecked((byte)(lastChar >> 8)) };
+ }
+
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, null);
+ }
+
+ // Get fallback.
+ charCount += fallbackBuffer.InternalFallback(byteBuffer, bytes);
+ }
+
+ // Ignore the last one which fell back already,
+ // and remember the new high surrogate
+ lastChar = ch;
+ continue;
+ }
+
+ // Its a low surrogate
+ if (lastChar == 0)
+ {
+ // Expected a previous high surrogate
+ charCount--;
+
+ // Get fallback for this low surrogate
+ // Note we have to reconstruct bytes because some may have been in decoder
+ byte[] byteBuffer = null;
+ if (bigEndian)
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)(ch >> 8)), unchecked((byte)ch) };
+ }
+ else
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)ch), unchecked((byte)(ch >> 8)) };
+ }
+
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, null);
+ }
+
+ charCount += fallbackBuffer.InternalFallback(byteBuffer, bytes);
+
+ // Ignore this one (we already did its fallback)
+ continue;
+ }
+
+ // Valid surrogate pair, already counted.
+ lastChar = (char)0;
+ }
+ else if (lastChar > 0)
+ {
+ // Had a high surrogate, expected a low surrogate
+ // Un-count the last high surrogate
+ charCount--;
+
+ // fall back the high surrogate.
+ byte[] byteBuffer = null;
+ if (bigEndian)
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)(lastChar >> 8)), unchecked((byte)lastChar) };
+ }
+ else
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)lastChar), unchecked((byte)(lastChar >> 8)) };
+ }
+
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, null);
+ }
+
+ // Already subtracted high surrogate
+ charCount += fallbackBuffer.InternalFallback(byteBuffer, bytes);
+
+ // Not left over now, clear previous high surrogate and continue to add current char
+ lastChar = (char)0;
+ }
+
+ // Valid char, already counted
+ }
+
+ // Extra space if we can't use decoder
+ if (decoder == null || decoder.MustFlush)
+ {
+ if (lastChar > 0)
+ {
+ // No hanging high surrogates allowed, do fallback and remove count for it
+ charCount--;
+ byte[] byteBuffer = null;
+ if (bigEndian)
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)(lastChar >> 8)), unchecked((byte)lastChar) };
+ }
+ else
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)lastChar), unchecked((byte)(lastChar >> 8)) };
+ }
+
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, null);
+ }
+
+ charCount += fallbackBuffer.InternalFallback(byteBuffer, bytes);
+
+ lastChar = (char)0;
+ }
+
+ if (lastByte >= 0)
+ {
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, null);
+ }
+
+ // No hanging odd bytes allowed if must flush
+ charCount += fallbackBuffer.InternalFallback(new byte[] { unchecked((byte)lastByte) }, bytes);
+ lastByte = -1;
+ }
+ }
+
+ // If we had a high surrogate left over, we can't count it
+ if (lastChar > 0)
+ charCount--;
+
+ // Shouldn't have anything in fallback buffer for GetCharCount
+ // (don't have to check _throwOnOverflow for count)
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[UnicodeEncoding.GetCharCount]Expected empty fallback buffer at end");
+
+ return charCount;
+ }
+
+ internal override unsafe int GetChars(byte* bytes, int byteCount,
+ char* chars, int charCount, DecoderNLS baseDecoder)
+ {
+ Debug.Assert(chars != null, "[UnicodeEncoding.GetChars]chars!=null");
+ Debug.Assert(byteCount >= 0, "[UnicodeEncoding.GetChars]byteCount >=0");
+ Debug.Assert(charCount >= 0, "[UnicodeEncoding.GetChars]charCount >=0");
+ Debug.Assert(bytes != null, "[UnicodeEncoding.GetChars]bytes!=null");
+
+ UnicodeEncoding.Decoder decoder = (UnicodeEncoding.Decoder)baseDecoder;
+
+ // Need last vars
+ int lastByte = -1;
+ char lastChar = (char)0;
+
+ // Get our decoder (but don't clear it yet)
+ if (decoder != null)
+ {
+ lastByte = decoder.lastByte;
+ lastChar = decoder.lastChar;
+
+ // Shouldn't have anything in fallback buffer for GetChars
+ // (don't have to check _throwOnOverflow for chars)
+ Debug.Assert(!decoder.InternalHasFallbackBuffer || decoder.FallbackBuffer.Remaining == 0,
+ "[UnicodeEncoding.GetChars]Expected empty fallback buffer at start");
+ }
+
+ // For fallback we may need a fallback buffer
+ DecoderFallbackBuffer fallbackBuffer = null;
+ char* charsForFallback;
+
+ byte* byteEnd = bytes + byteCount;
+ char* charEnd = chars + charCount;
+ byte* byteStart = bytes;
+ char* charStart = chars;
+
+ while (bytes < byteEnd)
+ {
+ // If we're aligned then maybe we can do it fast
+ // That'll hurt if we're unaligned because we'll always test but never be aligned
+#if !NO_FAST_UNICODE_LOOP
+#if BIGENDIAN
+ if (bigEndian &&
+#else // BIGENDIAN
+ if (!bigEndian &&
+#endif // BIGENDIAN
+#if BIT64 // win64 has to be long aligned
+ (unchecked((long)chars) & 7) == 0 && (unchecked((long)bytes) & 7) == 0 &&
+#else
+ (unchecked((int)chars) & 3) == 0 && (unchecked((int)bytes) & 3) == 0 &&
+#endif // BIT64
+ lastByte == -1 && lastChar == 0)
+ {
+ // Need -1 to check 2 at a time. If we have an even #, longChars will go
+ // from longEnd - 1/2 long to longEnd + 1/2 long. If we're odd, longChars
+ // will go from longEnd - 1 long to longEnd. (Might not get to use this)
+ // We can only go iCount units (limited by shorter of char or byte buffers.
+ ulong* longEnd = (ulong*)(bytes - 7 +
+ (((byteEnd - bytes) >> 1 < charEnd - chars) ?
+ (byteEnd - bytes) : (charEnd - chars) << 1));
+
+ // Need new char* so we can check 4 at a time
+ ulong* longBytes = (ulong*)bytes;
+ ulong* longChars = (ulong*)chars;
+
+ while (longBytes < longEnd)
+ {
+ // See if we potentially have surrogates (0x8000 bit set)
+ // (We're either big endian on a big endian machine or little endian on
+ // a little endian machine so that'll work)
+ if ((0x8000800080008000 & *longBytes) != 0)
+ {
+ // See if any of these are high or low surrogates (0xd800 - 0xdfff). If the high
+ // 5 bits looks like 11011, then its a high or low surrogate.
+ // We do the & f800 to filter the 5 bits, then ^ d800 to ensure the 0 isn't set.
+ // Note that we expect BMP characters to be more common than surrogates
+ // & each char with 11111... then ^ with 11011. Zeroes then indicate surrogates
+ ulong uTemp = (0xf800f800f800f800 & *longBytes) ^ 0xd800d800d800d800;
+
+ // Check each of the 4 chars. 0 for those 16 bits means it was a surrogate
+ // but no clue if they're high or low.
+ // If each of the 4 characters are non-zero, then none are surrogates.
+ if ((uTemp & 0xFFFF000000000000) == 0 ||
+ (uTemp & 0x0000FFFF00000000) == 0 ||
+ (uTemp & 0x00000000FFFF0000) == 0 ||
+ (uTemp & 0x000000000000FFFF) == 0)
+ {
+ // It has at least 1 surrogate, but we don't know if they're high or low surrogates,
+ // or if there's 1 or 4 surrogates
+
+ // If they happen to be high/low/high/low, we may as well continue. Check the next
+ // bit to see if its set (low) or not (high) in the right pattern
+#if BIGENDIAN
+ if (((0xfc00fc00fc00fc00 & *longBytes) ^ 0xd800dc00d800dc00) != 0)
+#else
+ if (((0xfc00fc00fc00fc00 & *longBytes) ^ 0xdc00d800dc00d800) != 0)
+#endif
+ {
+ // Either there weren't 4 surrogates, or the 0x0400 bit was set when a high
+ // was hoped for or the 0x0400 bit wasn't set where a low was hoped for.
+
+ // Drop out to the slow loop to resolve the surrogates
+ break;
+ }
+ // else they are all surrogates in High/Low/High/Low order, so we can use them.
+ }
+ // else none are surrogates, so we can use them.
+ }
+ // else all < 0x8000 so we can use them
+
+ // We can use these 4 chars.
+ *longChars = *longBytes;
+ longBytes++;
+ longChars++;
+ }
+
+ chars = (char*)longChars;
+ bytes = (byte*)longBytes;
+
+ if (bytes >= byteEnd)
+ break;
+ }
+#endif // !NO_FAST_UNICODE_LOOP
+
+ // Get 1st byte
+ if (lastByte < 0)
+ {
+ lastByte = *bytes++;
+ continue;
+ }
+
+ // Get full char
+ char ch;
+ if (bigEndian)
+ {
+ ch = (char)(lastByte << 8 | *(bytes++));
+ }
+ else
+ {
+ ch = (char)(*(bytes++) << 8 | lastByte);
+ }
+ lastByte = -1;
+
+ // See if the char's valid
+ if (ch >= 0xd800 && ch <= 0xdfff)
+ {
+ // Was it a high surrogate?
+ if (ch <= 0xdbff)
+ {
+ // Its a high surrogate, if we had one then do fallback for previous one
+ if (lastChar > 0)
+ {
+ // Get fallback for previous high surrogate
+ // Note we have to reconstruct bytes because some may have been in decoder
+ byte[] byteBuffer = null;
+ if (bigEndian)
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)(lastChar >> 8)), unchecked((byte)lastChar) };
+ }
+ else
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)lastChar), unchecked((byte)(lastChar >> 8)) };
+ }
+
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, charEnd);
+ }
+
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ bool fallbackResult = fallbackBuffer.InternalFallback(byteBuffer, bytes, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (!fallbackResult)
+ {
+ // couldn't fall back lonely surrogate
+ // We either advanced bytes or chars should == charStart and throw below
+ Debug.Assert(bytes >= byteStart + 2 || chars == charStart,
+ "[UnicodeEncoding.GetChars]Expected bytes to have advanced or no output (bad surrogate)");
+ bytes -= 2; // didn't use these 2 bytes
+ fallbackBuffer.InternalReset();
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ break; // couldn't fallback but didn't throw
+ }
+ }
+
+ // Ignore the previous high surrogate which fell back already,
+ // yet remember the current high surrogate for next time.
+ lastChar = ch;
+ continue;
+ }
+
+ // Its a low surrogate
+ if (lastChar == 0)
+ {
+ // Expected a previous high surrogate
+ // Get fallback for this low surrogate
+ // Note we have to reconstruct bytes because some may have been in decoder
+ byte[] byteBuffer = null;
+ if (bigEndian)
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)(ch >> 8)), unchecked((byte)ch) };
+ }
+ else
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)ch), unchecked((byte)(ch >> 8)) };
+ }
+
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, charEnd);
+ }
+
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ bool fallbackResult = fallbackBuffer.InternalFallback(byteBuffer, bytes, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (!fallbackResult)
+ {
+ // couldn't fall back lonely surrogate
+ // We either advanced bytes or chars should == charStart and throw below
+ Debug.Assert(bytes >= byteStart + 2 || chars == charStart,
+ "[UnicodeEncoding.GetChars]Expected bytes to have advanced or no output (lonely surrogate)");
+ bytes -= 2; // didn't use these 2 bytes
+ fallbackBuffer.InternalReset();
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ break; // couldn't fallback but didn't throw
+ }
+
+ // Didn't throw, ignore this one (we already did its fallback)
+ continue;
+ }
+
+ // Valid surrogate pair, add our lastChar (will need 2 chars)
+ if (chars >= charEnd - 1)
+ {
+ // couldn't find room for this surrogate pair
+ // We either advanced bytes or chars should == charStart and throw below
+ Debug.Assert(bytes >= byteStart + 2 || chars == charStart,
+ "[UnicodeEncoding.GetChars]Expected bytes to have advanced or no output (surrogate pair)");
+ bytes -= 2; // didn't use these 2 bytes
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ // Leave lastChar for next call to Convert()
+ break; // couldn't fallback but didn't throw
+ }
+
+ *chars++ = lastChar;
+ lastChar = (char)0;
+ }
+ else if (lastChar > 0)
+ {
+ // Had a high surrogate, expected a low surrogate, fall back the high surrogate.
+ byte[] byteBuffer = null;
+ if (bigEndian)
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)(lastChar >> 8)), unchecked((byte)lastChar) };
+ }
+ else
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)lastChar), unchecked((byte)(lastChar >> 8)) };
+ }
+
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, charEnd);
+ }
+
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ bool fallbackResult = fallbackBuffer.InternalFallback(byteBuffer, bytes, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (!fallbackResult)
+ {
+ // couldn't fall back high surrogate, or char that would be next
+ // We either advanced bytes or chars should == charStart and throw below
+ Debug.Assert(bytes >= byteStart + 2 || chars == charStart,
+ "[UnicodeEncoding.GetChars]Expected bytes to have advanced or no output (no low surrogate)");
+ bytes -= 2; // didn't use these 2 bytes
+ fallbackBuffer.InternalReset();
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ break; // couldn't fallback but didn't throw
+ }
+
+ // Not left over now, clear previous high surrogate and continue to add current char
+ lastChar = (char)0;
+ }
+
+ // Valid char, room for it?
+ if (chars >= charEnd)
+ {
+ // 2 bytes couldn't fall back
+ // We either advanced bytes or chars should == charStart and throw below
+ Debug.Assert(bytes >= byteStart + 2 || chars == charStart,
+ "[UnicodeEncoding.GetChars]Expected bytes to have advanced or no output (normal)");
+ bytes -= 2; // didn't use these bytes
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ break; // couldn't fallback but didn't throw
+ }
+
+ // add it
+ *chars++ = ch;
+ }
+
+ // Remember our decoder if we must
+ if (decoder == null || decoder.MustFlush)
+ {
+ if (lastChar > 0)
+ {
+ // No hanging high surrogates allowed, do fallback and remove count for it
+ byte[] byteBuffer = null;
+ if (bigEndian)
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)(lastChar >> 8)), unchecked((byte)lastChar) };
+ }
+ else
+ {
+ byteBuffer = new byte[]
+ { unchecked((byte)lastChar), unchecked((byte)(lastChar >> 8)) };
+ }
+
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, charEnd);
+ }
+
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ bool fallbackResult = fallbackBuffer.InternalFallback(byteBuffer, bytes, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (!fallbackResult)
+ {
+ // 2 bytes couldn't fall back
+ // We either advanced bytes or chars should == charStart and throw below
+ Debug.Assert(bytes >= byteStart + 2 || chars == charStart,
+ "[UnicodeEncoding.GetChars]Expected bytes to have advanced or no output (decoder)");
+ bytes -= 2; // didn't use these bytes
+ if (lastByte >= 0)
+ bytes--; // had an extra last byte hanging around
+ fallbackBuffer.InternalReset();
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ // We'll remember these in our decoder though
+ bytes += 2;
+ if (lastByte >= 0)
+ bytes++;
+ goto End;
+ }
+
+ // done with this one
+ lastChar = (char)0;
+ }
+
+ if (lastByte >= 0)
+ {
+ if (fallbackBuffer == null)
+ {
+ if (decoder == null)
+ fallbackBuffer = this.decoderFallback.CreateFallbackBuffer();
+ else
+ fallbackBuffer = decoder.FallbackBuffer;
+
+ // Set our internal fallback interesting things.
+ fallbackBuffer.InternalInitialize(byteStart, charEnd);
+ }
+
+ // No hanging odd bytes allowed if must flush
+ charsForFallback = chars; // Avoid passing chars by reference to allow it to be en-registered
+ bool fallbackResult = fallbackBuffer.InternalFallback(new byte[] { unchecked((byte)lastByte) }, bytes, ref charsForFallback);
+ chars = charsForFallback;
+
+ if (!fallbackResult)
+ {
+ // odd byte couldn't fall back
+ bytes--; // didn't use this byte
+ fallbackBuffer.InternalReset();
+ ThrowCharsOverflow(decoder, chars == charStart);// Might throw, if no chars output
+ // didn't throw, but we'll remember it in the decoder
+ bytes++;
+ goto End;
+ }
+
+ // Didn't fail, clear buffer
+ lastByte = -1;
+ }
+ }
+
+ End:
+
+ // Remember our decoder if we must
+ if (decoder != null)
+ {
+ Debug.Assert((decoder.MustFlush == false) || ((lastChar == (char)0) && (lastByte == -1)),
+ "[UnicodeEncoding.GetChars] Expected no left over chars or bytes if flushing"
+ // + " " + ((int)lastChar).ToString("X4") + " " + lastByte.ToString("X2")
+ );
+
+ decoder._bytesUsed = (int)(bytes - byteStart);
+ decoder.lastChar = lastChar;
+ decoder.lastByte = lastByte;
+ }
+
+ // Used to do this the old way
+ // System.IO.__UnmanagedMemoryStream.memcpyimpl((byte*)chars, bytes, byteCount);
+
+ // Shouldn't have anything in fallback buffer for GetChars
+ // (don't have to check _throwOnOverflow for count or chars)
+ Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0,
+ "[UnicodeEncoding.GetChars]Expected empty fallback buffer at end");
+
+ return (int)(chars - charStart);
+ }
+
+
+ public override System.Text.Encoder GetEncoder()
+ {
+ return new EncoderNLS(this);
+ }
+
+
+ public override System.Text.Decoder GetDecoder()
+ {
+ return new UnicodeEncoding.Decoder(this);
+ }
+
+
+ public override byte[] GetPreamble()
+ {
+ if (byteOrderMark)
+ {
+ // Note - we must allocate new byte[]'s here to prevent someone
+ // from modifying a cached byte[].
+ if (bigEndian)
+ return new byte[2] { 0xfe, 0xff };
+ else
+ return new byte[2] { 0xff, 0xfe };
+ }
+ return Array.Empty<Byte>();
+ }
+
+ public override ReadOnlySpan<byte> Preamble =>
+ GetType() != typeof(UnicodeEncoding) ? GetPreamble() : // in case a derived UnicodeEncoding overrode GetPreamble
+ byteOrderMark ? (bigEndian ? s_bigEndianPreamble : s_littleEndianPreamble) :
+ Array.Empty<byte>();
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ if (charCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(charCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // Characters would be # of characters + 1 in case left over high surrogate is ? * max fallback
+ long byteCount = (long)charCount + 1;
+
+ if (EncoderFallback.MaxCharCount > 1)
+ byteCount *= EncoderFallback.MaxCharCount;
+
+ // 2 bytes per char
+ byteCount <<= 1;
+
+ if (byteCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GetByteCountOverflow);
+
+ return (int)byteCount;
+ }
+
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ if (byteCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(byteCount),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ // long because byteCount could be biggest int.
+ // 1 char per 2 bytes. Round up in case 1 left over in decoder.
+ // Round up using &1 in case byteCount is max size
+ // Might also need an extra 1 if there's a left over high surrogate in the decoder.
+ long charCount = (long)(byteCount >> 1) + (byteCount & 1) + 1;
+
+ // Don't forget fallback (in case they have a bunch of lonely surrogates or something bizarre like that)
+ if (DecoderFallback.MaxCharCount > 1)
+ charCount *= DecoderFallback.MaxCharCount;
+
+ if (charCount > 0x7fffffff)
+ throw new ArgumentOutOfRangeException(nameof(byteCount), SR.ArgumentOutOfRange_GetCharCountOverflow);
+
+ return (int)charCount;
+ }
+
+
+ public override bool Equals(Object value)
+ {
+ UnicodeEncoding that = value as UnicodeEncoding;
+ if (that != null)
+ {
+ //
+ // Big Endian Unicode has different code page (1201) than small Endian one (1200),
+ // so we still have to check _codePage here.
+ //
+ return (CodePage == that.CodePage) &&
+ byteOrderMark == that.byteOrderMark &&
+ // isThrowException == that.isThrowException && // Same as Encoder/Decoder being exception fallbacks
+ bigEndian == that.bigEndian &&
+ (EncoderFallback.Equals(that.EncoderFallback)) &&
+ (DecoderFallback.Equals(that.DecoderFallback));
+ }
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return CodePage + this.EncoderFallback.GetHashCode() + this.DecoderFallback.GetHashCode() +
+ (byteOrderMark ? 4 : 0) + (bigEndian ? 8 : 0);
+ }
+
+ private sealed class Decoder : System.Text.DecoderNLS
+ {
+ internal int lastByte = -1;
+ internal char lastChar = '\0';
+
+ public Decoder(UnicodeEncoding encoding) : base(encoding)
+ {
+ // base calls reset
+ }
+
+ public override void Reset()
+ {
+ lastByte = -1;
+ lastChar = '\0';
+ if (_fallbackBuffer != null)
+ _fallbackBuffer.Reset();
+ }
+
+ // Anything left in our decoder?
+ internal override bool HasState
+ {
+ get
+ {
+ return (this.lastByte != -1 || this.lastChar != '\0');
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs b/src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs
new file mode 100644
index 0000000000..d41bea0be9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Text/ValueStringBuilder.cs
@@ -0,0 +1,257 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.Text
+{
+ internal ref struct ValueStringBuilder
+ {
+ private char[] _arrayToReturnToPool;
+ private Span<char> _chars;
+ private int _pos;
+
+ public ValueStringBuilder(Span<char> initialBuffer)
+ {
+ _arrayToReturnToPool = null;
+ _chars = initialBuffer;
+ _pos = 0;
+ }
+
+ public int Length
+ {
+ get => _pos;
+ set
+ {
+ Debug.Assert(value >= 0);
+ Debug.Assert(value <= _chars.Length);
+ _pos = value;
+ }
+ }
+
+ public int Capacity => _chars.Length;
+
+ public void EnsureCapacity(int capacity)
+ {
+ if (capacity > _chars.Length)
+ Grow(capacity - _chars.Length);
+ }
+
+ /// <summary>
+ /// Get a pinnable reference to the builder.
+ /// </summary>
+ /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
+ public ref char GetPinnableReference(bool terminate = false)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _chars[Length] = '\0';
+ }
+ return ref MemoryMarshal.GetReference(_chars);
+ }
+
+ public ref char this[int index]
+ {
+ get
+ {
+ Debug.Assert(index < _pos);
+ return ref _chars[index];
+ }
+ }
+
+ public override string ToString()
+ {
+ var s = new string(_chars.Slice(0, _pos));
+ Dispose();
+ return s;
+ }
+
+ /// <summary>Returns the underlying storage of the builder.</summary>
+ public Span<char> RawChars => _chars;
+
+ /// <summary>
+ /// Returns a span around the contents of the builder.
+ /// </summary>
+ /// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
+ public ReadOnlySpan<char> AsSpan(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _chars[Length] = '\0';
+ }
+ return _chars.Slice(0, _pos);
+ }
+
+ public ReadOnlySpan<char> AsSpan() => _chars.Slice(0, _pos);
+ public ReadOnlySpan<char> AsSpan(int start) => _chars.Slice(start, _pos - start);
+ public ReadOnlySpan<char> AsSpan(int start, int length) => _chars.Slice(start, length);
+
+ public bool TryCopyTo(Span<char> destination, out int charsWritten)
+ {
+ if (_chars.Slice(0, _pos).TryCopyTo(destination))
+ {
+ charsWritten = _pos;
+ Dispose();
+ return true;
+ }
+ else
+ {
+ charsWritten = 0;
+ Dispose();
+ return false;
+ }
+ }
+
+ public void Insert(int index, char value, int count)
+ {
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ int remaining = _pos - index;
+ _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
+ _chars.Slice(index, count).Fill(value);
+ _pos += count;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(char c)
+ {
+ int pos = _pos;
+ if (pos < _chars.Length)
+ {
+ _chars[pos] = c;
+ _pos = pos + 1;
+ }
+ else
+ {
+ GrowAndAppend(c);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(string s)
+ {
+ int pos = _pos;
+ if (s.Length == 1 && pos < _chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
+ {
+ _chars[pos] = s[0];
+ _pos = pos + 1;
+ }
+ else
+ {
+ AppendSlow(s);
+ }
+ }
+
+ private void AppendSlow(string s)
+ {
+ int pos = _pos;
+ if (pos > _chars.Length - s.Length)
+ {
+ Grow(s.Length);
+ }
+
+ s.AsSpan().CopyTo(_chars.Slice(pos));
+ _pos += s.Length;
+ }
+
+ public void Append(char c, int count)
+ {
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ Span<char> dst = _chars.Slice(_pos, count);
+ for (int i = 0; i < dst.Length; i++)
+ {
+ dst[i] = c;
+ }
+ _pos += count;
+ }
+
+ public unsafe void Append(char* value, int length)
+ {
+ int pos = _pos;
+ if (pos > _chars.Length - length)
+ {
+ Grow(length);
+ }
+
+ Span<char> dst = _chars.Slice(_pos, length);
+ for (int i = 0; i < dst.Length; i++)
+ {
+ dst[i] = *value++;
+ }
+ _pos += length;
+ }
+
+ public unsafe void Append(ReadOnlySpan<char> value)
+ {
+ int pos = _pos;
+ if (pos > _chars.Length - value.Length)
+ {
+ Grow(value.Length);
+ }
+
+ value.CopyTo(_chars.Slice(_pos));
+ _pos += value.Length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span<char> AppendSpan(int length)
+ {
+ int origPos = _pos;
+ if (origPos > _chars.Length - length)
+ {
+ Grow(length);
+ }
+
+ _pos = origPos + length;
+ return _chars.Slice(origPos, length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void GrowAndAppend(char c)
+ {
+ Grow(1);
+ Append(c);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void Grow(int requiredAdditionalCapacity)
+ {
+ Debug.Assert(requiredAdditionalCapacity > 0);
+
+ char[] poolArray = ArrayPool<char>.Shared.Rent(Math.Max(_pos + requiredAdditionalCapacity, _chars.Length * 2));
+
+ _chars.CopyTo(poolArray);
+
+ char[] toReturn = _arrayToReturnToPool;
+ _chars = _arrayToReturnToPool = poolArray;
+ if (toReturn != null)
+ {
+ ArrayPool<char>.Shared.Return(toReturn);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ char[] toReturn = _arrayToReturnToPool;
+ this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
+ if (toReturn != null)
+ {
+ ArrayPool<char>.Shared.Return(toReturn);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ThreadAttributes.cs b/src/System.Private.CoreLib/shared/System/ThreadAttributes.cs
new file mode 100644
index 0000000000..6248736107
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ThreadAttributes.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+** Purpose: For Threads-related custom attributes.
+**
+=============================================================================*/
+
+namespace System
+{
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class STAThreadAttribute : Attribute
+ {
+ public STAThreadAttribute()
+ {
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class MTAThreadAttribute : Attribute
+ {
+ public MTAThreadAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ThreadStaticAttribute.cs b/src/System.Private.CoreLib/shared/System/ThreadStaticAttribute.cs
new file mode 100644
index 0000000000..c12ac1c18d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ThreadStaticAttribute.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Custom attribute to indicate that the field should be treated
+** as a static relative to a thread.
+**
+**
+**
+===========================================================*/
+
+using System;
+
+namespace System
+{
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public class ThreadStaticAttribute : Attribute
+ {
+ public ThreadStaticAttribute()
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/AbandonedMutexException.cs b/src/System.Private.CoreLib/shared/System/Threading/AbandonedMutexException.cs
new file mode 100644
index 0000000000..c7e604f33d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/AbandonedMutexException.cs
@@ -0,0 +1,78 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//
+// AbandonedMutexException
+// Thrown when a wait completes because one or more mutexes was abandoned.
+// AbandonedMutexs indicate serious error in user code or machine state.
+////////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Threading;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class AbandonedMutexException : SystemException
+ {
+ private int _mutexIndex = -1;
+ private Mutex _mutex = null;
+
+ public AbandonedMutexException()
+ : base(SR.Threading_AbandonedMutexException)
+ {
+ HResult = HResults.COR_E_ABANDONEDMUTEX;
+ }
+
+ public AbandonedMutexException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ABANDONEDMUTEX;
+ }
+
+ public AbandonedMutexException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_ABANDONEDMUTEX;
+ }
+
+ public AbandonedMutexException(int location, WaitHandle handle)
+ : base(SR.Threading_AbandonedMutexException)
+ {
+ HResult = HResults.COR_E_ABANDONEDMUTEX;
+ SetupException(location, handle);
+ }
+
+ public AbandonedMutexException(String message, int location, WaitHandle handle)
+ : base(message)
+ {
+ HResult = HResults.COR_E_ABANDONEDMUTEX;
+ SetupException(location, handle);
+ }
+
+ public AbandonedMutexException(String message, Exception inner, int location, WaitHandle handle)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_ABANDONEDMUTEX;
+ SetupException(location, handle);
+ }
+
+ protected AbandonedMutexException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+
+ private void SetupException(int location, WaitHandle handle)
+ {
+ _mutexIndex = location;
+ if (handle != null)
+ _mutex = handle as Mutex;
+ }
+
+ public Mutex Mutex => _mutex;
+ public int MutexIndex => _mutexIndex;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ApartmentState.cs b/src/System.Private.CoreLib/shared/System/Threading/ApartmentState.cs
new file mode 100644
index 0000000000..47c1677cb5
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ApartmentState.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Threading
+{
+ public enum ApartmentState
+ {
+ /*=========================================================================
+ ** Constants for thread apartment states.
+ =========================================================================*/
+ STA = 0,
+ MTA = 1,
+ Unknown = 2
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/AsyncLocal.cs b/src/System.Private.CoreLib/shared/System/Threading/AsyncLocal.cs
new file mode 100644
index 0000000000..2bf8a046b7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/AsyncLocal.cs
@@ -0,0 +1,499 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Threading
+{
+ //
+ // AsyncLocal<T> represents "ambient" data that is local to a given asynchronous control flow, such as an
+ // async method. For example, say you want to associate a culture with a given async flow:
+ //
+ // static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>();
+ //
+ // static async Task SomeOperationAsync(Culture culture)
+ // {
+ // s_currentCulture.Value = culture;
+ //
+ // await FooAsync();
+ // }
+ //
+ // static async Task FooAsync()
+ // {
+ // PrintStringWithCulture(s_currentCulture.Value);
+ // }
+ //
+ // AsyncLocal<T> also provides optional notifications when the value associated with the current thread
+ // changes, either because it was explicitly changed by setting the Value property, or implicitly changed
+ // when the thread encountered an "await" or other context transition. For example, we might want our
+ // current culture to be communicated to the OS as well:
+ //
+ // static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>(
+ // args =>
+ // {
+ // NativeMethods.SetThreadCulture(args.CurrentValue.LCID);
+ // });
+ //
+ public sealed class AsyncLocal<T> : IAsyncLocal
+ {
+ private readonly Action<AsyncLocalValueChangedArgs<T>> m_valueChangedHandler;
+
+ //
+ // Constructs an AsyncLocal<T> that does not receive change notifications.
+ //
+ public AsyncLocal()
+ {
+ }
+
+ //
+ // Constructs an AsyncLocal<T> with a delegate that is called whenever the current value changes
+ // on any thread.
+ //
+ public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>> valueChangedHandler)
+ {
+ m_valueChangedHandler = valueChangedHandler;
+ }
+
+ public T Value
+ {
+ get
+ {
+ object obj = ExecutionContext.GetLocalValue(this);
+ return (obj == null) ? default(T) : (T)obj;
+ }
+ set
+ {
+ ExecutionContext.SetLocalValue(this, value, m_valueChangedHandler != null);
+ }
+ }
+
+ void IAsyncLocal.OnValueChanged(object previousValueObj, object currentValueObj, bool contextChanged)
+ {
+ Debug.Assert(m_valueChangedHandler != null);
+ T previousValue = previousValueObj == null ? default(T) : (T)previousValueObj;
+ T currentValue = currentValueObj == null ? default(T) : (T)currentValueObj;
+ m_valueChangedHandler(new AsyncLocalValueChangedArgs<T>(previousValue, currentValue, contextChanged));
+ }
+ }
+
+ //
+ // Interface to allow non-generic code in ExecutionContext to call into the generic AsyncLocal<T> type.
+ //
+ internal interface IAsyncLocal
+ {
+ void OnValueChanged(object previousValue, object currentValue, bool contextChanged);
+ }
+
+ public struct AsyncLocalValueChangedArgs<T>
+ {
+ public T PreviousValue { get; private set; }
+ public T CurrentValue { get; private set; }
+
+ //
+ // If the value changed because we changed to a different ExecutionContext, this is true. If it changed
+ // because someone set the Value property, this is false.
+ //
+ public bool ThreadContextChanged { get; private set; }
+
+ internal AsyncLocalValueChangedArgs(T previousValue, T currentValue, bool contextChanged)
+ : this()
+ {
+ PreviousValue = previousValue;
+ CurrentValue = currentValue;
+ ThreadContextChanged = contextChanged;
+ }
+ }
+
+ //
+ // Interface used to store an IAsyncLocal => object mapping in ExecutionContext.
+ // Implementations are specialized based on the number of elements in the immutable
+ // map in order to minimize memory consumption and look-up times.
+ //
+ internal interface IAsyncLocalValueMap
+ {
+ bool TryGetValue(IAsyncLocal key, out object value);
+ IAsyncLocalValueMap Set(IAsyncLocal key, object value, bool treatNullValueAsNonexistent);
+ }
+
+ //
+ // Utility functions for getting/creating instances of IAsyncLocalValueMap
+ //
+ internal static class AsyncLocalValueMap
+ {
+ public static IAsyncLocalValueMap Empty { get; } = new EmptyAsyncLocalValueMap();
+
+ public static bool IsEmpty(IAsyncLocalValueMap asyncLocalValueMap)
+ {
+ Debug.Assert(asyncLocalValueMap != null);
+ Debug.Assert(asyncLocalValueMap == Empty || asyncLocalValueMap.GetType() != typeof(EmptyAsyncLocalValueMap));
+
+ return asyncLocalValueMap == Empty;
+ }
+
+ public static IAsyncLocalValueMap Create(IAsyncLocal key, object value, bool treatNullValueAsNonexistent)
+ {
+ // If the value isn't null or a null value may not be treated as nonexistent, then create a new one-element map
+ // to store the key/value pair. Otherwise, use the empty map.
+ return value != null || !treatNullValueAsNonexistent ?
+ new OneElementAsyncLocalValueMap(key, value) :
+ Empty;
+ }
+
+ // Instance without any key/value pairs. Used as a singleton/
+ private sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap
+ {
+ public IAsyncLocalValueMap Set(IAsyncLocal key, object value, bool treatNullValueAsNonexistent)
+ {
+ // If the value isn't null or a null value may not be treated as nonexistent, then create a new one-element map
+ // to store the key/value pair. Otherwise, use the empty map.
+ return value != null || !treatNullValueAsNonexistent ?
+ new OneElementAsyncLocalValueMap(key, value) :
+ (IAsyncLocalValueMap)this;
+ }
+
+ public bool TryGetValue(IAsyncLocal key, out object value)
+ {
+ value = null;
+ return false;
+ }
+ }
+
+ // Instance with one key/value pair.
+ private sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap
+ {
+ private readonly IAsyncLocal _key1;
+ private readonly object _value1;
+
+ public OneElementAsyncLocalValueMap(IAsyncLocal key, object value)
+ {
+ _key1 = key; _value1 = value;
+ }
+
+ public IAsyncLocalValueMap Set(IAsyncLocal key, object value, bool treatNullValueAsNonexistent)
+ {
+ if (value != null || !treatNullValueAsNonexistent)
+ {
+ // If the key matches one already contained in this map, then create a new one-element map with the updated
+ // value, otherwise create a two-element map with the additional key/value.
+ return ReferenceEquals(key, _key1) ?
+ new OneElementAsyncLocalValueMap(key, value) :
+ (IAsyncLocalValueMap)new TwoElementAsyncLocalValueMap(_key1, _value1, key, value);
+ }
+ else
+ {
+ // If the key exists in this map, remove it by downgrading to an empty map. Otherwise, there's nothing to
+ // add or remove, so just return this map.
+ return ReferenceEquals(key, _key1) ?
+ Empty :
+ (IAsyncLocalValueMap)this;
+ }
+ }
+
+ public bool TryGetValue(IAsyncLocal key, out object value)
+ {
+ if (ReferenceEquals(key, _key1))
+ {
+ value = _value1;
+ return true;
+ }
+ else
+ {
+ value = null;
+ return false;
+ }
+ }
+ }
+
+ // Instance with two key/value pairs.
+ private sealed class TwoElementAsyncLocalValueMap : IAsyncLocalValueMap
+ {
+ private readonly IAsyncLocal _key1, _key2;
+ private readonly object _value1, _value2;
+
+ public TwoElementAsyncLocalValueMap(IAsyncLocal key1, object value1, IAsyncLocal key2, object value2)
+ {
+ _key1 = key1; _value1 = value1;
+ _key2 = key2; _value2 = value2;
+ }
+
+ public IAsyncLocalValueMap Set(IAsyncLocal key, object value, bool treatNullValueAsNonexistent)
+ {
+ if (value != null || !treatNullValueAsNonexistent)
+ {
+ // If the key matches one already contained in this map, then create a new two-element map with the updated
+ // value, otherwise create a three-element map with the additional key/value.
+ return
+ ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(key, value, _key2, _value2) :
+ ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, key, value) :
+ (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value);
+ }
+ else
+ {
+ // If the key exists in this map, remove it by downgrading to a one-element map without the key. Otherwise,
+ // there's nothing to add or remove, so just return this map.
+ return
+ ReferenceEquals(key, _key1) ? new OneElementAsyncLocalValueMap(_key2, _value2) :
+ ReferenceEquals(key, _key2) ? new OneElementAsyncLocalValueMap(_key1, _value1) :
+ (IAsyncLocalValueMap)this;
+ }
+ }
+
+ public bool TryGetValue(IAsyncLocal key, out object value)
+ {
+ if (ReferenceEquals(key, _key1))
+ {
+ value = _value1;
+ return true;
+ }
+ else if (ReferenceEquals(key, _key2))
+ {
+ value = _value2;
+ return true;
+ }
+ else
+ {
+ value = null;
+ return false;
+ }
+ }
+ }
+
+ // Instance with three key/value pairs.
+ private sealed class ThreeElementAsyncLocalValueMap : IAsyncLocalValueMap
+ {
+ private readonly IAsyncLocal _key1, _key2, _key3;
+ private readonly object _value1, _value2, _value3;
+
+ public ThreeElementAsyncLocalValueMap(IAsyncLocal key1, object value1, IAsyncLocal key2, object value2, IAsyncLocal key3, object value3)
+ {
+ _key1 = key1; _value1 = value1;
+ _key2 = key2; _value2 = value2;
+ _key3 = key3; _value3 = value3;
+ }
+
+ public IAsyncLocalValueMap Set(IAsyncLocal key, object value, bool treatNullValueAsNonexistent)
+ {
+ if (value != null || !treatNullValueAsNonexistent)
+ {
+ // If the key matches one already contained in this map, then create a new three-element map with the
+ // updated value.
+ if (ReferenceEquals(key, _key1)) return new ThreeElementAsyncLocalValueMap(key, value, _key2, _value2, _key3, _value3);
+ if (ReferenceEquals(key, _key2)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, key, value, _key3, _value3);
+ if (ReferenceEquals(key, _key3)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value);
+
+ // The key doesn't exist in this map, so upgrade to a multi map that contains
+ // the additional key/value pair.
+ var multi = new MultiElementAsyncLocalValueMap(4);
+ multi.UnsafeStore(0, _key1, _value1);
+ multi.UnsafeStore(1, _key2, _value2);
+ multi.UnsafeStore(2, _key3, _value3);
+ multi.UnsafeStore(3, key, value);
+ return multi;
+ }
+ else
+ {
+ // If the key exists in this map, remove it by downgrading to a two-element map without the key. Otherwise,
+ // there's nothing to add or remove, so just return this map.
+ return
+ ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(_key2, _value2, _key3, _value3) :
+ ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key3, _value3) :
+ ReferenceEquals(key, _key3) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key2, _value2) :
+ (IAsyncLocalValueMap)this;
+ }
+ }
+
+ public bool TryGetValue(IAsyncLocal key, out object value)
+ {
+ if (ReferenceEquals(key, _key1))
+ {
+ value = _value1;
+ return true;
+ }
+ else if (ReferenceEquals(key, _key2))
+ {
+ value = _value2;
+ return true;
+ }
+ else if (ReferenceEquals(key, _key3))
+ {
+ value = _value3;
+ return true;
+ }
+ else
+ {
+ value = null;
+ return false;
+ }
+ }
+ }
+
+ // Instance with up to 16 key/value pairs.
+ private sealed class MultiElementAsyncLocalValueMap : IAsyncLocalValueMap
+ {
+ internal const int MaxMultiElements = 16;
+ private readonly KeyValuePair<IAsyncLocal, object>[] _keyValues;
+
+ internal MultiElementAsyncLocalValueMap(int count)
+ {
+ Debug.Assert(count <= MaxMultiElements);
+ _keyValues = new KeyValuePair<IAsyncLocal, object>[count];
+ }
+
+ internal void UnsafeStore(int index, IAsyncLocal key, object value)
+ {
+ Debug.Assert(index < _keyValues.Length);
+ _keyValues[index] = new KeyValuePair<IAsyncLocal, object>(key, value);
+ }
+
+ public IAsyncLocalValueMap Set(IAsyncLocal key, object value, bool treatNullValueAsNonexistent)
+ {
+ // Find the key in this map.
+ for (int i = 0; i < _keyValues.Length; i++)
+ {
+ if (ReferenceEquals(key, _keyValues[i].Key))
+ {
+ // The key is in the map.
+ if (value != null || !treatNullValueAsNonexistent)
+ {
+ // Create a new map of the same size that has all of the same pairs, with this new key/value pair
+ // overwriting the old.
+ var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length);
+ Array.Copy(_keyValues, 0, multi._keyValues, 0, _keyValues.Length);
+ multi._keyValues[i] = new KeyValuePair<IAsyncLocal, object>(key, value);
+ return multi;
+ }
+ else if (_keyValues.Length == 4)
+ {
+ // We only have four elements, one of which we're removing, so downgrade to a three-element map,
+ // without the matching element.
+ return
+ i == 0 ? new ThreeElementAsyncLocalValueMap(_keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) :
+ i == 1 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) :
+ i == 2 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[3].Key, _keyValues[3].Value) :
+ (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value);
+ }
+ else
+ {
+ // We have enough elements remaining to warrant a multi map. Create a new one and copy all of the
+ // elements from this one, except the one to be removed.
+ var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length - 1);
+ if (i != 0) Array.Copy(_keyValues, 0, multi._keyValues, 0, i);
+ if (i != _keyValues.Length - 1) Array.Copy(_keyValues, i + 1, multi._keyValues, i, _keyValues.Length - i - 1);
+ return multi;
+ }
+ }
+ }
+
+ // The key does not already exist in this map.
+
+ if (value == null && treatNullValueAsNonexistent)
+ {
+ // We can simply return this same map, as there's nothing to add or remove.
+ return this;
+ }
+
+ // We need to create a new map that has the additional key/value pair.
+ // If with the addition we can still fit in a multi map, create one.
+ if (_keyValues.Length < MaxMultiElements)
+ {
+ var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length + 1);
+ Array.Copy(_keyValues, 0, multi._keyValues, 0, _keyValues.Length);
+ multi._keyValues[_keyValues.Length] = new KeyValuePair<IAsyncLocal, object>(key, value);
+ return multi;
+ }
+
+ // Otherwise, upgrade to a many map.
+ var many = new ManyElementAsyncLocalValueMap(MaxMultiElements + 1);
+ foreach (KeyValuePair<IAsyncLocal, object> pair in _keyValues)
+ {
+ many[pair.Key] = pair.Value;
+ }
+ many[key] = value;
+ return many;
+ }
+
+ public bool TryGetValue(IAsyncLocal key, out object value)
+ {
+ foreach (KeyValuePair<IAsyncLocal, object> pair in _keyValues)
+ {
+ if (ReferenceEquals(key, pair.Key))
+ {
+ value = pair.Value;
+ return true;
+ }
+ }
+ value = null;
+ return false;
+ }
+ }
+
+ // Instance with any number of key/value pairs.
+ private sealed class ManyElementAsyncLocalValueMap : Dictionary<IAsyncLocal, object>, IAsyncLocalValueMap
+ {
+ public ManyElementAsyncLocalValueMap(int capacity) : base(capacity) { }
+
+ public IAsyncLocalValueMap Set(IAsyncLocal key, object value, bool treatNullValueAsNonexistent)
+ {
+ int count = Count;
+ bool containsKey = ContainsKey(key);
+
+ // If the value being set exists, create a new many map, copy all of the elements from this one,
+ // and then store the new key/value pair into it. This is the most common case.
+ if (value != null || !treatNullValueAsNonexistent)
+ {
+ var map = new ManyElementAsyncLocalValueMap(count + (containsKey ? 0 : 1));
+ foreach (KeyValuePair<IAsyncLocal, object> pair in this)
+ {
+ map[pair.Key] = pair.Value;
+ }
+ map[key] = value;
+ return map;
+ }
+
+ // Otherwise, the value is null and a null value may be treated as nonexistent. We can downgrade to a smaller
+ // map rather than storing null.
+
+ // If the key is contained in this map, we're going to create a new map that's one pair smaller.
+ if (containsKey)
+ {
+ // If the new count would be within range of a multi map instead of a many map,
+ // downgrade to the multi map, which uses less memory and is faster to access.
+ // Otherwise, just create a new many map that's missing this key.
+ if (count == MultiElementAsyncLocalValueMap.MaxMultiElements + 1)
+ {
+ var multi = new MultiElementAsyncLocalValueMap(MultiElementAsyncLocalValueMap.MaxMultiElements);
+ int index = 0;
+ foreach (KeyValuePair<IAsyncLocal, object> pair in this)
+ {
+ if (!ReferenceEquals(key, pair.Key))
+ {
+ multi.UnsafeStore(index++, pair.Key, pair.Value);
+ }
+ }
+ Debug.Assert(index == MultiElementAsyncLocalValueMap.MaxMultiElements);
+ return multi;
+ }
+ else
+ {
+ var map = new ManyElementAsyncLocalValueMap(count - 1);
+ foreach (KeyValuePair<IAsyncLocal, object> pair in this)
+ {
+ if (!ReferenceEquals(key, pair.Key))
+ {
+ map[pair.Key] = pair.Value;
+ }
+ }
+ Debug.Assert(map.Count == count - 1);
+ return map;
+ }
+ }
+
+ // We were storing null and a null value may be treated as nonexistent, but the key wasn't in the map, so
+ // there's nothing to change. Just return this instance.
+ return this;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/AutoResetEvent.cs b/src/System.Private.CoreLib/shared/System/Threading/AutoResetEvent.cs
new file mode 100644
index 0000000000..8320d7ad5a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/AutoResetEvent.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Threading
+{
+ public sealed class AutoResetEvent : EventWaitHandle
+ {
+ public AutoResetEvent(bool initialState) : base(initialState, EventResetMode.AutoReset) { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Threading/DeferredDisposableLifetime.cs b/src/System.Private.CoreLib/shared/System/Threading/DeferredDisposableLifetime.cs
new file mode 100644
index 0000000000..e2b1eb983b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/DeferredDisposableLifetime.cs
@@ -0,0 +1,116 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Provides callbacks to objects whose lifetime is managed by <see cref="DeferredDisposableLifetime{T}"/>.
+ /// </summary>
+ internal interface IDeferredDisposable
+ {
+ /// <summary>
+ /// Called when the object's refcount reaches zero.
+ /// </summary>
+ /// <param name="disposed">
+ /// Indicates whether the object has been disposed.
+ /// </param>
+ /// <remarks>
+ /// If the refcount reaches zero before the object is disposed, this method will be called with
+ /// <paramref name="disposed"/> set to false. If the object is then disposed, this method will be
+ /// called again, with <paramref name="disposed"/> set to true. If the refcount reaches zero
+ /// after the object has already been disposed, this will be called a single time, with
+ /// <paramref name="disposed"/> set to true.
+ /// </remarks>
+ void OnFinalRelease(bool disposed);
+ }
+
+ /// <summary>
+ /// Manages the lifetime of an object which implements IDisposable, but which must defer the actual
+ /// cleanup of state until all existing uses of the object are complete.
+ /// </summary>
+ /// <typeparam name="T">The type of object whose lifetime will be managed.</typeparam>
+ /// <remarks>
+ /// This type maintains a reference count, and tracks whether the object has been disposed. When
+ /// Callbacks are made to <see cref="IDeferredDisposable.OnFinalRelease(bool)"/> when the refcount
+ /// reaches zero. Objects that need to defer cleanup until they have been disposed *and* they have
+ /// no more references can do so in <see cref="IDeferredDisposable.OnFinalRelease(bool)"/> when
+ /// 'disposed' is true.
+ /// </remarks>
+ internal struct DeferredDisposableLifetime<T> where T : class, IDeferredDisposable
+ {
+ //
+ // _count is positive until Dispose is called, after which it's (-1 - refcount).
+ //
+ private int _count;
+
+ public bool AddRef(T obj)
+ {
+ while (true)
+ {
+ int oldCount = Volatile.Read(ref _count);
+
+ // Have we been disposed?
+ if (oldCount < 0)
+ throw new ObjectDisposedException(typeof(T).ToString());
+
+ int newCount = checked(oldCount + 1);
+
+ if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount)
+ return true;
+ }
+ }
+
+ public void Release(T obj)
+ {
+ while (true)
+ {
+ int oldCount = Volatile.Read(ref _count);
+ if (oldCount > 0)
+ {
+ // We haven't been disposed. Decrement _count.
+ int newCount = oldCount - 1;
+ if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount)
+ {
+ if (newCount == 0)
+ obj.OnFinalRelease(disposed: false);
+ return;
+ }
+ }
+ else
+ {
+ Debug.Assert(oldCount != 0 && oldCount != -1);
+
+ // We've been disposed. Increment _count.
+ int newCount = oldCount + 1;
+ if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount)
+ {
+ if (newCount == -1)
+ obj.OnFinalRelease(disposed: true);
+ return;
+ }
+ }
+ }
+ }
+
+ public void Dispose(T obj)
+ {
+ while (true)
+ {
+ int oldCount = Volatile.Read(ref _count);
+ if (oldCount < 0)
+ return; // already disposed
+
+ int newCount = -1 - oldCount;
+ if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount)
+ {
+ if (newCount == -1)
+ obj.OnFinalRelease(disposed: true);
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/EventResetMode.cs b/src/System.Private.CoreLib/shared/System/Threading/EventResetMode.cs
new file mode 100644
index 0000000000..7aac0f51eb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/EventResetMode.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+** Enum: EventResetMode
+**
+**
+** Purpose: Enum to determine the Event type to create
+**
+**
+=============================================================================*/
+
+namespace System.Threading
+{
+ public enum EventResetMode
+ {
+ AutoReset = 0,
+ ManualReset = 1
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs b/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs
new file mode 100644
index 0000000000..975ed02d42
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs
@@ -0,0 +1,460 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Capture execution context for a thread
+**
+**
+===========================================================*/
+
+using System.Diagnostics;
+using System.Runtime.ExceptionServices;
+using System.Runtime.Serialization;
+
+using Thread = Internal.Runtime.Augments.RuntimeThread;
+
+namespace System.Threading
+{
+ public delegate void ContextCallback(Object state);
+
+ public sealed class ExecutionContext : IDisposable, ISerializable
+ {
+ internal static readonly ExecutionContext Default = new ExecutionContext(isDefault: true);
+ internal static readonly ExecutionContext DefaultFlowSuppressed = new ExecutionContext(AsyncLocalValueMap.Empty, Array.Empty<IAsyncLocal>(), isFlowSuppressed: true);
+
+ private readonly IAsyncLocalValueMap m_localValues;
+ private readonly IAsyncLocal[] m_localChangeNotifications;
+ private readonly bool m_isFlowSuppressed;
+ private readonly bool m_isDefault;
+
+ private ExecutionContext(bool isDefault)
+ {
+ m_isDefault = isDefault;
+ }
+
+ private ExecutionContext(
+ IAsyncLocalValueMap localValues,
+ IAsyncLocal[] localChangeNotifications,
+ bool isFlowSuppressed)
+ {
+ m_localValues = localValues;
+ m_localChangeNotifications = localChangeNotifications;
+ m_isFlowSuppressed = isFlowSuppressed;
+ }
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ public static ExecutionContext Capture()
+ {
+ ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
+ return
+ executionContext == null ? Default :
+ executionContext.m_isFlowSuppressed ? null :
+ executionContext;
+ }
+
+ private ExecutionContext ShallowClone(bool isFlowSuppressed)
+ {
+ Debug.Assert(isFlowSuppressed != m_isFlowSuppressed);
+
+ if (m_localValues == null || AsyncLocalValueMap.IsEmpty(m_localValues))
+ {
+ return isFlowSuppressed ?
+ DefaultFlowSuppressed :
+ null; // implies the default context
+ }
+
+ return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed);
+ }
+
+ public static AsyncFlowControl SuppressFlow()
+ {
+ Thread currentThread = Thread.CurrentThread;
+ ExecutionContext executionContext = currentThread.ExecutionContext ?? Default;
+ if (executionContext.m_isFlowSuppressed)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_CannotSupressFlowMultipleTimes);
+ }
+
+ executionContext = executionContext.ShallowClone(isFlowSuppressed: true);
+ var asyncFlowControl = new AsyncFlowControl();
+ currentThread.ExecutionContext = executionContext;
+ asyncFlowControl.Initialize(currentThread);
+ return asyncFlowControl;
+ }
+
+ public static void RestoreFlow()
+ {
+ Thread currentThread = Thread.CurrentThread;
+ ExecutionContext executionContext = currentThread.ExecutionContext;
+ if (executionContext == null || !executionContext.m_isFlowSuppressed)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_CannotRestoreUnsupressedFlow);
+ }
+
+ currentThread.ExecutionContext = executionContext.ShallowClone(isFlowSuppressed: false);
+ }
+
+ public static bool IsFlowSuppressed()
+ {
+ ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
+ return executionContext != null && executionContext.m_isFlowSuppressed;
+ }
+
+ internal bool HasChangeNotifications => m_localChangeNotifications != null;
+
+ internal bool IsDefault => m_isDefault;
+
+ public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state)
+ {
+ // Note: ExecutionContext.Run is an extremely hot function and used by every await, ThreadPool execution, etc.
+ if (executionContext == null)
+ {
+ ThrowNullContext();
+ }
+
+ RunInternal(executionContext, callback, state);
+ }
+
+ internal static void RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
+ {
+ // Note: ExecutionContext.RunInternal is an extremely hot function and used by every await, ThreadPool execution, etc.
+ // Note: Manual enregistering may be addressed by "Exception Handling Write Through Optimization"
+ // https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/eh-writethru.md
+
+ // Enregister variables with 0 post-fix so they can be used in registers without EH forcing them to stack
+ // Capture references to Thread Contexts
+ Thread currentThread0 = Thread.CurrentThread;
+ Thread currentThread = currentThread0;
+ ExecutionContext previousExecutionCtx0 = currentThread0.ExecutionContext;
+
+ // Store current ExecutionContext and SynchronizationContext as "previousXxx".
+ // This allows us to restore them and undo any Context changes made in callback.Invoke
+ // so that they won't "leak" back into caller.
+ // These variables will cross EH so be forced to stack
+ ExecutionContext previousExecutionCtx = previousExecutionCtx0;
+ SynchronizationContext previousSyncCtx = currentThread0.SynchronizationContext;
+
+ if (executionContext != null && executionContext.m_isDefault)
+ {
+ // Default is a null ExecutionContext internally
+ executionContext = null;
+ }
+
+ if (previousExecutionCtx0 != executionContext)
+ {
+ // Restore changed ExecutionContext
+ currentThread0.ExecutionContext = executionContext;
+ if ((executionContext != null && executionContext.HasChangeNotifications) ||
+ (previousExecutionCtx0 != null && previousExecutionCtx0.HasChangeNotifications))
+ {
+ // There are change notifications; trigger any affected
+ OnValuesChanged(previousExecutionCtx0, executionContext);
+ }
+ }
+
+ ExceptionDispatchInfo edi = null;
+ try
+ {
+ callback.Invoke(state);
+ }
+ catch (Exception ex)
+ {
+ // Note: we have a "catch" rather than a "finally" because we want
+ // to stop the first pass of EH here. That way we can restore the previous
+ // context before any of our callers' EH filters run.
+ edi = ExceptionDispatchInfo.Capture(ex);
+ }
+
+ // Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack
+ SynchronizationContext previousSyncCtx1 = previousSyncCtx;
+ Thread currentThread1 = currentThread;
+ // The common case is that these have not changed, so avoid the cost of a write barrier if not needed.
+ if (currentThread1.SynchronizationContext != previousSyncCtx1)
+ {
+ // Restore changed SynchronizationContext back to previous
+ currentThread1.SynchronizationContext = previousSyncCtx1;
+ }
+
+ ExecutionContext previousExecutionCtx1 = previousExecutionCtx;
+ ExecutionContext currentExecutionCtx1 = currentThread1.ExecutionContext;
+ if (currentExecutionCtx1 != previousExecutionCtx1)
+ {
+ // Restore changed ExecutionContext back to previous
+ currentThread1.ExecutionContext = previousExecutionCtx1;
+ if ((currentExecutionCtx1 != null && currentExecutionCtx1.HasChangeNotifications) ||
+ (previousExecutionCtx1 != null && previousExecutionCtx1.HasChangeNotifications))
+ {
+ // There are change notifications; trigger any affected
+ OnValuesChanged(currentExecutionCtx1, previousExecutionCtx1);
+ }
+ }
+
+ // If exception was thrown by callback, rethrow it now original contexts are restored
+ edi?.Throw();
+ }
+
+ internal static void OnValuesChanged(ExecutionContext previousExecutionCtx, ExecutionContext nextExecutionCtx)
+ {
+ Debug.Assert(previousExecutionCtx != nextExecutionCtx);
+
+ // Collect Change Notifications
+ IAsyncLocal[] previousChangeNotifications = previousExecutionCtx?.m_localChangeNotifications;
+ IAsyncLocal[] nextChangeNotifications = nextExecutionCtx?.m_localChangeNotifications;
+
+ // At least one side must have notifications
+ Debug.Assert(previousChangeNotifications != null || nextChangeNotifications != null);
+
+ // Fire Change Notifications
+ try
+ {
+ if (previousChangeNotifications != null && nextChangeNotifications != null)
+ {
+ // Notifications can't exist without values
+ Debug.Assert(previousExecutionCtx.m_localValues != null);
+ Debug.Assert(nextExecutionCtx.m_localValues != null);
+ // Both contexts have change notifications, check previousExecutionCtx first
+ foreach (IAsyncLocal local in previousChangeNotifications)
+ {
+ previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue);
+ nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);
+
+ if (previousValue != currentValue)
+ {
+ local.OnValueChanged(previousValue, currentValue, contextChanged: true);
+ }
+ }
+
+ if (nextChangeNotifications != previousChangeNotifications)
+ {
+ // Check for additional notifications in nextExecutionCtx
+ foreach (IAsyncLocal local in nextChangeNotifications)
+ {
+ // If the local has a value in the previous context, we already fired the event
+ // for that local in the code above.
+ if (!previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue))
+ {
+ nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);
+ if (previousValue != currentValue)
+ {
+ local.OnValueChanged(previousValue, currentValue, contextChanged: true);
+ }
+ }
+ }
+ }
+ }
+ else if (previousChangeNotifications != null)
+ {
+ // Notifications can't exist without values
+ Debug.Assert(previousExecutionCtx.m_localValues != null);
+ // No current values, so just check previous against null
+ foreach (IAsyncLocal local in previousChangeNotifications)
+ {
+ previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue);
+ if (previousValue != null)
+ {
+ local.OnValueChanged(previousValue, null, contextChanged: true);
+ }
+ }
+ }
+ else // Implied: nextChangeNotifications != null
+ {
+ // Notifications can't exist without values
+ Debug.Assert(nextExecutionCtx.m_localValues != null);
+ // No previous values, so just check current against null
+ foreach (IAsyncLocal local in nextChangeNotifications)
+ {
+ nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);
+ if (currentValue != null)
+ {
+ local.OnValueChanged(null, currentValue, contextChanged: true);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Environment.FailFast(
+ SR.ExecutionContext_ExceptionInAsyncLocalNotification,
+ ex);
+ }
+ }
+
+ [StackTraceHidden]
+ private static void ThrowNullContext()
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_NullContext);
+ }
+
+ internal static object GetLocalValue(IAsyncLocal local)
+ {
+ ExecutionContext current = Thread.CurrentThread.ExecutionContext;
+ if (current == null)
+ {
+ return null;
+ }
+
+ current.m_localValues.TryGetValue(local, out object value);
+ return value;
+ }
+
+ internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications)
+ {
+ ExecutionContext current = Thread.CurrentThread.ExecutionContext;
+
+ object previousValue = null;
+ bool hadPreviousValue = false;
+ if (current != null)
+ {
+ hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
+ }
+
+ if (previousValue == newValue)
+ {
+ return;
+ }
+
+ // Regarding 'treatNullValueAsNonexistent: !needChangeNotifications' below:
+ // - When change notifications are not necessary for this IAsyncLocal, there is no observable difference between
+ // storing a null value and removing the IAsyncLocal from 'm_localValues'
+ // - When change notifications are necessary for this IAsyncLocal, the IAsyncLocal's absence in 'm_localValues'
+ // indicates that this is the first value change for the IAsyncLocal and it needs to be registered for change
+ // notifications. So in this case, a null value must be stored in 'm_localValues' to indicate that the IAsyncLocal
+ // is already registered for change notifications.
+ IAsyncLocal[] newChangeNotifications = null;
+ IAsyncLocalValueMap newValues;
+ bool isFlowSuppressed = false;
+ if (current != null)
+ {
+ isFlowSuppressed = current.m_isFlowSuppressed;
+ newValues = current.m_localValues.Set(local, newValue, treatNullValueAsNonexistent: !needChangeNotifications);
+ newChangeNotifications = current.m_localChangeNotifications;
+ }
+ else
+ {
+ // First AsyncLocal
+ newValues = AsyncLocalValueMap.Create(local, newValue, treatNullValueAsNonexistent: !needChangeNotifications);
+ }
+
+ //
+ // Either copy the change notification array, or create a new one, depending on whether we need to add a new item.
+ //
+ if (needChangeNotifications)
+ {
+ if (hadPreviousValue)
+ {
+ Debug.Assert(newChangeNotifications != null);
+ Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
+ }
+ else if (newChangeNotifications == null)
+ {
+ newChangeNotifications = new IAsyncLocal[1] { local };
+ }
+ else
+ {
+ int newNotificationIndex = newChangeNotifications.Length;
+ Array.Resize(ref newChangeNotifications, newNotificationIndex + 1);
+ newChangeNotifications[newNotificationIndex] = local;
+ }
+ }
+
+ Thread.CurrentThread.ExecutionContext =
+ (!isFlowSuppressed && AsyncLocalValueMap.IsEmpty(newValues)) ?
+ null : // No values, return to Default context
+ new ExecutionContext(newValues, newChangeNotifications, isFlowSuppressed);
+
+ if (needChangeNotifications)
+ {
+ local.OnValueChanged(previousValue, newValue, contextChanged: false);
+ }
+ }
+
+ public ExecutionContext CreateCopy()
+ {
+ return this; // since CoreCLR's ExecutionContext is immutable, we don't need to create copies.
+ }
+
+ public void Dispose()
+ {
+ // For CLR compat only
+ }
+ }
+
+ public struct AsyncFlowControl : IDisposable
+ {
+ private Thread _thread;
+
+ internal void Initialize(Thread currentThread)
+ {
+ Debug.Assert(currentThread == Thread.CurrentThread);
+ _thread = currentThread;
+ }
+
+ public void Undo()
+ {
+ if (_thread == null)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCMultiple);
+ }
+ if (Thread.CurrentThread != _thread)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCOtherThread);
+ }
+
+ // An async flow control cannot be undone when a different execution context is applied. The desktop framework
+ // mutates the execution context when its state changes, and only changes the instance when an execution context
+ // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution
+ // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution
+ // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async
+ // local's value, the desktop framework verifies that a different execution context has not been applied by
+ // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core,
+ // since the execution context instance will change after changing the async local's value, it verifies that a
+ // different execution context has not been applied, by instead ensuring that the current execution context's
+ // flow is suppressed.
+ if (!ExecutionContext.IsFlowSuppressed())
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_AsyncFlowCtrlCtxMismatch);
+ }
+
+ _thread = null;
+ ExecutionContext.RestoreFlow();
+ }
+
+ public void Dispose()
+ {
+ Undo();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is AsyncFlowControl && Equals((AsyncFlowControl)obj);
+ }
+
+ public bool Equals(AsyncFlowControl obj)
+ {
+ return _thread == obj._thread;
+ }
+
+ public override int GetHashCode()
+ {
+ return _thread?.GetHashCode() ?? 0;
+ }
+
+ public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b)
+ {
+ return !(a == b);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/LazyInitializer.cs b/src/System.Private.CoreLib/shared/System/Threading/LazyInitializer.cs
new file mode 100644
index 0000000000..f422ab9172
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/LazyInitializer.cs
@@ -0,0 +1,288 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// a set of lightweight static helpers for lazy initialization.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Diagnostics;
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Provides lazy initialization routines.
+ /// </summary>
+ /// <remarks>
+ /// These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using
+ /// references to ensure targets have been initialized as they are accessed.
+ /// </remarks>
+ public static class LazyInitializer
+ {
+ /// <summary>
+ /// Initializes a target reference type with the type's default constructor if the target has not
+ /// already been initialized.
+ /// </summary>
+ /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam>
+ /// <param name="target">A reference of type <typeparamref name="T"/> to initialize if it has not
+ /// already been initialized.</param>
+ /// <returns>The initialized reference of type <typeparamref name="T"/>.</returns>
+ /// <exception cref="T:System.MissingMemberException">Type <typeparamref name="T"/> does not have a default
+ /// constructor.</exception>
+ /// <exception cref="T:System.MemberAccessException">
+ /// Permissions to access the constructor of type <typeparamref name="T"/> were missing.
+ /// </exception>
+ /// <remarks>
+ /// <para>
+ /// This method may only be used on reference types. To ensure initialization of value
+ /// types, see other overloads of EnsureInitialized.
+ /// </para>
+ /// <para>
+ /// This method may be used concurrently by multiple threads to initialize <paramref name="target"/>.
+ /// In the event that multiple threads access this method concurrently, multiple instances of <typeparamref name="T"/>
+ /// may be created, but only one will be stored into <paramref name="target"/>. In such an occurrence, this method will not dispose of the
+ /// objects that were not stored. If such objects must be disposed, it is up to the caller to determine
+ /// if an object was not used and to then dispose of the object appropriately.
+ /// </para>
+ /// </remarks>
+ public static T EnsureInitialized<T>(ref T target) where T : class =>
+ Volatile.Read(ref target) ?? EnsureInitializedCore(ref target);
+
+ /// <summary>
+ /// Initializes a target reference type with the type's default constructor (slow path)
+ /// </summary>
+ /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam>
+ /// <param name="target">The variable that need to be initialized</param>
+ /// <returns>The initialized variable</returns>
+ private static T EnsureInitializedCore<T>(ref T target) where T : class
+ {
+ try
+ {
+ Interlocked.CompareExchange(ref target, Activator.CreateInstance<T>(), null);
+ }
+ catch (MissingMethodException)
+ {
+ throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT);
+ }
+
+ Debug.Assert(target != null);
+ return target;
+ }
+
+ /// <summary>
+ /// Initializes a target reference type using the specified function if it has not already been
+ /// initialized.
+ /// </summary>
+ /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam>
+ /// <param name="target">The reference of type <typeparamref name="T"/> to initialize if it has not
+ /// already been initialized.</param>
+ /// <param name="valueFactory">The <see cref="T:System.Func{T}"/> invoked to initialize the
+ /// reference.</param>
+ /// <returns>The initialized reference of type <typeparamref name="T"/>.</returns>
+ /// <exception cref="T:System.MissingMemberException">Type <typeparamref name="T"/> does not have a
+ /// default constructor.</exception>
+ /// <exception cref="T:System.InvalidOperationException"><paramref name="valueFactory"/> returned
+ /// null.</exception>
+ /// <remarks>
+ /// <para>
+ /// This method may only be used on reference types, and <paramref name="valueFactory"/> may
+ /// not return a null reference (Nothing in Visual Basic). To ensure initialization of value types or
+ /// to allow null reference types, see other overloads of EnsureInitialized.
+ /// </para>
+ /// <para>
+ /// This method may be used concurrently by multiple threads to initialize <paramref name="target"/>.
+ /// In the event that multiple threads access this method concurrently, multiple instances of <typeparamref name="T"/>
+ /// may be created, but only one will be stored into <paramref name="target"/>. In such an occurrence, this method will not dispose of the
+ /// objects that were not stored. If such objects must be disposed, it is up to the caller to determine
+ /// if an object was not used and to then dispose of the object appropriately.
+ /// </para>
+ /// </remarks>
+ public static T EnsureInitialized<T>(ref T target, Func<T> valueFactory) where T : class =>
+ Volatile.Read(ref target) ?? EnsureInitializedCore(ref target, valueFactory);
+
+ /// <summary>
+ /// Initialize the target using the given delegate (slow path).
+ /// </summary>
+ /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam>
+ /// <param name="target">The variable that need to be initialized</param>
+ /// <param name="valueFactory">The delegate that will be executed to initialize the target</param>
+ /// <returns>The initialized variable</returns>
+ private static T EnsureInitializedCore<T>(ref T target, Func<T> valueFactory) where T : class
+ {
+ T value = valueFactory();
+ if (value == null)
+ {
+ throw new InvalidOperationException(SR.Lazy_StaticInit_InvalidOperation);
+ }
+
+ Interlocked.CompareExchange(ref target, value, null);
+ Debug.Assert(target != null);
+ return target;
+ }
+
+ /// <summary>
+ /// Initializes a target reference or value type with its default constructor if it has not already
+ /// been initialized.
+ /// </summary>
+ /// <typeparam name="T">The type of the reference to be initialized.</typeparam>
+ /// <param name="target">A reference or value of type <typeparamref name="T"/> to initialize if it
+ /// has not already been initialized.</param>
+ /// <param name="initialized">A reference to a boolean that determines whether the target has already
+ /// been initialized.</param>
+ /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing
+ /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param>
+ /// <returns>The initialized value of type <typeparamref name="T"/>.</returns>
+ public static T EnsureInitialized<T>(ref T target, ref bool initialized, ref object syncLock)
+ {
+ // Fast path.
+ if (Volatile.Read(ref initialized))
+ {
+ return target;
+ }
+
+ return EnsureInitializedCore(ref target, ref initialized, ref syncLock);
+ }
+
+ /// <summary>
+ /// Ensure the target is initialized and return the value (slow path). This overload permits nulls
+ /// and also works for value type targets. Uses the type's default constructor to create the value.
+ /// </summary>
+ /// <typeparam name="T">The type of target.</typeparam>
+ /// <param name="target">A reference to the target to be initialized.</param>
+ /// <param name="initialized">A reference to a location tracking whether the target has been initialized.</param>
+ /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
+ /// a new object will be instantiated.</param>
+ /// </param>
+ /// <returns>The initialized object.</returns>
+ private static T EnsureInitializedCore<T>(ref T target, ref bool initialized, ref object syncLock)
+ {
+ // Lazily initialize the lock if necessary and then double check if initialization is still required.
+ lock (EnsureLockInitialized(ref syncLock))
+ {
+ if (!Volatile.Read(ref initialized))
+ {
+ try
+ {
+ target = Activator.CreateInstance<T>();
+ }
+ catch (MissingMethodException)
+ {
+ throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT);
+ }
+
+ Volatile.Write(ref initialized, true);
+ }
+ }
+
+ return target;
+ }
+
+ /// <summary>
+ /// Initializes a target reference or value type with a specified function if it has not already been
+ /// initialized.
+ /// </summary>
+ /// <typeparam name="T">The type of the reference to be initialized.</typeparam>
+ /// <param name="target">A reference or value of type <typeparamref name="T"/> to initialize if it
+ /// has not already been initialized.</param>
+ /// <param name="initialized">A reference to a boolean that determines whether the target has already
+ /// been initialized.</param>
+ /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing
+ /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param>
+ /// <param name="valueFactory">The <see cref="T:System.Func{T}"/> invoked to initialize the
+ /// reference or value.</param>
+ /// <returns>The initialized value of type <typeparamref name="T"/>.</returns>
+ public static T EnsureInitialized<T>(ref T target, ref bool initialized, ref object syncLock, Func<T> valueFactory)
+ {
+ // Fast path.
+ if (Volatile.Read(ref initialized))
+ {
+ return target;
+ }
+
+ return EnsureInitializedCore(ref target, ref initialized, ref syncLock, valueFactory);
+ }
+
+ /// <summary>
+ /// Ensure the target is initialized and return the value (slow path). This overload permits nulls
+ /// and also works for value type targets. Uses the supplied function to create the value.
+ /// </summary>
+ /// <typeparam name="T">The type of target.</typeparam>
+ /// <param name="target">A reference to the target to be initialized.</param>
+ /// <param name="initialized">A reference to a location tracking whether the target has been initialized.</param>
+ /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
+ /// a new object will be instantiated.</param>
+ /// <param name="valueFactory">
+ /// The <see cref="T:System.Func{T}"/> to invoke in order to produce the lazily-initialized value.
+ /// </param>
+ /// <returns>The initialized object.</returns>
+ private static T EnsureInitializedCore<T>(ref T target, ref bool initialized, ref object syncLock, Func<T> valueFactory)
+ {
+ // Lazily initialize the lock if necessary and then double check if initialization is still required.
+ lock (EnsureLockInitialized(ref syncLock))
+ {
+ if (!Volatile.Read(ref initialized))
+ {
+ target = valueFactory();
+ Volatile.Write(ref initialized, true);
+ }
+ }
+
+ return target;
+ }
+
+ /// <summary>
+ /// Initializes a target reference type with a specified function if it has not already been initialized.
+ /// </summary>
+ /// <typeparam name="T">The type of the reference to be initialized. Has to be reference type.</typeparam>
+ /// <param name="target">A reference of type <typeparamref name="T"/> to initialize if it has not already been initialized.</param>
+ /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing
+ /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param>
+ /// <param name="valueFactory">The <see cref="T:System.Func{T}"/> invoked to initialize the reference.</param>
+ /// <returns>The initialized value of type <typeparamref name="T"/>.</returns>
+ public static T EnsureInitialized<T>(ref T target, ref object syncLock, Func<T> valueFactory) where T : class =>
+ Volatile.Read(ref target) ?? EnsureInitializedCore(ref target, ref syncLock, valueFactory);
+
+ /// <summary>
+ /// Ensure the target is initialized and return the value (slow path). This overload works only for reference type targets.
+ /// Uses the supplied function to create the value.
+ /// </summary>
+ /// <typeparam name="T">The type of target. Has to be reference type.</typeparam>
+ /// <param name="target">A reference to the target to be initialized.</param>
+ /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
+ /// a new object will be instantiated.</param>
+ /// <param name="valueFactory">
+ /// The <see cref="T:System.Func{T}"/> to invoke in order to produce the lazily-initialized value.
+ /// </param>
+ /// <returns>The initialized object.</returns>
+ private static T EnsureInitializedCore<T>(ref T target, ref object syncLock, Func<T> valueFactory) where T : class
+ {
+ // Lazily initialize the lock if necessary and then double check if initialization is still required.
+ lock (EnsureLockInitialized(ref syncLock))
+ {
+ if (Volatile.Read(ref target) == null)
+ {
+ Volatile.Write(ref target, valueFactory());
+ if (target == null)
+ {
+ throw new InvalidOperationException(SR.Lazy_StaticInit_InvalidOperation);
+ }
+ }
+ }
+
+ return target;
+ }
+
+ /// <summary>
+ /// Ensure the lock object is initialized.
+ /// </summary>
+ /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
+ /// a new object will be instantiated.</param>
+ /// <returns>Initialized lock object.</returns>
+ private static object EnsureLockInitialized(ref object syncLock) =>
+ syncLock ??
+ Interlocked.CompareExchange(ref syncLock, new object(), null) ??
+ syncLock;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/LazyThreadSafetyMode.cs b/src/System.Private.CoreLib/shared/System/Threading/LazyThreadSafetyMode.cs
new file mode 100644
index 0000000000..2d13f23762
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/LazyThreadSafetyMode.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// a set of lightweight static helpers for lazy initialization.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Specifies how a <see cref="T:System.Threading.Lazy{T}"/> instance should synchronize access among multiple threads.
+ /// </summary>
+ public enum LazyThreadSafetyMode
+ {
+ /// <summary>
+ /// This mode makes no guarantees around the thread-safety of the <see cref="T:System.Threading.Lazy{T}"/> instance. If used from multiple threads, the behavior of the <see cref="T:System.Threading.Lazy{T}"/> is undefined.
+ /// This mode should be used when a <see cref="T:System.Threading.Lazy{T}"/> is guaranteed to never be initialized from more than one thread simultaneously and high performance is crucial.
+ /// If valueFactory throws an exception when the <see cref="T:System.Threading.Lazy{T}"/> is initialized, the exception will be cached and returned on subsequent accesses to Value. Also, if valueFactory recursively
+ /// accesses Value on this <see cref="T:System.Threading.Lazy{T}"/> instance, a <see cref="T:System.InvalidOperationException"/> will be thrown.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// When multiple threads attempt to simultaneously initialize a <see cref="T:System.Threading.Lazy{T}"/> instance, this mode allows each thread to execute the
+ /// valueFactory but only the first thread to complete initialization will be allowed to set the final value of the <see cref="T:System.Threading.Lazy{T}"/>.
+ /// Once initialized successfully, any future calls to Value will return the cached result. If valueFactory throws an exception on any thread, that exception will be
+ /// propagated out of Value. If any thread executes valueFactory without throwing an exception and, therefore, successfully sets the value, that value will be returned on
+ /// subsequent accesses to Value from any thread. If no thread succeeds in setting the value, IsValueCreated will remain false and subsequent accesses to Value will result in
+ /// the valueFactory delegate re-executing. Also, if valueFactory recursively accesses Value on this <see cref="T:System.Threading.Lazy{T}"/> instance, an exception will NOT be thrown.
+ /// </summary>
+ PublicationOnly,
+
+ /// <summary>
+ /// This mode uses locks to ensure that only a single thread can initialize a <see cref="T:System.Threading.Lazy{T}"/> instance in a thread-safe manner. In general,
+ /// taken if this mode is used in conjunction with a <see cref="T:System.Threading.Lazy{T}"/> valueFactory delegate that uses locks internally, a deadlock can occur if not
+ /// handled carefully. If valueFactory throws an exception when the<see cref="T:System.Threading.Lazy{T}"/> is initialized, the exception will be cached and returned on
+ /// subsequent accesses to Value. Also, if valueFactory recursively accesses Value on this <see cref="T:System.Threading.Lazy{T}"/> instance, a <see cref="T:System.InvalidOperationException"/> will be thrown.
+ /// </summary>
+ ExecutionAndPublication
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/LockRecursionException.cs b/src/System.Private.CoreLib/shared/System/Threading/LockRecursionException.cs
new file mode 100644
index 0000000000..c76c7a5040
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/LockRecursionException.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class LockRecursionException : System.Exception
+ {
+ public LockRecursionException()
+ {
+ }
+
+ public LockRecursionException(string message)
+ : base(message)
+ {
+ }
+
+ public LockRecursionException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ protected LockRecursionException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ManualResetEvent.cs b/src/System.Private.CoreLib/shared/System/Threading/ManualResetEvent.cs
new file mode 100644
index 0000000000..4b8d61f960
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ManualResetEvent.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Threading
+{
+ public sealed class ManualResetEvent : EventWaitHandle
+ {
+ public ManualResetEvent(bool initialState) : base(initialState, EventResetMode.ManualReset) { }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ParameterizedThreadStart.cs b/src/System.Private.CoreLib/shared/System/Threading/ParameterizedThreadStart.cs
new file mode 100644
index 0000000000..c0f29e8e80
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ParameterizedThreadStart.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: This class is a Delegate which defines the start method
+** for starting a thread. That method must match this delegate.
+**
+**
+=============================================================================*/
+
+namespace System.Threading
+{
+ public delegate void ParameterizedThreadStart(object obj);
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ReaderWriterLockSlim.cs b/src/System.Private.CoreLib/shared/System/Threading/ReaderWriterLockSlim.cs
new file mode 100644
index 0000000000..45175488e7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ReaderWriterLockSlim.cs
@@ -0,0 +1,1682 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Internal.Runtime.Augments;
+using System.Diagnostics; // for TraceInformation
+using System.Runtime.CompilerServices;
+
+namespace System.Threading
+{
+ public enum LockRecursionPolicy
+ {
+ NoRecursion = 0,
+ SupportsRecursion = 1,
+ }
+
+ //
+ // ReaderWriterCount tracks how many of each kind of lock is held by each thread.
+ // We keep a linked list for each thread, attached to a ThreadStatic field.
+ // These are reused wherever possible, so that a given thread will only
+ // allocate N of these, where N is the maximum number of locks held simultaneously
+ // by that thread.
+ //
+ internal class ReaderWriterCount
+ {
+ // Which lock does this object belong to? This is a numeric ID for two reasons:
+ // 1) We don't want this field to keep the lock object alive, and a WeakReference would
+ // be too expensive.
+ // 2) Setting the value of a long is faster than setting the value of a reference.
+ // The "hot" paths in ReaderWriterLockSlim are short enough that this actually
+ // matters.
+ public long lockID;
+
+ // How many reader locks does this thread hold on this ReaderWriterLockSlim instance?
+ public int readercount;
+
+ // Ditto for writer/upgrader counts. These are only used if the lock allows recursion.
+ // But we have to have the fields on every ReaderWriterCount instance, because
+ // we reuse it for different locks.
+ public int writercount;
+ public int upgradecount;
+
+ // Next RWC in this thread's list.
+ public ReaderWriterCount next;
+ }
+
+ /// <summary>
+ /// A reader-writer lock implementation that is intended to be simple, yet very
+ /// efficient. In particular only 1 interlocked operation is taken for any lock
+ /// operation (we use spin locks to achieve this). The spin lock is never held
+ /// for more than a few instructions (in particular, we never call event APIs
+ /// or in fact any non-trivial API while holding the spin lock).
+ /// </summary>
+ public class ReaderWriterLockSlim : IDisposable
+ {
+ private static readonly int ProcessorCount = Environment.ProcessorCount;
+
+ //Specifying if the lock can be reacquired recursively.
+ private readonly bool _fIsReentrant;
+
+ // Lock specification for _spinLock: This lock protects exactly the local fields associated with this
+ // instance of ReaderWriterLockSlim. It does NOT protect the memory associated with
+ // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
+ SpinLock _spinLock;
+
+ // These variables allow use to avoid Setting events (which is expensive) if we don't have to.
+ private uint _numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
+ private uint _numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
+ private uint _numWriteUpgradeWaiters; // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1).
+ private uint _numUpgradeWaiters;
+
+ private WaiterStates _waiterStates;
+
+ private int _upgradeLockOwnerId;
+ private int _writeLockOwnerId;
+
+ // conditions we wait on.
+ private EventWaitHandle _writeEvent; // threads waiting to acquire a write lock go here.
+ private EventWaitHandle _readEvent; // threads waiting to acquire a read lock go here (will be released in bulk)
+ private EventWaitHandle _upgradeEvent; // thread waiting to acquire the upgrade lock
+ private EventWaitHandle _waitUpgradeEvent; // thread waiting to upgrade from the upgrade lock to a write lock go here (at most one)
+
+ // Every lock instance has a unique ID, which is used by ReaderWriterCount to associate itself with the lock
+ // without holding a reference to it.
+ private static long s_nextLockID;
+ private long _lockID;
+
+ // See comments on ReaderWriterCount.
+ [ThreadStatic]
+ private static ReaderWriterCount t_rwc;
+
+ private bool _fUpgradeThreadHoldingRead;
+
+ private const int MaxSpinCount = 20;
+
+ //The uint, that contains info like if the writer lock is held, num of
+ //readers etc.
+ private uint _owners;
+
+ //Various R/W masks
+ //Note:
+ //The Uint is divided as follows:
+ //
+ //Writer-Owned Waiting-Writers Waiting Upgraders Num-Readers
+ // 31 30 29 28.......0
+ //
+ //Dividing the uint, allows to vastly simplify logic for checking if a
+ //reader should go in etc. Setting the writer bit will automatically
+ //make the value of the uint much larger than the max num of readers
+ //allowed, thus causing the check for max_readers to fail.
+
+ private const uint WRITER_HELD = 0x80000000;
+ private const uint WAITING_WRITERS = 0x40000000;
+ private const uint WAITING_UPGRADER = 0x20000000;
+
+ //The max readers is actually one less then its theoretical max.
+ //This is done in order to prevent reader count overflows. If the reader
+ //count reaches max, other readers will wait.
+ private const uint MAX_READER = 0x10000000 - 2;
+
+ private const uint READER_MASK = 0x10000000 - 1;
+
+ private bool _fDisposed;
+
+ private void InitializeThreadCounts()
+ {
+ _upgradeLockOwnerId = -1;
+ _writeLockOwnerId = -1;
+ }
+
+ public ReaderWriterLockSlim()
+ : this(LockRecursionPolicy.NoRecursion)
+ {
+ }
+
+ public ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy)
+ {
+ if (recursionPolicy == LockRecursionPolicy.SupportsRecursion)
+ {
+ _fIsReentrant = true;
+ }
+ InitializeThreadCounts();
+ _waiterStates = WaiterStates.NoWaiters;
+ _lockID = Interlocked.Increment(ref s_nextLockID);
+ }
+
+ private bool HasNoWaiters
+ {
+ get
+ {
+#if DEBUG
+ Debug.Assert(_spinLock.IsHeld);
+#endif
+
+ return (_waiterStates & WaiterStates.NoWaiters) != WaiterStates.None;
+ }
+ set
+ {
+#if DEBUG
+ Debug.Assert(_spinLock.IsHeld);
+#endif
+
+ if (value)
+ {
+ _waiterStates |= WaiterStates.NoWaiters;
+ }
+ else
+ {
+ _waiterStates &= ~WaiterStates.NoWaiters;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsRWEntryEmpty(ReaderWriterCount rwc)
+ {
+ if (rwc.lockID == 0)
+ return true;
+ else if (rwc.readercount == 0 && rwc.writercount == 0 && rwc.upgradecount == 0)
+ return true;
+ else
+ return false;
+ }
+
+ private bool IsRwHashEntryChanged(ReaderWriterCount lrwc)
+ {
+ return lrwc.lockID != _lockID;
+ }
+
+ /// <summary>
+ /// This routine retrieves/sets the per-thread counts needed to enforce the
+ /// various rules related to acquiring the lock.
+ ///
+ /// DontAllocate is set to true if the caller just wants to get an existing
+ /// entry for this thread, but doesn't want to add one if an existing one
+ /// could not be found.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ReaderWriterCount GetThreadRWCount(bool dontAllocate)
+ {
+ ReaderWriterCount rwc = t_rwc;
+ ReaderWriterCount empty = null;
+ while (rwc != null)
+ {
+ if (rwc.lockID == _lockID)
+ return rwc;
+
+ if (!dontAllocate && empty == null && IsRWEntryEmpty(rwc))
+ empty = rwc;
+
+ rwc = rwc.next;
+ }
+
+ if (dontAllocate)
+ return null;
+
+ if (empty == null)
+ {
+ empty = new ReaderWriterCount();
+ empty.next = t_rwc;
+ t_rwc = empty;
+ }
+
+ empty.lockID = _lockID;
+ return empty;
+ }
+
+ public void EnterReadLock()
+ {
+ TryEnterReadLock(-1);
+ }
+
+ //
+ // Common timeout support
+ //
+ private struct TimeoutTracker
+ {
+ private int _total;
+ private int _start;
+
+ public TimeoutTracker(TimeSpan timeout)
+ {
+ long ltm = (long)timeout.TotalMilliseconds;
+ if (ltm < -1 || ltm > (long)Int32.MaxValue)
+ throw new ArgumentOutOfRangeException(nameof(timeout));
+ _total = (int)ltm;
+ if (_total != -1 && _total != 0)
+ _start = Environment.TickCount;
+ else
+ _start = 0;
+ }
+
+ public TimeoutTracker(int millisecondsTimeout)
+ {
+ if (millisecondsTimeout < -1)
+ throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));
+ _total = millisecondsTimeout;
+ if (_total != -1 && _total != 0)
+ _start = Environment.TickCount;
+ else
+ _start = 0;
+ }
+
+ public int RemainingMilliseconds
+ {
+ get
+ {
+ if (_total == -1 || _total == 0)
+ return _total;
+
+ int elapsed = Environment.TickCount - _start;
+ // elapsed may be negative if TickCount has overflowed by 2^31 milliseconds.
+ if (elapsed < 0 || elapsed >= _total)
+ return 0;
+
+ return _total - elapsed;
+ }
+ }
+
+ public bool IsExpired
+ {
+ get
+ {
+ return RemainingMilliseconds == 0;
+ }
+ }
+ }
+
+ public bool TryEnterReadLock(TimeSpan timeout)
+ {
+ return TryEnterReadLock(new TimeoutTracker(timeout));
+ }
+
+ public bool TryEnterReadLock(int millisecondsTimeout)
+ {
+ return TryEnterReadLock(new TimeoutTracker(millisecondsTimeout));
+ }
+
+ private bool TryEnterReadLock(TimeoutTracker timeout)
+ {
+ return TryEnterReadLockCore(timeout);
+ }
+
+ private bool TryEnterReadLockCore(TimeoutTracker timeout)
+ {
+ if (_fDisposed)
+ throw new ObjectDisposedException(null);
+
+ ReaderWriterCount lrwc = null;
+ int id = Environment.CurrentManagedThreadId;
+
+ if (!_fIsReentrant)
+ {
+ if (id == _writeLockOwnerId)
+ {
+ //Check for AW->AR
+ throw new LockRecursionException(SR.LockRecursionException_ReadAfterWriteNotAllowed);
+ }
+
+ _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
+
+ lrwc = GetThreadRWCount(false);
+
+ //Check if the reader lock is already acquired. Note, we could
+ //check the presence of a reader by not allocating rwc (But that
+ //would lead to two lookups in the common case. It's better to keep
+ //a count in the structure).
+ if (lrwc.readercount > 0)
+ {
+ _spinLock.Exit();
+ throw new LockRecursionException(SR.LockRecursionException_RecursiveReadNotAllowed);
+ }
+ else if (id == _upgradeLockOwnerId)
+ {
+ //The upgrade lock is already held.
+ //Update the global read counts and exit.
+
+ lrwc.readercount++;
+ _owners++;
+ _spinLock.Exit();
+ return true;
+ }
+ }
+ else
+ {
+ _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
+ lrwc = GetThreadRWCount(false);
+ if (lrwc.readercount > 0)
+ {
+ lrwc.readercount++;
+ _spinLock.Exit();
+ return true;
+ }
+ else if (id == _upgradeLockOwnerId)
+ {
+ //The upgrade lock is already held.
+ //Update the global read counts and exit.
+ lrwc.readercount++;
+ _owners++;
+ _spinLock.Exit();
+ _fUpgradeThreadHoldingRead = true;
+ return true;
+ }
+ else if (id == _writeLockOwnerId)
+ {
+ //The write lock is already held.
+ //Update global read counts here,
+ lrwc.readercount++;
+ _owners++;
+ _spinLock.Exit();
+ return true;
+ }
+ }
+
+ bool retVal = true;
+ int spinCount = 0;
+
+ for (; ;)
+ {
+ // We can enter a read lock if there are only read-locks have been given out
+ // and a writer is not trying to get in.
+
+ if (_owners < MAX_READER)
+ {
+ // Good case, there is no contention, we are basically done
+ _owners++; // Indicate we have another reader
+ lrwc.readercount++;
+ break;
+ }
+
+ if (timeout.IsExpired)
+ {
+ _spinLock.Exit();
+ return false;
+ }
+
+ if (spinCount < MaxSpinCount && ShouldSpinForEnterAnyRead())
+ {
+ _spinLock.Exit();
+ spinCount++;
+ SpinWait(spinCount);
+ _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
+ //The per-thread structure may have been recycled as the lock is acquired (due to message pumping), load again.
+ if (IsRwHashEntryChanged(lrwc))
+ lrwc = GetThreadRWCount(false);
+ continue;
+ }
+
+ // Drat, we need to wait. Mark that we have waiters and wait.
+ if (_readEvent == null) // Create the needed event
+ {
+ LazyCreateEvent(ref _readEvent, EnterLockType.Read);
+ if (IsRwHashEntryChanged(lrwc))
+ lrwc = GetThreadRWCount(false);
+ continue; // since we left the lock, start over.
+ }
+
+ retVal = WaitOnEvent(_readEvent, ref _numReadWaiters, timeout, EnterLockType.Read);
+ if (!retVal)
+ {
+ return false;
+ }
+ if (IsRwHashEntryChanged(lrwc))
+ lrwc = GetThreadRWCount(false);
+ }
+
+ _spinLock.Exit();
+ return retVal;
+ }
+
+ public void EnterWriteLock()
+ {
+ TryEnterWriteLock(-1);
+ }
+
+ public bool TryEnterWriteLock(TimeSpan timeout)
+ {
+ return TryEnterWriteLock(new TimeoutTracker(timeout));
+ }
+
+ public bool TryEnterWriteLock(int millisecondsTimeout)
+ {
+ return TryEnterWriteLock(new TimeoutTracker(millisecondsTimeout));
+ }
+
+ private bool TryEnterWriteLock(TimeoutTracker timeout)
+ {
+ return TryEnterWriteLockCore(timeout);
+ }
+
+ private bool TryEnterWriteLockCore(TimeoutTracker timeout)
+ {
+ if (_fDisposed)
+ throw new ObjectDisposedException(null);
+
+ int id = Environment.CurrentManagedThreadId;
+ ReaderWriterCount lrwc;
+ bool upgradingToWrite = false;
+
+ if (!_fIsReentrant)
+ {
+ EnterSpinLockReason enterMyLockReason;
+ if (id == _writeLockOwnerId)
+ {
+ //Check for AW->AW
+ throw new LockRecursionException(SR.LockRecursionException_RecursiveWriteNotAllowed);
+ }
+ else if (id == _upgradeLockOwnerId)
+ {
+ //AU->AW case is allowed once.
+ upgradingToWrite = true;
+ enterMyLockReason = EnterSpinLockReason.UpgradeToWrite;
+ }
+ else
+ {
+ enterMyLockReason = EnterSpinLockReason.EnterWrite;
+ }
+ _spinLock.Enter(enterMyLockReason);
+
+ lrwc = GetThreadRWCount(true);
+
+ //Can't acquire write lock with reader lock held.
+ if (lrwc != null && lrwc.readercount > 0)
+ {
+ _spinLock.Exit();
+ throw new LockRecursionException(SR.LockRecursionException_WriteAfterReadNotAllowed);
+ }
+ }
+ else
+ {
+ EnterSpinLockReason enterMyLockReason;
+ if (id == _writeLockOwnerId)
+ {
+ enterMyLockReason = EnterSpinLockReason.EnterRecursiveWrite;
+ }
+ else if (id == _upgradeLockOwnerId)
+ {
+ enterMyLockReason = EnterSpinLockReason.UpgradeToWrite;
+ }
+ else
+ {
+ enterMyLockReason = EnterSpinLockReason.EnterWrite;
+ }
+ _spinLock.Enter(enterMyLockReason);
+
+ lrwc = GetThreadRWCount(false);
+
+ if (id == _writeLockOwnerId)
+ {
+ lrwc.writercount++;
+ _spinLock.Exit();
+ return true;
+ }
+ else if (id == _upgradeLockOwnerId)
+ {
+ upgradingToWrite = true;
+ }
+ else if (lrwc.readercount > 0)
+ {
+ //Write locks may not be acquired if only read locks have been
+ //acquired.
+ _spinLock.Exit();
+ throw new LockRecursionException(SR.LockRecursionException_WriteAfterReadNotAllowed);
+ }
+ }
+
+ bool retVal = true;
+ int spinCount = 0;
+
+ for (; ;)
+ {
+ if (IsWriterAcquired())
+ {
+ // Good case, there is no contention, we are basically done
+ SetWriterAcquired();
+ break;
+ }
+
+ //Check if there is just one upgrader, and no readers.
+ //Assumption: Only one thread can have the upgrade lock, so the
+ //following check will fail for all other threads that may sneak in
+ //when the upgrading thread is waiting.
+
+ if (upgradingToWrite)
+ {
+ uint readercount = GetNumReaders();
+
+ if (readercount == 1)
+ {
+ //Good case again, there is just one upgrader, and no readers.
+ SetWriterAcquired(); // indicate we have a writer.
+ break;
+ }
+ else if (readercount == 2)
+ {
+ if (lrwc != null)
+ {
+ if (IsRwHashEntryChanged(lrwc))
+ lrwc = GetThreadRWCount(false);
+
+ if (lrwc.readercount > 0)
+ {
+ //This check is needed for EU->ER->EW case, as the owner count will be two.
+ Debug.Assert(_fIsReentrant);
+ Debug.Assert(_fUpgradeThreadHoldingRead);
+
+ //Good case again, there is just one upgrader, and no readers.
+ SetWriterAcquired(); // indicate we have a writer.
+ break;
+ }
+ }
+ }
+ }
+
+ if (timeout.IsExpired)
+ {
+ _spinLock.Exit();
+ return false;
+ }
+
+ if (spinCount < MaxSpinCount && ShouldSpinForEnterAnyWrite(upgradingToWrite))
+ {
+ _spinLock.Exit();
+ spinCount++;
+ SpinWait(spinCount);
+ _spinLock.Enter(upgradingToWrite ? EnterSpinLockReason.UpgradeToWrite : EnterSpinLockReason.EnterWrite);
+ continue;
+ }
+
+ if (upgradingToWrite)
+ {
+ if (_waitUpgradeEvent == null) // Create the needed event
+ {
+ LazyCreateEvent(ref _waitUpgradeEvent, EnterLockType.UpgradeToWrite);
+ continue; // since we left the lock, start over.
+ }
+
+ Debug.Assert(_numWriteUpgradeWaiters == 0, "There can be at most one thread with the upgrade lock held.");
+
+ retVal = WaitOnEvent(_waitUpgradeEvent, ref _numWriteUpgradeWaiters, timeout, EnterLockType.UpgradeToWrite);
+
+ //The lock is not held in case of failure.
+ if (!retVal)
+ return false;
+ }
+ else
+ {
+ // Drat, we need to wait. Mark that we have waiters and wait.
+ if (_writeEvent == null) // create the needed event.
+ {
+ LazyCreateEvent(ref _writeEvent, EnterLockType.Write);
+ continue; // since we left the lock, start over.
+ }
+
+ retVal = WaitOnEvent(_writeEvent, ref _numWriteWaiters, timeout, EnterLockType.Write);
+ //The lock is not held in case of failure.
+ if (!retVal)
+ return false;
+ }
+ }
+
+ Debug.Assert((_owners & WRITER_HELD) > 0);
+
+ if (_fIsReentrant)
+ {
+ if (IsRwHashEntryChanged(lrwc))
+ lrwc = GetThreadRWCount(false);
+ lrwc.writercount++;
+ }
+
+ _spinLock.Exit();
+
+ _writeLockOwnerId = id;
+
+ return true;
+ }
+
+ public void EnterUpgradeableReadLock()
+ {
+ TryEnterUpgradeableReadLock(-1);
+ }
+
+ public bool TryEnterUpgradeableReadLock(TimeSpan timeout)
+ {
+ return TryEnterUpgradeableReadLock(new TimeoutTracker(timeout));
+ }
+
+ public bool TryEnterUpgradeableReadLock(int millisecondsTimeout)
+ {
+ return TryEnterUpgradeableReadLock(new TimeoutTracker(millisecondsTimeout));
+ }
+
+ private bool TryEnterUpgradeableReadLock(TimeoutTracker timeout)
+ {
+ return TryEnterUpgradeableReadLockCore(timeout);
+ }
+
+ private bool TryEnterUpgradeableReadLockCore(TimeoutTracker timeout)
+ {
+ if (_fDisposed)
+ throw new ObjectDisposedException(null);
+
+ int id = Environment.CurrentManagedThreadId;
+ ReaderWriterCount lrwc;
+
+ if (!_fIsReentrant)
+ {
+ if (id == _upgradeLockOwnerId)
+ {
+ //Check for AU->AU
+ throw new LockRecursionException(SR.LockRecursionException_RecursiveUpgradeNotAllowed);
+ }
+ else if (id == _writeLockOwnerId)
+ {
+ //Check for AU->AW
+ throw new LockRecursionException(SR.LockRecursionException_UpgradeAfterWriteNotAllowed);
+ }
+
+ _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
+ lrwc = GetThreadRWCount(true);
+ //Can't acquire upgrade lock with reader lock held.
+ if (lrwc != null && lrwc.readercount > 0)
+ {
+ _spinLock.Exit();
+ throw new LockRecursionException(SR.LockRecursionException_UpgradeAfterReadNotAllowed);
+ }
+ }
+ else
+ {
+ _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
+ lrwc = GetThreadRWCount(false);
+
+ if (id == _upgradeLockOwnerId)
+ {
+ lrwc.upgradecount++;
+ _spinLock.Exit();
+ return true;
+ }
+ else if (id == _writeLockOwnerId)
+ {
+ //Write lock is already held, Just update the global state
+ //to show presence of upgrader.
+ Debug.Assert((_owners & WRITER_HELD) > 0);
+ _owners++;
+ _upgradeLockOwnerId = id;
+ lrwc.upgradecount++;
+ if (lrwc.readercount > 0)
+ _fUpgradeThreadHoldingRead = true;
+ _spinLock.Exit();
+ return true;
+ }
+ else if (lrwc.readercount > 0)
+ {
+ //Upgrade locks may not be acquired if only read locks have been
+ //acquired.
+ _spinLock.Exit();
+ throw new LockRecursionException(SR.LockRecursionException_UpgradeAfterReadNotAllowed);
+ }
+ }
+
+ bool retVal = true;
+ int spinCount = 0;
+
+ for (; ;)
+ {
+ //Once an upgrade lock is taken, it's like having a reader lock held
+ //until upgrade or downgrade operations are performed.
+
+ if ((_upgradeLockOwnerId == -1) && (_owners < MAX_READER))
+ {
+ _owners++;
+ _upgradeLockOwnerId = id;
+ break;
+ }
+
+ if (timeout.IsExpired)
+ {
+ _spinLock.Exit();
+ return false;
+ }
+
+ if (spinCount < MaxSpinCount && ShouldSpinForEnterAnyRead())
+ {
+ _spinLock.Exit();
+ spinCount++;
+ SpinWait(spinCount);
+ _spinLock.Enter(EnterSpinLockReason.EnterAnyRead);
+ continue;
+ }
+
+ // Drat, we need to wait. Mark that we have waiters and wait.
+ if (_upgradeEvent == null) // Create the needed event
+ {
+ LazyCreateEvent(ref _upgradeEvent, EnterLockType.UpgradeableRead);
+ continue; // since we left the lock, start over.
+ }
+
+ //Only one thread with the upgrade lock held can proceed.
+ retVal = WaitOnEvent(_upgradeEvent, ref _numUpgradeWaiters, timeout, EnterLockType.UpgradeableRead);
+ if (!retVal)
+ return false;
+ }
+
+ if (_fIsReentrant)
+ {
+ //The lock may have been dropped getting here, so make a quick check to see whether some other
+ //thread did not grab the entry.
+ if (IsRwHashEntryChanged(lrwc))
+ lrwc = GetThreadRWCount(false);
+ lrwc.upgradecount++;
+ }
+
+ _spinLock.Exit();
+
+ return true;
+ }
+
+ public void ExitReadLock()
+ {
+ ReaderWriterCount lrwc = null;
+
+ _spinLock.Enter(EnterSpinLockReason.ExitAnyRead);
+
+ lrwc = GetThreadRWCount(true);
+
+ if (lrwc == null || lrwc.readercount < 1)
+ {
+ //You have to be holding the read lock to make this call.
+ _spinLock.Exit();
+ throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedRead);
+ }
+
+ if (_fIsReentrant)
+ {
+ if (lrwc.readercount > 1)
+ {
+ lrwc.readercount--;
+ _spinLock.Exit();
+ return;
+ }
+
+ if (Environment.CurrentManagedThreadId == _upgradeLockOwnerId)
+ {
+ _fUpgradeThreadHoldingRead = false;
+ }
+ }
+
+ Debug.Assert(_owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
+
+ --_owners;
+
+ Debug.Assert(lrwc.readercount == 1);
+ lrwc.readercount--;
+
+ ExitAndWakeUpAppropriateWaiters();
+ }
+
+ public void ExitWriteLock()
+ {
+ ReaderWriterCount lrwc;
+ if (!_fIsReentrant)
+ {
+ if (Environment.CurrentManagedThreadId != _writeLockOwnerId)
+ {
+ //You have to be holding the write lock to make this call.
+ throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedWrite);
+ }
+ _spinLock.Enter(EnterSpinLockReason.ExitAnyWrite);
+ }
+ else
+ {
+ _spinLock.Enter(EnterSpinLockReason.ExitAnyWrite);
+ lrwc = GetThreadRWCount(false);
+
+ if (lrwc == null)
+ {
+ _spinLock.Exit();
+ throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedWrite);
+ }
+
+ if (lrwc.writercount < 1)
+ {
+ _spinLock.Exit();
+ throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedWrite);
+ }
+
+ lrwc.writercount--;
+
+ if (lrwc.writercount > 0)
+ {
+ _spinLock.Exit();
+ return;
+ }
+ }
+
+ Debug.Assert((_owners & WRITER_HELD) > 0, "Calling ReleaseWriterLock when no write lock is held");
+
+ ClearWriterAcquired();
+
+ _writeLockOwnerId = -1;
+
+ ExitAndWakeUpAppropriateWaiters();
+ }
+
+ public void ExitUpgradeableReadLock()
+ {
+ ReaderWriterCount lrwc;
+ if (!_fIsReentrant)
+ {
+ if (Environment.CurrentManagedThreadId != _upgradeLockOwnerId)
+ {
+ //You have to be holding the upgrade lock to make this call.
+ throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedUpgrade);
+ }
+ _spinLock.Enter(EnterSpinLockReason.ExitAnyRead);
+ }
+ else
+ {
+ _spinLock.Enter(EnterSpinLockReason.ExitAnyRead);
+ lrwc = GetThreadRWCount(true);
+
+ if (lrwc == null)
+ {
+ _spinLock.Exit();
+ throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedUpgrade);
+ }
+
+ if (lrwc.upgradecount < 1)
+ {
+ _spinLock.Exit();
+ throw new SynchronizationLockException(SR.SynchronizationLockException_MisMatchedUpgrade);
+ }
+
+ lrwc.upgradecount--;
+
+ if (lrwc.upgradecount > 0)
+ {
+ _spinLock.Exit();
+ return;
+ }
+
+ _fUpgradeThreadHoldingRead = false;
+ }
+
+ _owners--;
+ _upgradeLockOwnerId = -1;
+
+ ExitAndWakeUpAppropriateWaiters();
+ }
+
+ /// <summary>
+ /// A routine for lazily creating a event outside the lock (so if errors
+ /// happen they are outside the lock and that we don't do much work
+ /// while holding a spin lock). If all goes well, reenter the lock and
+ /// set 'waitEvent'
+ /// </summary>
+ private void LazyCreateEvent(ref EventWaitHandle waitEvent, EnterLockType enterLockType)
+ {
+#if DEBUG
+ Debug.Assert(_spinLock.IsHeld);
+ Debug.Assert(waitEvent == null);
+#endif
+
+ _spinLock.Exit();
+
+ var newEvent =
+ new EventWaitHandle(
+ false,
+ enterLockType == EnterLockType.Read ? EventResetMode.ManualReset : EventResetMode.AutoReset);
+
+ EnterSpinLockReason enterMyLockReason;
+ switch (enterLockType)
+ {
+ case EnterLockType.Read:
+ case EnterLockType.UpgradeableRead:
+ enterMyLockReason = EnterSpinLockReason.EnterAnyRead | EnterSpinLockReason.Wait;
+ break;
+
+ case EnterLockType.Write:
+ enterMyLockReason = EnterSpinLockReason.EnterWrite | EnterSpinLockReason.Wait;
+ break;
+
+ default:
+ Debug.Assert(enterLockType == EnterLockType.UpgradeToWrite);
+ enterMyLockReason = EnterSpinLockReason.UpgradeToWrite | EnterSpinLockReason.Wait;
+ break;
+ }
+ _spinLock.Enter(enterMyLockReason);
+
+ if (waitEvent == null) // maybe someone snuck in.
+ waitEvent = newEvent;
+ else
+ newEvent.Dispose();
+ }
+
+ /// <summary>
+ /// Waits on 'waitEvent' with a timeout
+ /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
+ /// </summary>
+ private bool WaitOnEvent(
+ EventWaitHandle waitEvent,
+ ref uint numWaiters,
+ TimeoutTracker timeout,
+ EnterLockType enterLockType)
+ {
+#if DEBUG
+ Debug.Assert(_spinLock.IsHeld);
+#endif
+
+ WaiterStates waiterSignaledState = WaiterStates.None;
+ EnterSpinLockReason enterMyLockReason;
+ switch (enterLockType)
+ {
+ case EnterLockType.UpgradeableRead:
+ waiterSignaledState = WaiterStates.UpgradeableReadWaiterSignaled;
+ goto case EnterLockType.Read;
+
+ case EnterLockType.Read:
+ enterMyLockReason = EnterSpinLockReason.EnterAnyRead;
+ break;
+
+ case EnterLockType.Write:
+ waiterSignaledState = WaiterStates.WriteWaiterSignaled;
+ enterMyLockReason = EnterSpinLockReason.EnterWrite;
+ break;
+
+ default:
+ Debug.Assert(enterLockType == EnterLockType.UpgradeToWrite);
+ enterMyLockReason = EnterSpinLockReason.UpgradeToWrite;
+ break;
+ }
+
+ // It was not possible to acquire the RW lock because some other thread was holding some type of lock. The other
+ // thread, when it releases its lock, will wake appropriate waiters. Along with resetting the wait event, clear the
+ // waiter signaled bit for this type of waiter if applicable, to indicate that a waiter of this type is no longer
+ // signaled.
+ //
+ // If the waiter signaled bit is not updated upon event reset, the following scenario would lead to deadlock:
+ // - Thread T0 signals the write waiter event or the upgradeable read waiter event to wake a waiter
+ // - There are no threads waiting on the event, but T1 is in WaitOnEvent() after exiting the spin lock and before
+ // actually waiting on the event (that is, it's recorded that there is one waiter for the event). It remains in
+ // this region for a while, in the repro case it typically gets context-switched out.
+ // - T2 acquires the RW lock in some fashion that blocks T0 or T3 from acquiring the RW lock
+ // - T0 or T3 fails to acquire the RW lock enough times for it to enter WaitOnEvent for the same event as T1
+ // - T0 or T3 resets the event
+ // - T2 releases the RW lock and does not wake a waiter because the reset at the previous step lost a signal but
+ // _waiterStates was not updated to reflect that
+ // - T1 and other threads begin waiting on the event, but there's no longer any thread that would wake them
+ if (waiterSignaledState != WaiterStates.None && (_waiterStates & waiterSignaledState) != WaiterStates.None)
+ {
+ _waiterStates &= ~waiterSignaledState;
+ }
+ waitEvent.Reset();
+
+ numWaiters++;
+ HasNoWaiters = false;
+
+ //Setting these bits will prevent new readers from getting in.
+ if (_numWriteWaiters == 1)
+ SetWritersWaiting();
+ if (_numWriteUpgradeWaiters == 1)
+ SetUpgraderWaiting();
+
+ bool waitSuccessful = false;
+ _spinLock.Exit(); // Do the wait outside of any lock
+
+ try
+ {
+ waitSuccessful = waitEvent.WaitOne(timeout.RemainingMilliseconds);
+ }
+ finally
+ {
+ _spinLock.Enter(enterMyLockReason);
+
+ --numWaiters;
+
+ if (waitSuccessful &&
+ waiterSignaledState != WaiterStates.None &&
+ (_waiterStates & waiterSignaledState) != WaiterStates.None)
+ {
+ // Indicate that a signaled waiter of this type has woken. Since non-read waiters are signaled to wake one
+ // at a time, we avoid waking up more than one waiter of that type upon successive enter/exit loops until
+ // the signaled thread actually wakes up. For example, if there are multiple write waiters and one thread is
+ // repeatedly entering and exiting a write lock, every exit would otherwise signal a different write waiter
+ // to wake up unnecessarily when only one woken waiter may actually succeed in entering the write lock.
+ _waiterStates &= ~waiterSignaledState;
+ }
+
+ if (_numWriteWaiters == 0 && _numWriteUpgradeWaiters == 0 && _numUpgradeWaiters == 0 && _numReadWaiters == 0)
+ HasNoWaiters = true;
+
+ if (_numWriteWaiters == 0)
+ ClearWritersWaiting();
+ if (_numWriteUpgradeWaiters == 0)
+ ClearUpgraderWaiting();
+
+ if (!waitSuccessful) // We may also be about to throw for some reason. Exit myLock.
+ {
+ if (enterLockType >= EnterLockType.Write)
+ {
+ // Write waiters block read waiters from acquiring the lock. Since this was the last write waiter, try
+ // to wake up the appropriate read waiters.
+ ExitAndWakeUpAppropriateReadWaiters();
+ }
+ else
+ {
+ _spinLock.Exit();
+ }
+ }
+ }
+ return waitSuccessful;
+ }
+
+ /// <summary>
+ /// Determines the appropriate events to set, leaves the locks, and sets the events.
+ /// </summary>
+ private void ExitAndWakeUpAppropriateWaiters()
+ {
+#if DEBUG
+ Debug.Assert(_spinLock.IsHeld);
+#endif
+ if (HasNoWaiters)
+ {
+ _spinLock.Exit();
+ return;
+ }
+
+ ExitAndWakeUpAppropriateWaitersPreferringWriters();
+ }
+
+ private void ExitAndWakeUpAppropriateWaitersPreferringWriters()
+ {
+ uint readercount = GetNumReaders();
+
+ //We need this case for EU->ER->EW case, as the read count will be 2 in
+ //that scenario.
+ if (_fIsReentrant)
+ {
+ if (_numWriteUpgradeWaiters > 0 && _fUpgradeThreadHoldingRead && readercount == 2)
+ {
+ _spinLock.Exit(); // Exit before signaling to improve efficiency (wakee will need the lock)
+ _waitUpgradeEvent.Set(); // release all upgraders (however there can be at most one).
+ return;
+ }
+ }
+
+ if (readercount == 1 && _numWriteUpgradeWaiters > 0)
+ {
+ //We have to be careful now, as we are dropping the lock.
+ //No new writes should be allowed to sneak in if an upgrade
+ //was pending.
+
+ _spinLock.Exit(); // Exit before signaling to improve efficiency (wakee will need the lock)
+ _waitUpgradeEvent.Set(); // release all upgraders (however there can be at most one).
+ }
+ else if (readercount == 0 && _numWriteWaiters > 0)
+ {
+ // Check if a waiter of the same type has already been signaled but hasn't woken yet. If so, avoid signaling
+ // and waking another waiter unnecessarily.
+ WaiterStates signaled = _waiterStates & WaiterStates.WriteWaiterSignaled;
+ if (signaled == WaiterStates.None)
+ {
+ _waiterStates |= WaiterStates.WriteWaiterSignaled;
+ }
+
+ _spinLock.Exit(); // Exit before signaling to improve efficiency (wakee will need the lock)
+
+ if (signaled == WaiterStates.None)
+ {
+ _writeEvent.Set(); // release one writer.
+ }
+ }
+ else
+ {
+ ExitAndWakeUpAppropriateReadWaiters();
+ }
+ }
+
+ private void ExitAndWakeUpAppropriateReadWaiters()
+ {
+#if DEBUG
+ Debug.Assert(_spinLock.IsHeld);
+#endif
+
+ if (_numWriteWaiters != 0 || _numWriteUpgradeWaiters != 0 || HasNoWaiters)
+ {
+ _spinLock.Exit();
+ return;
+ }
+
+ Debug.Assert(_numReadWaiters != 0 || _numUpgradeWaiters != 0);
+
+ bool setReadEvent = _numReadWaiters != 0;
+ bool setUpgradeEvent = _numUpgradeWaiters != 0 && _upgradeLockOwnerId == -1;
+ if (setUpgradeEvent)
+ {
+ // Check if a waiter of the same type has already been signaled but hasn't woken yet. If so, avoid signaling
+ // and waking another waiter unnecessarily.
+ if ((_waiterStates & WaiterStates.UpgradeableReadWaiterSignaled) == WaiterStates.None)
+ {
+ _waiterStates |= WaiterStates.UpgradeableReadWaiterSignaled;
+ }
+ else
+ {
+ setUpgradeEvent = false;
+ }
+ }
+
+ _spinLock.Exit(); // Exit before signaling to improve efficiency (wakee will need the lock)
+
+ if (setReadEvent)
+ _readEvent.Set(); // release all readers.
+
+ if (setUpgradeEvent)
+ _upgradeEvent.Set(); //release one upgrader.
+ }
+
+ private bool IsWriterAcquired()
+ {
+ return (_owners & ~WAITING_WRITERS) == 0;
+ }
+
+ private void SetWriterAcquired()
+ {
+ _owners |= WRITER_HELD; // indicate we have a writer.
+ }
+
+ private void ClearWriterAcquired()
+ {
+ _owners &= ~WRITER_HELD;
+ }
+
+ private void SetWritersWaiting()
+ {
+ _owners |= WAITING_WRITERS;
+ }
+
+ private void ClearWritersWaiting()
+ {
+ _owners &= ~WAITING_WRITERS;
+ }
+
+ private void SetUpgraderWaiting()
+ {
+ _owners |= WAITING_UPGRADER;
+ }
+
+ private void ClearUpgraderWaiting()
+ {
+ _owners &= ~WAITING_UPGRADER;
+ }
+
+ private uint GetNumReaders()
+ {
+ return _owners & READER_MASK;
+ }
+
+ private bool ShouldSpinForEnterAnyRead()
+ {
+ // If there is a write waiter or write upgrade waiter, the waiter would block a reader from acquiring the RW lock
+ // because the waiter takes precedence. In that case, the reader is not likely to make progress by spinning.
+ // Although another thread holding a write lock would prevent this thread from acquiring a read lock, it is by
+ // itself not a good enough reason to skip spinning.
+ return HasNoWaiters || (_numWriteWaiters == 0 && _numWriteUpgradeWaiters == 0);
+ }
+
+ private bool ShouldSpinForEnterAnyWrite(bool isUpgradeToWrite)
+ {
+ // If there is a write upgrade waiter, the waiter would block a writer from acquiring the RW lock because the waiter
+ // holds a read lock. In that case, the writer is not likely to make progress by spinning. Regarding upgrading to a
+ // write lock, there is no type of waiter that would block the upgrade from happening. Although another thread
+ // holding a read or write lock would prevent this thread from acquiring the write lock, it is by itself not a good
+ // enough reason to skip spinning.
+ return isUpgradeToWrite || _numWriteUpgradeWaiters == 0;
+ }
+
+ private static void SpinWait(int spinCount)
+ {
+ const int LockSpinCycles = 20;
+
+ //Exponential back-off
+ if ((spinCount < 5) && (ProcessorCount > 1))
+ {
+ RuntimeThread.SpinWait(LockSpinCycles * spinCount);
+ }
+ else
+ {
+ RuntimeThread.Sleep(0);
+ }
+
+ // Don't want to Sleep(1) in this spin wait:
+ // - Don't want to spin for that long, since a proper wait will follow when the spin wait fails. The artificial
+ // delay introduced by Sleep(1) will in some cases be much longer than desired.
+ // - Sleep(1) would put the thread into a wait state, and a proper wait will follow when the spin wait fails
+ // anyway, so it's preferable to put the thread into the proper wait state
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing && !_fDisposed)
+ {
+ if (WaitingReadCount > 0 || WaitingUpgradeCount > 0 || WaitingWriteCount > 0)
+ throw new SynchronizationLockException(SR.SynchronizationLockException_IncorrectDispose);
+
+ if (IsReadLockHeld || IsUpgradeableReadLockHeld || IsWriteLockHeld)
+ throw new SynchronizationLockException(SR.SynchronizationLockException_IncorrectDispose);
+
+ if (_writeEvent != null)
+ {
+ _writeEvent.Dispose();
+ _writeEvent = null;
+ }
+
+ if (_readEvent != null)
+ {
+ _readEvent.Dispose();
+ _readEvent = null;
+ }
+
+ if (_upgradeEvent != null)
+ {
+ _upgradeEvent.Dispose();
+ _upgradeEvent = null;
+ }
+
+ if (_waitUpgradeEvent != null)
+ {
+ _waitUpgradeEvent.Dispose();
+ _waitUpgradeEvent = null;
+ }
+
+ _fDisposed = true;
+ }
+ }
+
+ public bool IsReadLockHeld
+ {
+ get
+ {
+ if (RecursiveReadCount > 0)
+ return true;
+ else
+ return false;
+ }
+ }
+
+ public bool IsUpgradeableReadLockHeld
+ {
+ get
+ {
+ if (RecursiveUpgradeCount > 0)
+ return true;
+ else
+ return false;
+ }
+ }
+
+ public bool IsWriteLockHeld
+ {
+ get
+ {
+ if (RecursiveWriteCount > 0)
+ return true;
+ else
+ return false;
+ }
+ }
+
+ public LockRecursionPolicy RecursionPolicy
+ {
+ get
+ {
+ if (_fIsReentrant)
+ {
+ return LockRecursionPolicy.SupportsRecursion;
+ }
+ else
+ {
+ return LockRecursionPolicy.NoRecursion;
+ }
+ }
+ }
+
+ public int CurrentReadCount
+ {
+ get
+ {
+ int numreaders = (int)GetNumReaders();
+
+ if (_upgradeLockOwnerId != -1)
+ return numreaders - 1;
+ else
+ return numreaders;
+ }
+ }
+
+
+ public int RecursiveReadCount
+ {
+ get
+ {
+ int count = 0;
+ ReaderWriterCount lrwc = GetThreadRWCount(true);
+ if (lrwc != null)
+ count = lrwc.readercount;
+
+ return count;
+ }
+ }
+
+ public int RecursiveUpgradeCount
+ {
+ get
+ {
+ if (_fIsReentrant)
+ {
+ int count = 0;
+
+ ReaderWriterCount lrwc = GetThreadRWCount(true);
+ if (lrwc != null)
+ count = lrwc.upgradecount;
+
+ return count;
+ }
+ else
+ {
+ if (Environment.CurrentManagedThreadId == _upgradeLockOwnerId)
+ return 1;
+ else
+ return 0;
+ }
+ }
+ }
+
+ public int RecursiveWriteCount
+ {
+ get
+ {
+ if (_fIsReentrant)
+ {
+ int count = 0;
+
+ ReaderWriterCount lrwc = GetThreadRWCount(true);
+ if (lrwc != null)
+ count = lrwc.writercount;
+
+ return count;
+ }
+ else
+ {
+ if (Environment.CurrentManagedThreadId == _writeLockOwnerId)
+ return 1;
+ else
+ return 0;
+ }
+ }
+ }
+
+ public int WaitingReadCount
+ {
+ get
+ {
+ return (int)_numReadWaiters;
+ }
+ }
+
+ public int WaitingUpgradeCount
+ {
+ get
+ {
+ return (int)_numUpgradeWaiters;
+ }
+ }
+
+ public int WaitingWriteCount
+ {
+ get
+ {
+ return (int)_numWriteWaiters;
+ }
+ }
+
+ private struct SpinLock
+ {
+ private int _isLocked;
+
+ /// <summary>
+ /// Used to deprioritize threads attempting to enter the lock when they would not make progress after doing so.
+ /// <see cref="EnterSpin(EnterSpinLockReason)"/> avoids acquiring the lock as long as the operation for which it
+ /// was called is deprioritized.
+ ///
+ /// Layout:
+ /// - Low 16 bits: Number of threads that have deprioritized an enter-any-write operation
+ /// - High 16 bits: Number of threads that have deprioritized an enter-any-read operation
+ /// </summary>
+ private int _enterDeprioritizationState;
+
+ // Layout-specific constants for _enterDeprioritizationState
+ private const int DeprioritizeEnterAnyReadIncrement = 1 << 16;
+ private const int DeprioritizeEnterAnyWriteIncrement = 1;
+
+ // The variables controlling spinning behavior of this spin lock
+ private const int LockSpinCycles = 20;
+ private const int LockSpinCount = 10;
+ private const int LockSleep0Count = 5;
+ private const int DeprioritizedLockSleep1Count = 5;
+
+ private static int GetEnterDeprioritizationStateChange(EnterSpinLockReason reason)
+ {
+ EnterSpinLockReason operation = reason & EnterSpinLockReason.OperationMask;
+ switch (operation)
+ {
+ case EnterSpinLockReason.EnterAnyRead:
+ return 0;
+
+ case EnterSpinLockReason.ExitAnyRead:
+ // A read lock is held until this thread is able to exit it, so deprioritize enter-write threads as they
+ // will not be able to make progress
+ return DeprioritizeEnterAnyWriteIncrement;
+
+ case EnterSpinLockReason.EnterWrite:
+ // Writers are typically much less frequent and much less in number than readers. Waiting writers take
+ // precedence over new read attempts in order to let current readers release their lock and allow a
+ // writer to obtain the lock. Before a writer can register as a waiter though, the presence of just
+ // relatively few enter-read spins can easily starve the enter-write from even entering this lock,
+ // delaying its spin loop for an unreasonable duration.
+ //
+ // Deprioritize enter-read to preference enter-write. This makes it easier for enter-write threads to
+ // starve enter-read threads. However, writers can already by design starve readers. A waiting writer
+ // blocks enter-read threads and a new enter-write that needs to wait will be given precedence over
+ // previously waiting enter-read threads. So this is not a new problem, and the RW lock is designed for
+ // scenarios where writers are rare compared to readers.
+ return DeprioritizeEnterAnyReadIncrement;
+
+ default:
+ Debug.Assert(
+ operation == EnterSpinLockReason.UpgradeToWrite ||
+ operation == EnterSpinLockReason.EnterRecursiveWrite ||
+ operation == EnterSpinLockReason.ExitAnyWrite);
+
+ // UpgradeToWrite:
+ // - A read lock is held and an exit-read is not nearby, so deprioritize enter-write threads as they
+ // will not be able to make progress. This thread also intends to enter a write lock, so deprioritize
+ // enter -read threads as well, see case EnterSpinLockReason.EnterWrite for the rationale.
+ // EnterRecursiveWrite, ExitAnyWrite:
+ // - In both cases, a write lock is held until this thread is able to exit it, so deprioritize
+ // enter -read and enter-write threads as they will not be able to make progress
+ return DeprioritizeEnterAnyReadIncrement + DeprioritizeEnterAnyWriteIncrement;
+ }
+ }
+
+ private ushort EnterForEnterAnyReadDeprioritizedCount
+ {
+ get
+ {
+ Debug.Assert(DeprioritizeEnterAnyReadIncrement == (1 << 16));
+ return (ushort)((uint)_enterDeprioritizationState >> 16);
+ }
+ }
+
+ private ushort EnterForEnterAnyWriteDeprioritizedCount
+ {
+ get
+ {
+ Debug.Assert(DeprioritizeEnterAnyWriteIncrement == 1);
+ return (ushort)_enterDeprioritizationState;
+ }
+ }
+
+ private bool IsEnterDeprioritized(EnterSpinLockReason reason)
+ {
+ Debug.Assert((reason & EnterSpinLockReason.Wait) != 0 || reason == (reason & EnterSpinLockReason.OperationMask));
+ Debug.Assert(
+ (reason & EnterSpinLockReason.Wait) == 0 ||
+ (reason & EnterSpinLockReason.OperationMask) == EnterSpinLockReason.EnterAnyRead ||
+ (reason & EnterSpinLockReason.OperationMask) == EnterSpinLockReason.EnterWrite ||
+ (reason & EnterSpinLockReason.OperationMask) == EnterSpinLockReason.UpgradeToWrite);
+
+ switch (reason)
+ {
+ default:
+ Debug.Assert(
+ (reason & EnterSpinLockReason.Wait) != 0 ||
+ reason == EnterSpinLockReason.ExitAnyRead ||
+ reason == EnterSpinLockReason.EnterRecursiveWrite ||
+ reason == EnterSpinLockReason.ExitAnyWrite);
+ return false;
+
+ case EnterSpinLockReason.EnterAnyRead:
+ return EnterForEnterAnyReadDeprioritizedCount != 0;
+
+ case EnterSpinLockReason.EnterWrite:
+ Debug.Assert((GetEnterDeprioritizationStateChange(reason) & DeprioritizeEnterAnyWriteIncrement) == 0);
+ return EnterForEnterAnyWriteDeprioritizedCount != 0;
+
+ case EnterSpinLockReason.UpgradeToWrite:
+ Debug.Assert((GetEnterDeprioritizationStateChange(reason) & DeprioritizeEnterAnyWriteIncrement) != 0);
+ return EnterForEnterAnyWriteDeprioritizedCount > 1;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool TryEnter()
+ {
+ return Interlocked.CompareExchange(ref _isLocked, 1, 0) == 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Enter(EnterSpinLockReason reason)
+ {
+ if (!TryEnter())
+ {
+ EnterSpin(reason);
+ }
+ }
+
+ private void EnterSpin(EnterSpinLockReason reason)
+ {
+ int deprioritizationStateChange = GetEnterDeprioritizationStateChange(reason);
+ if (deprioritizationStateChange != 0)
+ {
+ Interlocked.Add(ref _enterDeprioritizationState, deprioritizationStateChange);
+ }
+
+ int processorCount = ProcessorCount;
+ for (int spinIndex = 0; ; spinIndex++)
+ {
+ if (spinIndex < LockSpinCount && processorCount > 1)
+ {
+ RuntimeThread.SpinWait(LockSpinCycles * (spinIndex + 1)); // Wait a few dozen instructions to let another processor release lock.
+ }
+ else if (spinIndex < (LockSpinCount + LockSleep0Count))
+ {
+ RuntimeThread.Sleep(0); // Give up my quantum.
+ }
+ else
+ {
+ RuntimeThread.Sleep(1); // Give up my quantum.
+ }
+
+ if (!IsEnterDeprioritized(reason))
+ {
+ if (_isLocked == 0 && TryEnter())
+ {
+ if (deprioritizationStateChange != 0)
+ {
+ Interlocked.Add(ref _enterDeprioritizationState, -deprioritizationStateChange);
+ }
+ return;
+ }
+ continue;
+ }
+
+ // It's possible for an Enter thread to be deprioritized for an extended duration. It's undesirable for a
+ // deprioritized thread to keep waking up to spin despite a Sleep(1) when a large number of such threads are
+ // involved. After a threshold of Sleep(1)s, ignore the deprioritization and enter this lock to allow this
+ // thread to stop spinning and hopefully enter a proper wait state.
+ Debug.Assert(
+ reason == EnterSpinLockReason.EnterAnyRead ||
+ reason == EnterSpinLockReason.EnterWrite ||
+ reason == EnterSpinLockReason.UpgradeToWrite);
+ if (spinIndex >= (LockSpinCount + LockSleep0Count + DeprioritizedLockSleep1Count))
+ {
+ reason |= EnterSpinLockReason.Wait;
+ spinIndex = -1;
+ }
+ }
+ }
+
+ public void Exit()
+ {
+ Debug.Assert(_isLocked != 0, "Exiting spin lock that is not held");
+ Volatile.Write(ref _isLocked, 0);
+ }
+
+#if DEBUG
+ public bool IsHeld => _isLocked != 0;
+#endif
+ }
+
+ [Flags]
+ private enum WaiterStates : byte
+ {
+ None = 0x0,
+
+ // Used for quick check when there are no waiters
+ NoWaiters = 0x1,
+
+ // Used to avoid signaling more than one waiter to wake up when only one can make progress, see WaitOnEvent
+ WriteWaiterSignaled = 0x2,
+ UpgradeableReadWaiterSignaled = 0x4
+ // Write upgrade waiters are excluded because there can only be one at any given time
+ }
+
+ private enum EnterSpinLockReason
+ {
+ EnterAnyRead = 0,
+ ExitAnyRead = 1,
+ EnterWrite = 2,
+ UpgradeToWrite = 3,
+ EnterRecursiveWrite = 4,
+ ExitAnyWrite = 5,
+
+ OperationMask = 0x7,
+
+ Wait = 0x8
+ }
+
+ private enum EnterLockType
+ {
+ Read,
+ UpgradeableRead,
+ Write,
+ UpgradeToWrite
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/SemaphoreFullException.cs b/src/System.Private.CoreLib/shared/System/Threading/SemaphoreFullException.cs
new file mode 100644
index 0000000000..18558b19e0
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/SemaphoreFullException.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class SemaphoreFullException : SystemException
+ {
+ public SemaphoreFullException() : base(SR.Threading_SemaphoreFullException)
+ {
+ }
+
+ public SemaphoreFullException(String message) : base(message)
+ {
+ }
+
+ public SemaphoreFullException(String message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected SemaphoreFullException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/SendOrPostCallback.cs b/src/System.Private.CoreLib/shared/System/Threading/SendOrPostCallback.cs
new file mode 100644
index 0000000000..6692d35ab2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/SendOrPostCallback.cs
@@ -0,0 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Threading
+{
+ public delegate void SendOrPostCallback(Object state);
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/SpinWait.cs b/src/System.Private.CoreLib/shared/System/Threading/SpinWait.cs
new file mode 100644
index 0000000000..414ad1852f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/SpinWait.cs
@@ -0,0 +1,359 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+// Central spin logic used across the entire code-base.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Diagnostics;
+using Internal.Runtime.Augments;
+
+namespace System.Threading
+{
+ // SpinWait is just a little value type that encapsulates some common spinning
+ // logic. It ensures we always yield on single-proc machines (instead of using busy
+ // waits), and that we work well on HT. It encapsulates a good mixture of spinning
+ // and real yielding. It's a value type so that various areas of the engine can use
+ // one by allocating it on the stack w/out unnecessary GC allocation overhead, e.g.:
+ //
+ // void f() {
+ // SpinWait wait = new SpinWait();
+ // while (!p) { wait.SpinOnce(); }
+ // ...
+ // }
+ //
+ // Internally it just maintains a counter that is used to decide when to yield, etc.
+ //
+ // A common usage is to spin before blocking. In those cases, the NextSpinWillYield
+ // property allows a user to decide to fall back to waiting once it returns true:
+ //
+ // void f() {
+ // SpinWait wait = new SpinWait();
+ // while (!p) {
+ // if (wait.NextSpinWillYield) { /* block! */ }
+ // else { wait.SpinOnce(); }
+ // }
+ // ...
+ // }
+
+ /// <summary>
+ /// Provides support for spin-based waiting.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// <see cref="SpinWait"/> encapsulates common spinning logic. On single-processor machines, yields are
+ /// always used instead of busy waits, and on computers with Intel(R) processors employing Hyper-Threading
+ /// technology, it helps to prevent hardware thread starvation. SpinWait encapsulates a good mixture of
+ /// spinning and true yielding.
+ /// </para>
+ /// <para>
+ /// <see cref="SpinWait"/> is a value type, which means that low-level code can utilize SpinWait without
+ /// fear of unnecessary allocation overheads. SpinWait is not generally useful for ordinary applications.
+ /// In most cases, you should use the synchronization classes provided by the .NET Framework, such as
+ /// <see cref="System.Threading.Monitor"/>. For most purposes where spin waiting is required, however,
+ /// the <see cref="SpinWait"/> type should be preferred over the <see
+ /// cref="System.Threading.Thread.SpinWait"/> method.
+ /// </para>
+ /// <para>
+ /// While SpinWait is designed to be used in concurrent applications, it is not designed to be
+ /// used from multiple threads concurrently. SpinWait's members are not thread-safe. If multiple
+ /// threads must spin, each should use its own instance of SpinWait.
+ /// </para>
+ /// </remarks>
+ public struct SpinWait
+ {
+ // These constants determine the frequency of yields versus spinning. The
+ // numbers may seem fairly arbitrary, but were derived with at least some
+ // thought in the design document. I fully expect they will need to change
+ // over time as we gain more experience with performance.
+ internal const int YieldThreshold = 10; // When to switch over to a true yield.
+ private const int Sleep0EveryHowManyYields = 5; // After how many yields should we Sleep(0)?
+ internal const int DefaultSleep1Threshold = 20; // After how many yields should we Sleep(1) frequently?
+
+ /// <summary>
+ /// A suggested number of spin iterations before doing a proper wait, such as waiting on an event that becomes signaled
+ /// when the resource becomes available.
+ /// </summary>
+ /// <remarks>
+ /// These numbers were arrived at by experimenting with different numbers in various cases that currently use it. It's
+ /// only a suggested value and typically works well when the proper wait is something like an event.
+ ///
+ /// Spinning less can lead to early waiting and more context switching, spinning more can decrease latency but may use
+ /// up some CPU time unnecessarily. Depends on the situation too, for instance SemaphoreSlim uses more iterations
+ /// because the waiting there is currently a lot more expensive (involves more spinning, taking a lock, etc.). It also
+ /// depends on the likelihood of the spin being successful and how long the wait would be but those are not accounted
+ /// for here.
+ /// </remarks>
+ internal static readonly int SpinCountforSpinBeforeWait = PlatformHelper.IsSingleProcessor ? 1 : 35;
+ internal const int Sleep1ThresholdForSpinBeforeWait = 40; // should be greater than SpinCountforSpinBeforeWait
+
+ // The number of times we've spun already.
+ private int _count;
+
+ /// <summary>
+ /// Gets the number of times <see cref="SpinOnce"/> has been called on this instance.
+ /// </summary>
+ public int Count
+ {
+ get => _count;
+ internal set
+ {
+ Debug.Assert(value >= 0);
+ _count = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether the next call to <see cref="SpinOnce"/> will yield the processor, triggering a
+ /// forced context switch.
+ /// </summary>
+ /// <value>Whether the next call to <see cref="SpinOnce"/> will yield the processor, triggering a
+ /// forced context switch.</value>
+ /// <remarks>
+ /// On a single-CPU machine, <see cref="SpinOnce"/> always yields the processor. On machines with
+ /// multiple CPUs, <see cref="SpinOnce"/> may yield after an unspecified number of calls.
+ /// </remarks>
+ public bool NextSpinWillYield => _count >= YieldThreshold || PlatformHelper.IsSingleProcessor;
+
+ /// <summary>
+ /// Performs a single spin.
+ /// </summary>
+ /// <remarks>
+ /// This is typically called in a loop, and may change in behavior based on the number of times a
+ /// <see cref="SpinOnce"/> has been called thus far on this instance.
+ /// </remarks>
+ public void SpinOnce()
+ {
+ SpinOnce(DefaultSleep1Threshold);
+ }
+
+ internal void SpinOnce(int sleep1Threshold)
+ {
+ Debug.Assert(sleep1Threshold >= YieldThreshold || PlatformHelper.IsSingleProcessor); // so that NextSpinWillYield behaves as requested
+
+ // (_count - YieldThreshold) % 2 == 0: The purpose of this check is to interleave Thread.Yield/Sleep(0) with
+ // Thread.SpinWait. Otherwise, the following issues occur:
+ // - When there are no threads to switch to, Yield and Sleep(0) become no-op and it turns the spin loop into a
+ // busy-spin that may quickly reach the max spin count and cause the thread to enter a wait state, or may
+ // just busy-spin for longer than desired before a Sleep(1). Completing the spin loop too early can cause
+ // excessive context switcing if a wait follows, and entering the Sleep(1) stage too early can cause
+ // excessive delays.
+ // - If there are multiple threads doing Yield and Sleep(0) (typically from the same spin loop due to
+ // contention), they may switch between one another, delaying work that can make progress.
+ if ((
+ _count >= YieldThreshold &&
+ (_count >= sleep1Threshold || (_count - YieldThreshold) % 2 == 0)
+ ) ||
+ PlatformHelper.IsSingleProcessor)
+ {
+ //
+ // We must yield.
+ //
+ // We prefer to call Thread.Yield first, triggering a SwitchToThread. This
+ // unfortunately doesn't consider all runnable threads on all OS SKUs. In
+ // some cases, it may only consult the runnable threads whose ideal processor
+ // is the one currently executing code. Thus we occasionally issue a call to
+ // Sleep(0), which considers all runnable threads at equal priority. Even this
+ // is insufficient since we may be spin waiting for lower priority threads to
+ // execute; we therefore must call Sleep(1) once in a while too, which considers
+ // all runnable threads, regardless of ideal processor and priority, but may
+ // remove the thread from the scheduler's queue for 10+ms, if the system is
+ // configured to use the (default) coarse-grained system timer.
+ //
+
+ if (_count >= sleep1Threshold)
+ {
+ RuntimeThread.Sleep(1);
+ }
+ else
+ {
+ int yieldsSoFar = _count >= YieldThreshold ? (_count - YieldThreshold) / 2 : _count;
+ if ((yieldsSoFar % Sleep0EveryHowManyYields) == (Sleep0EveryHowManyYields - 1))
+ {
+ RuntimeThread.Sleep(0);
+ }
+ else
+ {
+ RuntimeThread.Yield();
+ }
+ }
+ }
+ else
+ {
+ //
+ // Otherwise, we will spin.
+ //
+ // We do this using the CLR's SpinWait API, which is just a busy loop that
+ // issues YIELD/PAUSE instructions to ensure multi-threaded CPUs can react
+ // intelligently to avoid starving. (These are NOOPs on other CPUs.) We
+ // choose a number for the loop iteration count such that each successive
+ // call spins for longer, to reduce cache contention. We cap the total
+ // number of spins we are willing to tolerate to reduce delay to the caller,
+ // since we expect most callers will eventually block anyway.
+ //
+ // Also, cap the maximum spin count to a value such that many thousands of CPU cycles would not be wasted doing
+ // the equivalent of YieldProcessor(), as that that point SwitchToThread/Sleep(0) are more likely to be able to
+ // allow other useful work to run. Long YieldProcessor() loops can help to reduce contention, but Sleep(1) is
+ // usually better for that.
+ //
+ // RuntimeThread.OptimalMaxSpinWaitsPerSpinIteration:
+ // - See Thread::InitializeYieldProcessorNormalized(), which describes and calculates this value.
+ //
+ int n = RuntimeThread.OptimalMaxSpinWaitsPerSpinIteration;
+ if (_count <= 30 && (1 << _count) < n)
+ {
+ n = 1 << _count;
+ }
+ RuntimeThread.SpinWait(n);
+ }
+
+ // Finally, increment our spin counter.
+ _count = (_count == int.MaxValue ? YieldThreshold : _count + 1);
+ }
+
+ /// <summary>
+ /// Resets the spin counter.
+ /// </summary>
+ /// <remarks>
+ /// This makes <see cref="SpinOnce"/> and <see cref="NextSpinWillYield"/> behave as though no calls
+ /// to <see cref="SpinOnce"/> had been issued on this instance. If a <see cref="SpinWait"/> instance
+ /// is reused many times, it may be useful to reset it to avoid yielding too soon.
+ /// </remarks>
+ public void Reset()
+ {
+ _count = 0;
+ }
+
+ #region Static Methods
+ /// <summary>
+ /// Spins until the specified condition is satisfied.
+ /// </summary>
+ /// <param name="condition">A delegate to be executed over and over until it returns true.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception>
+ public static void SpinUntil(Func<bool> condition)
+ {
+#if DEBUG
+ bool result =
+#endif
+ SpinUntil(condition, Timeout.Infinite);
+#if DEBUG
+ Debug.Assert(result);
+#endif
+ }
+
+ /// <summary>
+ /// Spins until the specified condition is satisfied or until the specified timeout is expired.
+ /// </summary>
+ /// <param name="condition">A delegate to be executed over and over until it returns true.</param>
+ /// <param name="timeout">
+ /// A <see cref="TimeSpan"/> that represents the number of milliseconds to wait,
+ /// or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
+ /// <returns>True if the condition is satisfied within the timeout; otherwise, false</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative number
+ /// other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than
+ /// <see cref="System.Int32.MaxValue"/>.</exception>
+ public static bool SpinUntil(Func<bool> condition, TimeSpan timeout)
+ {
+ // Validate the timeout
+ long totalMilliseconds = (long)timeout.TotalMilliseconds;
+ if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
+ {
+ throw new System.ArgumentOutOfRangeException(
+ nameof(timeout), timeout, SR.SpinWait_SpinUntil_TimeoutWrong);
+ }
+
+ // Call wait with the timeout milliseconds
+ return SpinUntil(condition, (int)totalMilliseconds);
+ }
+
+ /// <summary>
+ /// Spins until the specified condition is satisfied or until the specified timeout is expired.
+ /// </summary>
+ /// <param name="condition">A delegate to be executed over and over until it returns true.</param>
+ /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
+ /// cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param>
+ /// <returns>True if the condition is satisfied within the timeout; otherwise, false</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception>
+ /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
+ /// negative number other than -1, which represents an infinite time-out.</exception>
+ public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout)
+ {
+ if (millisecondsTimeout < Timeout.Infinite)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(millisecondsTimeout), millisecondsTimeout, SR.SpinWait_SpinUntil_TimeoutWrong);
+ }
+ if (condition == null)
+ {
+ throw new ArgumentNullException(nameof(condition), SR.SpinWait_SpinUntil_ArgumentNull);
+ }
+ uint startTime = 0;
+ if (millisecondsTimeout != 0 && millisecondsTimeout != Timeout.Infinite)
+ {
+ startTime = TimeoutHelper.GetTime();
+ }
+ SpinWait spinner = new SpinWait();
+ while (!condition())
+ {
+ if (millisecondsTimeout == 0)
+ {
+ return false;
+ }
+
+ spinner.SpinOnce();
+
+ if (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield)
+ {
+ if (millisecondsTimeout <= (TimeoutHelper.GetTime() - startTime))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ #endregion
+ }
+
+ /// <summary>
+ /// A helper class to get the number of processors, it updates the numbers of processors every sampling interval.
+ /// </summary>
+ internal static class PlatformHelper
+ {
+ private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; // How often to refresh the count, in milliseconds.
+ private static volatile int s_processorCount; // The last count seen.
+ private static volatile int s_lastProcessorCountRefreshTicks; // The last time we refreshed.
+
+ /// <summary>
+ /// Gets the number of available processors
+ /// </summary>
+ internal static int ProcessorCount
+ {
+ get
+ {
+ int now = Environment.TickCount;
+ int procCount = s_processorCount;
+ if (procCount == 0 || (now - s_lastProcessorCountRefreshTicks) >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS)
+ {
+ s_processorCount = procCount = Environment.ProcessorCount;
+ s_lastProcessorCountRefreshTicks = now;
+ }
+
+ Debug.Assert(procCount > 0,
+ "Processor count should be greater than 0.");
+
+ return procCount;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether the current machine has only a single processor.
+ /// </summary>
+ /// <remarks>This typically does not change on a machine, so it's checked only once.</remarks>
+ internal static readonly bool IsSingleProcessor = ProcessorCount == 1;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/SynchronizationLockException.cs b/src/System.Private.CoreLib/shared/System/Threading/SynchronizationLockException.cs
new file mode 100644
index 0000000000..e050e1539d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/SynchronizationLockException.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Wait(), Notify() or NotifyAll() was called from an unsynchronized
+** block of code.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class SynchronizationLockException : SystemException
+ {
+ public SynchronizationLockException()
+ : base(SR.Arg_SynchronizationLockException)
+ {
+ HResult = HResults.COR_E_SYNCHRONIZATIONLOCK;
+ }
+
+ public SynchronizationLockException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_SYNCHRONIZATIONLOCK;
+ }
+
+ public SynchronizationLockException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_SYNCHRONIZATIONLOCK;
+ }
+
+ protected SynchronizationLockException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs
new file mode 100644
index 0000000000..6a6483f9af
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs
@@ -0,0 +1,750 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+//
+//
+// A pair of schedulers that together support concurrent (reader) / exclusive (writer)
+// task scheduling. Using just the exclusive scheduler can be used to simulate a serial
+// processing queue, and using just the concurrent scheduler with a specified
+// MaximumConcurrentlyLevel can be used to achieve a MaxDegreeOfParallelism across
+// a bunch of tasks, parallel loops, dataflow blocks, etc.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Threading.Tasks
+{
+ /// <summary>
+ /// Provides concurrent and exclusive task schedulers that coordinate to execute
+ /// tasks while ensuring that concurrent tasks may run concurrently and exclusive tasks never do.
+ /// </summary>
+ [DebuggerDisplay("Concurrent={ConcurrentTaskCountForDebugger}, Exclusive={ExclusiveTaskCountForDebugger}, Mode={ModeForDebugger}")]
+ [DebuggerTypeProxy(typeof(ConcurrentExclusiveSchedulerPair.DebugView))]
+ public class ConcurrentExclusiveSchedulerPair
+ {
+ /// <summary>A processing mode to denote what kinds of tasks are currently being processed on this thread.</summary>
+ private readonly ThreadLocal<ProcessingMode> m_threadProcessingMode = new ThreadLocal<ProcessingMode>();
+ /// <summary>The scheduler used to queue and execute "concurrent" tasks that may run concurrently with other concurrent tasks.</summary>
+ private readonly ConcurrentExclusiveTaskScheduler m_concurrentTaskScheduler;
+ /// <summary>The scheduler used to queue and execute "exclusive" tasks that must run exclusively while no other tasks for this pair are running.</summary>
+ private readonly ConcurrentExclusiveTaskScheduler m_exclusiveTaskScheduler;
+ /// <summary>The underlying task scheduler to which all work should be scheduled.</summary>
+ private readonly TaskScheduler m_underlyingTaskScheduler;
+ /// <summary>
+ /// The maximum number of tasks allowed to run concurrently. This only applies to concurrent tasks,
+ /// since exclusive tasks are inherently limited to 1.
+ /// </summary>
+ private readonly int m_maxConcurrencyLevel;
+ /// <summary>The maximum number of tasks we can process before recycling our runner tasks.</summary>
+ private readonly int m_maxItemsPerTask;
+ /// <summary>
+ /// If positive, it represents the number of concurrently running concurrent tasks.
+ /// If negative, it means an exclusive task has been scheduled.
+ /// If 0, nothing has been scheduled.
+ /// </summary>
+ private int m_processingCount;
+ /// <summary>Completion state for a task representing the completion of this pair.</summary>
+ /// <remarks>Lazily-initialized only if the scheduler pair is shutting down or if the Completion is requested.</remarks>
+ private CompletionState m_completionState;
+
+ /// <summary>A constant value used to signal unlimited processing.</summary>
+ private const int UNLIMITED_PROCESSING = -1;
+ /// <summary>Constant used for m_processingCount to indicate that an exclusive task is being processed.</summary>
+ private const int EXCLUSIVE_PROCESSING_SENTINEL = -1;
+ /// <summary>Default MaxItemsPerTask to use for processing if none is specified.</summary>
+ private const int DEFAULT_MAXITEMSPERTASK = UNLIMITED_PROCESSING;
+ /// <summary>Default MaxConcurrencyLevel is the processor count if not otherwise specified.</summary>
+ private static Int32 DefaultMaxConcurrencyLevel { get { return Environment.ProcessorCount; } }
+
+ /// <summary>Gets the sync obj used to protect all state on this instance.</summary>
+ private object ValueLock { get { return m_threadProcessingMode; } }
+
+ /// <summary>
+ /// Initializes the ConcurrentExclusiveSchedulerPair.
+ /// </summary>
+ public ConcurrentExclusiveSchedulerPair() :
+ this(TaskScheduler.Default, DefaultMaxConcurrencyLevel, DEFAULT_MAXITEMSPERTASK)
+ { }
+
+ /// <summary>
+ /// Initializes the ConcurrentExclusiveSchedulerPair to target the specified scheduler.
+ /// </summary>
+ /// <param name="taskScheduler">The target scheduler on which this pair should execute.</param>
+ public ConcurrentExclusiveSchedulerPair(TaskScheduler taskScheduler) :
+ this(taskScheduler, DefaultMaxConcurrencyLevel, DEFAULT_MAXITEMSPERTASK)
+ { }
+
+ /// <summary>
+ /// Initializes the ConcurrentExclusiveSchedulerPair to target the specified scheduler with a maximum concurrency level.
+ /// </summary>
+ /// <param name="taskScheduler">The target scheduler on which this pair should execute.</param>
+ /// <param name="maxConcurrencyLevel">The maximum number of tasks to run concurrently.</param>
+ public ConcurrentExclusiveSchedulerPair(TaskScheduler taskScheduler, int maxConcurrencyLevel) :
+ this(taskScheduler, maxConcurrencyLevel, DEFAULT_MAXITEMSPERTASK)
+ { }
+
+ /// <summary>
+ /// Initializes the ConcurrentExclusiveSchedulerPair to target the specified scheduler with a maximum
+ /// concurrency level and a maximum number of scheduled tasks that may be processed as a unit.
+ /// </summary>
+ /// <param name="taskScheduler">The target scheduler on which this pair should execute.</param>
+ /// <param name="maxConcurrencyLevel">The maximum number of tasks to run concurrently.</param>
+ /// <param name="maxItemsPerTask">The maximum number of tasks to process for each underlying scheduled task used by the pair.</param>
+ public ConcurrentExclusiveSchedulerPair(TaskScheduler taskScheduler, int maxConcurrencyLevel, int maxItemsPerTask)
+ {
+ // Validate arguments
+ if (taskScheduler == null) throw new ArgumentNullException(nameof(taskScheduler));
+ if (maxConcurrencyLevel == 0 || maxConcurrencyLevel < -1) throw new ArgumentOutOfRangeException(nameof(maxConcurrencyLevel));
+ if (maxItemsPerTask == 0 || maxItemsPerTask < -1) throw new ArgumentOutOfRangeException(nameof(maxItemsPerTask));
+
+ // Store configuration
+ m_underlyingTaskScheduler = taskScheduler;
+ m_maxConcurrencyLevel = maxConcurrencyLevel;
+ m_maxItemsPerTask = maxItemsPerTask;
+
+ // Downgrade to the underlying scheduler's max degree of parallelism if it's lower than the user-supplied level
+ int mcl = taskScheduler.MaximumConcurrencyLevel;
+ if (mcl > 0 && mcl < m_maxConcurrencyLevel) m_maxConcurrencyLevel = mcl;
+
+ // Treat UNLIMITED_PROCESSING/-1 for both MCL and MIPT as the biggest possible value so that we don't
+ // have to special case UNLIMITED_PROCESSING later on in processing.
+ if (m_maxConcurrencyLevel == UNLIMITED_PROCESSING) m_maxConcurrencyLevel = Int32.MaxValue;
+ if (m_maxItemsPerTask == UNLIMITED_PROCESSING) m_maxItemsPerTask = Int32.MaxValue;
+
+ // Create the concurrent/exclusive schedulers for this pair
+ m_exclusiveTaskScheduler = new ConcurrentExclusiveTaskScheduler(this, 1, ProcessingMode.ProcessingExclusiveTask);
+ m_concurrentTaskScheduler = new ConcurrentExclusiveTaskScheduler(this, m_maxConcurrencyLevel, ProcessingMode.ProcessingConcurrentTasks);
+ }
+
+ /// <summary>Informs the scheduler pair that it should not accept any more tasks.</summary>
+ /// <remarks>
+ /// Calling <see cref="Complete"/> is optional, and it's only necessary if the <see cref="Completion"/>
+ /// will be relied on for notification of all processing being completed.
+ /// </remarks>
+ public void Complete()
+ {
+ lock (ValueLock)
+ {
+ if (!CompletionRequested)
+ {
+ RequestCompletion();
+ CleanupStateIfCompletingAndQuiesced();
+ }
+ }
+ }
+
+ /// <summary>Gets a <see cref="System.Threading.Tasks.Task"/> that will complete when the scheduler has completed processing.</summary>
+ public Task Completion
+ {
+ // ValueLock not needed, but it's ok if it's held
+ get { return EnsureCompletionStateInitialized().Task; }
+ }
+
+ /// <summary>Gets the lazily-initialized completion state.</summary>
+ private CompletionState EnsureCompletionStateInitialized()
+ {
+ // ValueLock not needed, but it's ok if it's held
+ return LazyInitializer.EnsureInitialized(ref m_completionState, () => new CompletionState());
+ }
+
+ /// <summary>Gets whether completion has been requested.</summary>
+ private bool CompletionRequested
+ {
+ // ValueLock not needed, but it's ok if it's held
+ get { return m_completionState != null && Volatile.Read(ref m_completionState.m_completionRequested); }
+ }
+
+ /// <summary>Sets that completion has been requested.</summary>
+ private void RequestCompletion()
+ {
+ ContractAssertMonitorStatus(ValueLock, held: true);
+ EnsureCompletionStateInitialized().m_completionRequested = true;
+ }
+
+ /// <summary>
+ /// Cleans up state if and only if there's no processing currently happening
+ /// and no more to be done later.
+ /// </summary>
+ private void CleanupStateIfCompletingAndQuiesced()
+ {
+ ContractAssertMonitorStatus(ValueLock, held: true);
+ if (ReadyToComplete) CompleteTaskAsync();
+ }
+
+ /// <summary>Gets whether the pair is ready to complete.</summary>
+ private bool ReadyToComplete
+ {
+ get
+ {
+ ContractAssertMonitorStatus(ValueLock, held: true);
+
+ // We can only complete if completion has been requested and no processing is currently happening.
+ if (!CompletionRequested || m_processingCount != 0) return false;
+
+ // Now, only allow shutdown if an exception occurred or if there are no more tasks to process.
+ var cs = EnsureCompletionStateInitialized();
+ return
+ (cs.m_exceptions != null && cs.m_exceptions.Count > 0) ||
+ (m_concurrentTaskScheduler.m_tasks.IsEmpty && m_exclusiveTaskScheduler.m_tasks.IsEmpty);
+ }
+ }
+
+ /// <summary>Completes the completion task asynchronously.</summary>
+ private void CompleteTaskAsync()
+ {
+ Debug.Assert(ReadyToComplete, "The block must be ready to complete to be here.");
+ ContractAssertMonitorStatus(ValueLock, held: true);
+
+ // Ensure we only try to complete once, then schedule completion
+ // in order to escape held locks and the caller's context
+ var cs = EnsureCompletionStateInitialized();
+ if (!cs.m_completionQueued)
+ {
+ cs.m_completionQueued = true;
+ ThreadPool.QueueUserWorkItem(state =>
+ {
+ var localThis = (ConcurrentExclusiveSchedulerPair)state;
+ Debug.Assert(!localThis.m_completionState.Task.IsCompleted, "Completion should only happen once.");
+
+ List<Exception> exceptions = localThis.m_completionState.m_exceptions;
+ bool success = (exceptions != null && exceptions.Count > 0) ?
+ localThis.m_completionState.TrySetException(exceptions) :
+ localThis.m_completionState.TrySetResult(default);
+ Debug.Assert(success, "Expected to complete completion task.");
+
+ localThis.m_threadProcessingMode.Dispose();
+ }, this);
+ }
+ }
+
+ /// <summary>Initiates scheduler shutdown due to a worker task faulting.</summary>
+ /// <param name="faultedTask">The faulted worker task that's initiating the shutdown.</param>
+ private void FaultWithTask(Task faultedTask)
+ {
+ Debug.Assert(faultedTask != null && faultedTask.IsFaulted && faultedTask.Exception.InnerExceptions.Count > 0,
+ "Needs a task in the faulted state and thus with exceptions.");
+ ContractAssertMonitorStatus(ValueLock, held: true);
+
+ // Store the faulted task's exceptions
+ var cs = EnsureCompletionStateInitialized();
+ if (cs.m_exceptions == null) cs.m_exceptions = new List<Exception>();
+ cs.m_exceptions.AddRange(faultedTask.Exception.InnerExceptions);
+
+ // Now that we're doomed, request completion
+ RequestCompletion();
+ }
+
+ /// <summary>
+ /// Gets a TaskScheduler that can be used to schedule tasks to this pair
+ /// that may run concurrently with other tasks on this pair.
+ /// </summary>
+ public TaskScheduler ConcurrentScheduler { get { return m_concurrentTaskScheduler; } }
+ /// <summary>
+ /// Gets a TaskScheduler that can be used to schedule tasks to this pair
+ /// that must run exclusively with regards to other tasks on this pair.
+ /// </summary>
+ public TaskScheduler ExclusiveScheduler { get { return m_exclusiveTaskScheduler; } }
+
+ /// <summary>Gets the number of tasks waiting to run concurrently.</summary>
+ /// <remarks>This does not take the necessary lock, as it's only called from under the debugger.</remarks>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ private int ConcurrentTaskCountForDebugger { get { return m_concurrentTaskScheduler.m_tasks.Count; } }
+
+ /// <summary>Gets the number of tasks waiting to run exclusively.</summary>
+ /// <remarks>This does not take the necessary lock, as it's only called from under the debugger.</remarks>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ private int ExclusiveTaskCountForDebugger { get { return m_exclusiveTaskScheduler.m_tasks.Count; } }
+
+ /// <summary>Notifies the pair that new work has arrived to be processed.</summary>
+ /// <param name="fairly">Whether tasks should be scheduled fairly with regards to other tasks.</param>
+ /// <remarks>Must only be called while holding the lock.</remarks>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals")]
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ private void ProcessAsyncIfNecessary(bool fairly = false)
+ {
+ ContractAssertMonitorStatus(ValueLock, held: true);
+
+ // If the current processing count is >= 0, we can potentially launch further processing.
+ if (m_processingCount >= 0)
+ {
+ // We snap whether there are any exclusive tasks or concurrent tasks waiting.
+ // (We grab the concurrent count below only once we know we need it.)
+ // With processing happening concurrent to this operation, this data may
+ // immediately be out of date, but it can only go from non-empty
+ // to empty and not the other way around. As such, this is safe,
+ // as worst case is we'll schedule an extra task when we didn't
+ // otherwise need to, and we'll just eat its overhead.
+ bool exclusiveTasksAreWaiting = !m_exclusiveTaskScheduler.m_tasks.IsEmpty;
+
+ // If there's no processing currently happening but there are waiting exclusive tasks,
+ // let's start processing those exclusive tasks.
+ Task processingTask = null;
+ if (m_processingCount == 0 && exclusiveTasksAreWaiting)
+ {
+ // Launch exclusive task processing
+ m_processingCount = EXCLUSIVE_PROCESSING_SENTINEL; // -1
+ try
+ {
+ processingTask = new Task(thisPair => ((ConcurrentExclusiveSchedulerPair)thisPair).ProcessExclusiveTasks(), this,
+ default(CancellationToken), GetCreationOptionsForTask(fairly));
+ processingTask.Start(m_underlyingTaskScheduler);
+ // When we call Start, if the underlying scheduler throws in QueueTask, TPL will fault the task and rethrow
+ // the exception. To deal with that, we need a reference to the task object, so that we can observe its exception.
+ // Hence, we separate creation and starting, so that we can store a reference to the task before we attempt QueueTask.
+ }
+ catch
+ {
+ m_processingCount = 0;
+ FaultWithTask(processingTask);
+ }
+ }
+ // If there are no waiting exclusive tasks, there are concurrent tasks, and we haven't reached our maximum
+ // concurrency level for processing, let's start processing more concurrent tasks.
+ else
+ {
+ int concurrentTasksWaitingCount = m_concurrentTaskScheduler.m_tasks.Count;
+
+ if (concurrentTasksWaitingCount > 0 && !exclusiveTasksAreWaiting && m_processingCount < m_maxConcurrencyLevel)
+ {
+ // Launch concurrent task processing, up to the allowed limit
+ for (int i = 0; i < concurrentTasksWaitingCount && m_processingCount < m_maxConcurrencyLevel; ++i)
+ {
+ ++m_processingCount;
+ try
+ {
+ processingTask = new Task(thisPair => ((ConcurrentExclusiveSchedulerPair)thisPair).ProcessConcurrentTasks(), this,
+ default(CancellationToken), GetCreationOptionsForTask(fairly));
+ processingTask.Start(m_underlyingTaskScheduler); // See above logic for why we use new + Start rather than StartNew
+ }
+ catch
+ {
+ --m_processingCount;
+ FaultWithTask(processingTask);
+ }
+ }
+ }
+ }
+
+ // Check to see if all tasks have completed and if completion has been requested.
+ CleanupStateIfCompletingAndQuiesced();
+ }
+ else Debug.Assert(m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL, "The processing count must be the sentinel if it's not >= 0.");
+ }
+
+ /// <summary>
+ /// Processes exclusive tasks serially until either there are no more to process
+ /// or we've reached our user-specified maximum limit.
+ /// </summary>
+ private void ProcessExclusiveTasks()
+ {
+ Debug.Assert(m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL, "Processing exclusive tasks requires being in exclusive mode.");
+ Debug.Assert(!m_exclusiveTaskScheduler.m_tasks.IsEmpty, "Processing exclusive tasks requires tasks to be processed.");
+ ContractAssertMonitorStatus(ValueLock, held: false);
+ try
+ {
+ // Note that we're processing exclusive tasks on the current thread
+ Debug.Assert(m_threadProcessingMode.Value == ProcessingMode.NotCurrentlyProcessing,
+ "This thread should not yet be involved in this pair's processing.");
+ m_threadProcessingMode.Value = ProcessingMode.ProcessingExclusiveTask;
+
+ // Process up to the maximum number of items per task allowed
+ for (int i = 0; i < m_maxItemsPerTask; i++)
+ {
+ // Get the next available exclusive task. If we can't find one, bail.
+ Task exclusiveTask;
+ if (!m_exclusiveTaskScheduler.m_tasks.TryDequeue(out exclusiveTask)) break;
+
+ // Execute the task. If the scheduler was previously faulted,
+ // this task could have been faulted when it was queued; ignore such tasks.
+ if (!exclusiveTask.IsFaulted) m_exclusiveTaskScheduler.ExecuteTask(exclusiveTask);
+ }
+ }
+ finally
+ {
+ // We're no longer processing exclusive tasks on the current thread
+ Debug.Assert(m_threadProcessingMode.Value == ProcessingMode.ProcessingExclusiveTask,
+ "Somehow we ended up escaping exclusive mode.");
+ m_threadProcessingMode.Value = ProcessingMode.NotCurrentlyProcessing;
+
+ lock (ValueLock)
+ {
+ // When this task was launched, we tracked it by setting m_processingCount to WRITER_IN_PROGRESS.
+ // now reset it to 0. Then check to see whether there's more processing to be done.
+ // There might be more concurrent tasks available, for example, if concurrent tasks arrived
+ // after we exited the loop, or if we exited the loop while concurrent tasks were still
+ // available but we hit our maxItemsPerTask limit.
+ Debug.Assert(m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL, "The processing mode should not have deviated from exclusive.");
+ m_processingCount = 0;
+ ProcessAsyncIfNecessary(true);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Processes concurrent tasks serially until either there are no more to process,
+ /// we've reached our user-specified maximum limit, or exclusive tasks have arrived.
+ /// </summary>
+ private void ProcessConcurrentTasks()
+ {
+ Debug.Assert(m_processingCount > 0, "Processing concurrent tasks requires us to be in concurrent mode.");
+ ContractAssertMonitorStatus(ValueLock, held: false);
+ try
+ {
+ // Note that we're processing concurrent tasks on the current thread
+ Debug.Assert(m_threadProcessingMode.Value == ProcessingMode.NotCurrentlyProcessing,
+ "This thread should not yet be involved in this pair's processing.");
+ m_threadProcessingMode.Value = ProcessingMode.ProcessingConcurrentTasks;
+
+ // Process up to the maximum number of items per task allowed
+ for (int i = 0; i < m_maxItemsPerTask; i++)
+ {
+ // Get the next available concurrent task. If we can't find one, bail.
+ Task concurrentTask;
+ if (!m_concurrentTaskScheduler.m_tasks.TryDequeue(out concurrentTask)) break;
+
+ // Execute the task. If the scheduler was previously faulted,
+ // this task could have been faulted when it was queued; ignore such tasks.
+ if (!concurrentTask.IsFaulted) m_concurrentTaskScheduler.ExecuteTask(concurrentTask);
+
+ // Now check to see if exclusive tasks have arrived; if any have, they take priority
+ // so we'll bail out here. Note that we could have checked this condition
+ // in the for loop's condition, but that could lead to extra overhead
+ // in the case where a concurrent task arrives, this task is launched, and then
+ // before entering the loop an exclusive task arrives. If we didn't execute at
+ // least one task, we would have spent all of the overhead to launch a
+ // task but with none of the benefit. There's of course also an inherent
+ // race condition here with regards to exclusive tasks arriving, and we're ok with
+ // executing one more concurrent task than we should before giving priority to exclusive tasks.
+ if (!m_exclusiveTaskScheduler.m_tasks.IsEmpty) break;
+ }
+ }
+ finally
+ {
+ // We're no longer processing concurrent tasks on the current thread
+ Debug.Assert(m_threadProcessingMode.Value == ProcessingMode.ProcessingConcurrentTasks,
+ "Somehow we ended up escaping concurrent mode.");
+ m_threadProcessingMode.Value = ProcessingMode.NotCurrentlyProcessing;
+
+ lock (ValueLock)
+ {
+ // When this task was launched, we tracked it with a positive processing count;
+ // decrement that count. Then check to see whether there's more processing to be done.
+ // There might be more concurrent tasks available, for example, if concurrent tasks arrived
+ // after we exited the loop, or if we exited the loop while concurrent tasks were still
+ // available but we hit our maxItemsPerTask limit.
+ Debug.Assert(m_processingCount > 0, "The procesing mode should not have deviated from concurrent.");
+ if (m_processingCount > 0) --m_processingCount;
+ ProcessAsyncIfNecessary(true);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Holder for lazily-initialized state about the completion of a scheduler pair.
+ /// Completion is only triggered either by rare exceptional conditions or by
+ /// the user calling Complete, and as such we only lazily initialize this
+ /// state in one of those conditions or if the user explicitly asks for
+ /// the Completion.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
+ private sealed class CompletionState : TaskCompletionSource<VoidTaskResult>
+ {
+ /// <summary>Whether the scheduler has had completion requested.</summary>
+ /// <remarks>This variable is not volatile, so to gurantee safe reading reads, Volatile.Read is used in TryExecuteTaskInline.</remarks>
+ internal bool m_completionRequested;
+ /// <summary>Whether completion processing has been queued.</summary>
+ internal bool m_completionQueued;
+ /// <summary>Unrecoverable exceptions incurred while processing.</summary>
+ internal List<Exception> m_exceptions;
+ }
+
+ /// <summary>
+ /// A scheduler shim used to queue tasks to the pair and execute those tasks on request of the pair.
+ /// </summary>
+ [DebuggerDisplay("Count={CountForDebugger}, MaxConcurrencyLevel={m_maxConcurrencyLevel}, Id={Id}")]
+ [DebuggerTypeProxy(typeof(ConcurrentExclusiveTaskScheduler.DebugView))]
+ private sealed class ConcurrentExclusiveTaskScheduler : TaskScheduler
+ {
+ /// <summary>Cached delegate for invoking TryExecuteTaskShim.</summary>
+ private static readonly Func<object, bool> s_tryExecuteTaskShim = new Func<object, bool>(TryExecuteTaskShim);
+ /// <summary>The parent pair.</summary>
+ private readonly ConcurrentExclusiveSchedulerPair m_pair;
+ /// <summary>The maximum concurrency level for the scheduler.</summary>
+ private readonly int m_maxConcurrencyLevel;
+ /// <summary>The processing mode of this scheduler, exclusive or concurrent.</summary>
+ private readonly ProcessingMode m_processingMode;
+ /// <summary>Gets the queue of tasks for this scheduler.</summary>
+ internal readonly IProducerConsumerQueue<Task> m_tasks;
+
+ /// <summary>Initializes the scheduler.</summary>
+ /// <param name="pair">The parent pair.</param>
+ /// <param name="maxConcurrencyLevel">The maximum degree of concurrency this scheduler may use.</param>
+ /// <param name="processingMode">The processing mode of this scheduler.</param>
+ internal ConcurrentExclusiveTaskScheduler(ConcurrentExclusiveSchedulerPair pair, int maxConcurrencyLevel, ProcessingMode processingMode)
+ {
+ Debug.Assert(pair != null, "Scheduler must be associated with a valid pair.");
+ Debug.Assert(processingMode == ProcessingMode.ProcessingConcurrentTasks || processingMode == ProcessingMode.ProcessingExclusiveTask,
+ "Scheduler must be for concurrent or exclusive processing.");
+ Debug.Assert(
+ (processingMode == ProcessingMode.ProcessingConcurrentTasks && (maxConcurrencyLevel >= 1 || maxConcurrencyLevel == UNLIMITED_PROCESSING)) ||
+ (processingMode == ProcessingMode.ProcessingExclusiveTask && maxConcurrencyLevel == 1),
+ "If we're in concurrent mode, our concurrency level should be positive or unlimited. If exclusive, it should be 1.");
+
+ m_pair = pair;
+ m_maxConcurrencyLevel = maxConcurrencyLevel;
+ m_processingMode = processingMode;
+ m_tasks = (processingMode == ProcessingMode.ProcessingExclusiveTask) ?
+ (IProducerConsumerQueue<Task>)new SingleProducerSingleConsumerQueue<Task>() :
+ (IProducerConsumerQueue<Task>)new MultiProducerMultiConsumerQueue<Task>();
+ }
+
+ /// <summary>Gets the maximum concurrency level this scheduler is able to support.</summary>
+ public override int MaximumConcurrencyLevel { get { return m_maxConcurrencyLevel; } }
+
+ /// <summary>Queues a task to the scheduler.</summary>
+ /// <param name="task">The task to be queued.</param>
+ protected internal override void QueueTask(Task task)
+ {
+ Debug.Assert(task != null, "Infrastructure should have provided a non-null task.");
+ lock (m_pair.ValueLock)
+ {
+ // If the scheduler has already had completion requested, no new work is allowed to be scheduled
+ if (m_pair.CompletionRequested) throw new InvalidOperationException(GetType().ToString());
+
+ // Queue the task, and then let the pair know that more work is now available to be scheduled
+ m_tasks.Enqueue(task);
+ m_pair.ProcessAsyncIfNecessary();
+ }
+ }
+
+ /// <summary>Executes a task on this scheduler.</summary>
+ /// <param name="task">The task to be executed.</param>
+ internal void ExecuteTask(Task task)
+ {
+ Debug.Assert(task != null, "Infrastructure should have provided a non-null task.");
+ base.TryExecuteTask(task);
+ }
+
+ /// <summary>Tries to execute the task synchronously on this scheduler.</summary>
+ /// <param name="task">The task to execute.</param>
+ /// <param name="taskWasPreviouslyQueued">Whether the task was previously queued to the scheduler.</param>
+ /// <returns>true if the task could be executed; otherwise, false.</returns>
+ protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
+ {
+ Debug.Assert(task != null, "Infrastructure should have provided a non-null task.");
+
+ // If the scheduler has had completion requested, no new work is allowed to be scheduled.
+ // A non-locked read on m_completionRequested (in CompletionRequested) is acceptable here because:
+ // a) we don't need to be exact... a Complete call could come in later in the function anyway
+ // b) this is only a fast path escape hatch. To actually inline the task,
+ // we need to be inside of an already executing task, and in such a case,
+ // while completion may have been requested, we can't have shutdown yet.
+ if (!taskWasPreviouslyQueued && m_pair.CompletionRequested) return false;
+
+ // We know the implementation of the default scheduler and how it will behave.
+ // As it's the most common underlying scheduler, we optimize for it.
+ bool isDefaultScheduler = m_pair.m_underlyingTaskScheduler == TaskScheduler.Default;
+
+ // If we're targeting the default scheduler and taskWasPreviouslyQueued is true,
+ // we know that the default scheduler will only allow it to be inlined
+ // if we're on a thread pool thread (but it won't always allow it in that case,
+ // since it'll only allow inlining if it can find the task in the local queue).
+ // As such, if we're not on a thread pool thread, we know for sure the
+ // task won't be inlined, so let's not even try.
+ if (isDefaultScheduler && taskWasPreviouslyQueued && !Thread.CurrentThread.IsThreadPoolThread)
+ {
+ return false;
+ }
+ else
+ {
+ // If a task is already running on this thread, allow inline execution to proceed.
+ // If there's already a task from this scheduler running on the current thread, we know it's safe
+ // to run this task, in effect temporarily taking that task's count allocation.
+ if (m_pair.m_threadProcessingMode.Value == m_processingMode)
+ {
+ // If we're targeting the default scheduler and taskWasPreviouslyQueued is false,
+ // we know the default scheduler will allow it, so we can just execute it here.
+ // Otherwise, delegate to the target scheduler's inlining.
+ return (isDefaultScheduler && !taskWasPreviouslyQueued) ?
+ TryExecuteTask(task) :
+ TryExecuteTaskInlineOnTargetScheduler(task);
+ }
+ }
+
+ // We're not in the context of a task already executing on this scheduler. Bail.
+ return false;
+ }
+
+ /// <summary>
+ /// Implements a reasonable approximation for TryExecuteTaskInline on the underlying scheduler,
+ /// which we can't call directly on the underlying scheduler.
+ /// </summary>
+ /// <param name="task">The task to execute inline if possible.</param>
+ /// <returns>true if the task was inlined successfully; otherwise, false.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "ignored")]
+ private bool TryExecuteTaskInlineOnTargetScheduler(Task task)
+ {
+ // We'd like to simply call TryExecuteTaskInline here, but we can't.
+ // As there's no built-in API for this, a workaround is to create a new task that,
+ // when executed, will simply call TryExecuteTask to run the real task, and then
+ // we run our new shim task synchronously on the target scheduler. If all goes well,
+ // our synchronous invocation will succeed in running the shim task on the current thread,
+ // which will in turn run the real task on the current thread. If the scheduler
+ // doesn't allow that execution, RunSynchronously will block until the underlying scheduler
+ // is able to invoke the task, which might account for an additional but unavoidable delay.
+ // Once it's done, we can return whether the task executed by returning the
+ // shim task's Result, which is in turn the result of TryExecuteTask.
+ var t = new Task<bool>(s_tryExecuteTaskShim, Tuple.Create(this, task));
+ try
+ {
+ t.RunSynchronously(m_pair.m_underlyingTaskScheduler);
+ return t.Result;
+ }
+ catch
+ {
+ Debug.Assert(t.IsFaulted, "Task should be faulted due to the scheduler faulting it and throwing the exception.");
+ var ignored = t.Exception;
+ throw;
+ }
+ finally { t.Dispose(); }
+ }
+
+ /// <summary>Shim used to invoke this.TryExecuteTask(task).</summary>
+ /// <param name="state">A tuple of the ConcurrentExclusiveTaskScheduler and the task to execute.</param>
+ /// <returns>true if the task was successfully inlined; otherwise, false.</returns>
+ /// <remarks>
+ /// This method is separated out not because of performance reasons but so that
+ /// the SecuritySafeCritical attribute may be employed.
+ /// </remarks>
+ private static bool TryExecuteTaskShim(object state)
+ {
+ var tuple = (Tuple<ConcurrentExclusiveTaskScheduler, Task>)state;
+ return tuple.Item1.TryExecuteTask(tuple.Item2);
+ }
+
+ /// <summary>Gets for debugging purposes the tasks scheduled to this scheduler.</summary>
+ /// <returns>An enumerable of the tasks queued.</returns>
+ protected override IEnumerable<Task> GetScheduledTasks() { return m_tasks; }
+
+ /// <summary>Gets the number of tasks queued to this scheduler.</summary>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ private int CountForDebugger { get { return m_tasks.Count; } }
+
+ /// <summary>Provides a debug view for ConcurrentExclusiveTaskScheduler.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The scheduler being debugged.</summary>
+ private readonly ConcurrentExclusiveTaskScheduler m_taskScheduler;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="scheduler">The scheduler being debugged.</param>
+ public DebugView(ConcurrentExclusiveTaskScheduler scheduler)
+ {
+ Debug.Assert(scheduler != null, "Need a scheduler with which to construct the debug view.");
+ m_taskScheduler = scheduler;
+ }
+
+ /// <summary>Gets this pair's maximum allowed concurrency level.</summary>
+ public int MaximumConcurrencyLevel { get { return m_taskScheduler.m_maxConcurrencyLevel; } }
+ /// <summary>Gets the tasks scheduled to this scheduler.</summary>
+ public IEnumerable<Task> ScheduledTasks { get { return m_taskScheduler.m_tasks; } }
+ /// <summary>Gets the scheduler pair with which this scheduler is associated.</summary>
+ public ConcurrentExclusiveSchedulerPair SchedulerPair { get { return m_taskScheduler.m_pair; } }
+ }
+ }
+
+ /// <summary>Provides a debug view for ConcurrentExclusiveSchedulerPair.</summary>
+ private sealed class DebugView
+ {
+ /// <summary>The pair being debugged.</summary>
+ private readonly ConcurrentExclusiveSchedulerPair m_pair;
+
+ /// <summary>Initializes the debug view.</summary>
+ /// <param name="pair">The pair being debugged.</param>
+ public DebugView(ConcurrentExclusiveSchedulerPair pair)
+ {
+ Debug.Assert(pair != null, "Need a pair with which to construct the debug view.");
+ m_pair = pair;
+ }
+
+ /// <summary>Gets a representation of the execution state of the pair.</summary>
+ public ProcessingMode Mode { get { return m_pair.ModeForDebugger; } }
+ /// <summary>Gets the number of tasks waiting to run exclusively.</summary>
+ public IEnumerable<Task> ScheduledExclusive { get { return m_pair.m_exclusiveTaskScheduler.m_tasks; } }
+ /// <summary>Gets the number of tasks waiting to run concurrently.</summary>
+ public IEnumerable<Task> ScheduledConcurrent { get { return m_pair.m_concurrentTaskScheduler.m_tasks; } }
+ /// <summary>Gets the number of tasks currently being executed.</summary>
+ public int CurrentlyExecutingTaskCount
+ {
+ get { return (m_pair.m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL) ? 1 : m_pair.m_processingCount; }
+ }
+ /// <summary>Gets the underlying task scheduler that actually executes the tasks.</summary>
+ public TaskScheduler TargetScheduler { get { return m_pair.m_underlyingTaskScheduler; } }
+ }
+
+ /// <summary>Gets an enumeration for debugging that represents the current state of the scheduler pair.</summary>
+ /// <remarks>This is only for debugging. It does not take the necessary locks to be useful for runtime usage.</remarks>
+ private ProcessingMode ModeForDebugger
+ {
+ get
+ {
+ // If our completion task is done, so are we.
+ if (m_completionState != null && m_completionState.Task.IsCompleted) return ProcessingMode.Completed;
+
+ // Otherwise, summarize our current state.
+ var mode = ProcessingMode.NotCurrentlyProcessing;
+ if (m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL) mode |= ProcessingMode.ProcessingExclusiveTask;
+ if (m_processingCount >= 1) mode |= ProcessingMode.ProcessingConcurrentTasks;
+ if (CompletionRequested) mode |= ProcessingMode.Completing;
+ return mode;
+ }
+ }
+
+ /// <summary>Asserts that a given synchronization object is either held or not held.</summary>
+ /// <param name="syncObj">The monitor to check.</param>
+ /// <param name="held">Whether we want to assert that it's currently held or not held.</param>
+ [Conditional("DEBUG")]
+ private static void ContractAssertMonitorStatus(object syncObj, bool held)
+ {
+ Debug.Assert(syncObj != null, "The monitor object to check must be provided.");
+ Debug.Assert(Monitor.IsEntered(syncObj) == held, "The locking scheme was not correctly followed.");
+ }
+
+ /// <summary>Gets the options to use for tasks.</summary>
+ /// <param name="isReplacementReplica">If this task is being created to replace another.</param>
+ /// <remarks>
+ /// These options should be used for all tasks that have the potential to run user code or
+ /// that are repeatedly spawned and thus need a modicum of fair treatment.
+ /// </remarks>
+ /// <returns>The options to use.</returns>
+ internal static TaskCreationOptions GetCreationOptionsForTask(bool isReplacementReplica = false)
+ {
+ TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
+ if (isReplacementReplica) options |= TaskCreationOptions.PreferFairness;
+ return options;
+ }
+
+ /// <summary>Provides an enumeration that represents the current state of the scheduler pair.</summary>
+ [Flags]
+ private enum ProcessingMode : byte
+ {
+ /// <summary>The scheduler pair is currently dormant, with no work scheduled.</summary>
+ NotCurrentlyProcessing = 0x0,
+ /// <summary>The scheduler pair has queued processing for exclusive tasks.</summary>
+ ProcessingExclusiveTask = 0x1,
+ /// <summary>The scheduler pair has queued processing for concurrent tasks.</summary>
+ ProcessingConcurrentTasks = 0x2,
+ /// <summary>Completion has been requested.</summary>
+ Completing = 0x4,
+ /// <summary>The scheduler pair is finished processing.</summary>
+ Completed = 0x8
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs
new file mode 100644
index 0000000000..e411146a1d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Threading.Tasks.Sources
+{
+ /// <summary>
+ /// Flags passed from <see cref="ValueTask"/> and <see cref="ValueTask{TResult}"/> to
+ /// <see cref="IValueTaskSource.OnCompleted"/> and <see cref="IValueTaskSource{TResult}.OnCompleted"/>
+ /// to control behavior.
+ /// </summary>
+ [Flags]
+ public enum ValueTaskSourceOnCompletedFlags
+ {
+ /// <summary>
+ /// No requirements are placed on how the continuation is invoked.
+ /// </summary>
+ None,
+ /// <summary>
+ /// Set if OnCompleted should capture the current scheduling context (e.g. SynchronizationContext)
+ /// and use it when queueing the continuation for execution. If this is not set, the implementation
+ /// may choose to execute the continuation in an arbitrary location.
+ /// </summary>
+ UseSchedulingContext = 0x1,
+ /// <summary>
+ /// Set if OnCompleted should capture the current ExecutionContext and use it to run the continuation.
+ /// </summary>
+ FlowExecutionContext = 0x2,
+ }
+
+ /// <summary>Indicates the status of an <see cref="IValueTaskSource"/> or <see cref="IValueTaskSource{TResult}"/>.</summary>
+ public enum ValueTaskSourceStatus
+ {
+ /// <summary>The operation has not yet completed.</summary>
+ Pending = 0,
+ /// <summary>The operation completed successfully.</summary>
+ Succeeded = 1,
+ /// <summary>The operation completed with an error.</summary>
+ Faulted = 2,
+ /// <summary>The operation completed due to cancellation.</summary>
+ Canceled = 3
+ }
+
+ /// <summary>Represents an object that can be wrapped by a <see cref="ValueTask"/>.</summary>
+ public interface IValueTaskSource
+ {
+ /// <summary>Gets the status of the current operation.</summary>
+ /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+ ValueTaskSourceStatus GetStatus(short token);
+
+ /// <summary>Schedules the continuation action for this <see cref="IValueTaskSource"/>.</summary>
+ /// <param name="continuation">The continuation to invoke when the operation has completed.</param>
+ /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param>
+ /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+ /// <param name="flags">The flags describing the behavior of the continuation.</param>
+ void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags);
+
+ /// <summary>Gets the result of the <see cref="IValueTaskSource"/>.</summary>
+ /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+ void GetResult(short token);
+ }
+
+ /// <summary>Represents an object that can be wrapped by a <see cref="ValueTask{TResult}"/>.</summary>
+ /// <typeparam name="TResult">Specifies the type of data returned from the object.</typeparam>
+ public interface IValueTaskSource<out TResult>
+ {
+ /// <summary>Gets the status of the current operation.</summary>
+ /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+ ValueTaskSourceStatus GetStatus(short token);
+
+ /// <summary>Schedules the continuation action for this <see cref="IValueTaskSource{TResult}"/>.</summary>
+ /// <param name="continuation">The continuation to invoke when the operation has completed.</param>
+ /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param>
+ /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+ /// <param name="flags">The flags describing the behavior of the continuation.</param>
+ void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags);
+
+ /// <summary>Gets the result of the <see cref="IValueTaskSource{TResult}"/>.</summary>
+ /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+ TResult GetResult(short token);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskCanceledException.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskCanceledException.cs
new file mode 100644
index 0000000000..c3ee31a53c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskCanceledException.cs
@@ -0,0 +1,99 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+//
+//
+// An exception for task cancellations.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+
+namespace System.Threading.Tasks
+{
+ /// <summary>
+ /// Represents an exception used to communicate task cancellation.
+ /// </summary>
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class TaskCanceledException : OperationCanceledException
+ {
+ [NonSerialized]
+ private readonly Task _canceledTask; // The task which has been canceled.
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/> class.
+ /// </summary>
+ public TaskCanceledException() : base(SR.TaskCanceledException_ctor_DefaultMessage)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/>
+ /// class with a specified error message.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ public TaskCanceledException(string message) : base(message)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/>
+ /// class with a specified error message and a reference to the inner exception that is the cause of
+ /// this exception.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerException">The exception that is the cause of the current exception.</param>
+ public TaskCanceledException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/>
+ /// class with a specified error message, a reference to the inner exception that is the cause of
+ /// this exception, and the <see cref="CancellationToken"/> that triggered the cancellation.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerException">The exception that is the cause of the current exception.</param>
+ /// <param name="token">The <see cref="CancellationToken"/> that triggered the cancellation.</param>
+ public TaskCanceledException(string message, Exception innerException, CancellationToken token) : base(message, innerException, token)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/> class
+ /// with a reference to the <see cref="T:System.Threading.Tasks.Task"/> that has been canceled.
+ /// </summary>
+ /// <param name="task">A task that has been canceled.</param>
+ public TaskCanceledException(Task task) :
+ base(SR.TaskCanceledException_ctor_DefaultMessage, task != null ? task.CancellationToken : new CancellationToken())
+ {
+ _canceledTask = task;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/>
+ /// class with serialized data.
+ /// </summary>
+ /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
+ /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination. </param>
+ protected TaskCanceledException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ /// <summary>
+ /// Gets the task associated with this exception.
+ /// </summary>
+ /// <remarks>
+ /// It is permissible for no Task to be associated with a
+ /// <see cref="T:System.Threading.Tasks.TaskCanceledException"/>, in which case
+ /// this property will return null.
+ /// </remarks>
+ public Task Task => _canceledTask;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskCompletionSource.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskCompletionSource.cs
new file mode 100644
index 0000000000..992e9db767
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskCompletionSource.cs
@@ -0,0 +1,349 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+//
+//
+// TaskCompletionSource<TResult> is the producer end of an unbound future. Its
+// Task member may be distributed as the consumer end of the future.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+
+// Disable the "reference to volatile field not treated as volatile" error.
+#pragma warning disable 0420
+
+namespace System.Threading.Tasks
+{
+ /// <summary>
+ /// Represents the producer side of a <see cref="T:System.Threading.Tasks.Task{TResult}"/> unbound to a
+ /// delegate, providing access to the consumer side through the <see cref="Task"/> property.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// It is often the case that a <see cref="T:System.Threading.Tasks.Task{TResult}"/> is desired to
+ /// represent another asynchronous operation.
+ /// <see cref="TaskCompletionSource{TResult}">TaskCompletionSource</see> is provided for this purpose. It enables
+ /// the creation of a task that can be handed out to consumers, and those consumers can use the members
+ /// of the task as they would any other. However, unlike most tasks, the state of a task created by a
+ /// TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the
+ /// completion of the external asynchronous operation to be propagated to the underlying Task. The
+ /// separation also ensures that consumers are not able to transition the state without access to the
+ /// corresponding TaskCompletionSource.
+ /// </para>
+ /// <para>
+ /// All members of <see cref="TaskCompletionSource{TResult}"/> are thread-safe
+ /// and may be used from multiple threads concurrently.
+ /// </para>
+ /// </remarks>
+ /// <typeparam name="TResult">The type of the result value associated with this <see
+ /// cref="TaskCompletionSource{TResult}"/>.</typeparam>
+ public class TaskCompletionSource<TResult>
+ {
+ private readonly Task<TResult> _task;
+
+ /// <summary>
+ /// Creates a <see cref="TaskCompletionSource{TResult}"/>.
+ /// </summary>
+ public TaskCompletionSource()
+ {
+ _task = new Task<TResult>();
+ }
+
+ /// <summary>
+ /// Creates a <see cref="TaskCompletionSource{TResult}"/>
+ /// with the specified options.
+ /// </summary>
+ /// <remarks>
+ /// The <see cref="T:System.Threading.Tasks.Task{TResult}"/> created
+ /// by this instance and accessible through its <see cref="Task"/> property
+ /// will be instantiated using the specified <paramref name="creationOptions"/>.
+ /// </remarks>
+ /// <param name="creationOptions">The options to use when creating the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="creationOptions"/> represent options invalid for use
+ /// with a <see cref="TaskCompletionSource{TResult}"/>.
+ /// </exception>
+ public TaskCompletionSource(TaskCreationOptions creationOptions)
+ : this(null, creationOptions)
+ {
+ }
+
+ /// <summary>
+ /// Creates a <see cref="TaskCompletionSource{TResult}"/>
+ /// with the specified state.
+ /// </summary>
+ /// <param name="state">The state to use as the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>'s AsyncState.</param>
+ public TaskCompletionSource(object state)
+ : this(state, TaskCreationOptions.None)
+ {
+ }
+
+ /// <summary>
+ /// Creates a <see cref="TaskCompletionSource{TResult}"/> with
+ /// the specified state and options.
+ /// </summary>
+ /// <param name="creationOptions">The options to use when creating the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
+ /// <param name="state">The state to use as the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>'s AsyncState.</param>
+ /// <exception cref="T:System.ArgumentOutOfRangeException">
+ /// The <paramref name="creationOptions"/> represent options invalid for use
+ /// with a <see cref="TaskCompletionSource{TResult}"/>.
+ /// </exception>
+ public TaskCompletionSource(object state, TaskCreationOptions creationOptions)
+ {
+ _task = new Task<TResult>(state, creationOptions);
+ }
+
+
+ /// <summary>
+ /// Gets the <see cref="T:System.Threading.Tasks.Task{TResult}"/> created
+ /// by this <see cref="TaskCompletionSource{TResult}"/>.
+ /// </summary>
+ /// <remarks>
+ /// This property enables a consumer access to the <see
+ /// cref="T:System.Threading.Tasks.Task{TResult}"/> that is controlled by this instance.
+ /// The <see cref="SetResult"/>, <see cref="SetException(System.Exception)"/>,
+ /// <see cref="SetException(System.Collections.Generic.IEnumerable{System.Exception})"/>, and <see cref="SetCanceled"/>
+ /// methods (and their "Try" variants) on this instance all result in the relevant state
+ /// transitions on this underlying Task.
+ /// </remarks>
+ public Task<TResult> Task => _task;
+
+ /// <summary>Spins until the underlying task is completed.</summary>
+ /// <remarks>This should only be called if the task is in the process of being completed by another thread.</remarks>
+ private void SpinUntilCompleted()
+ {
+ // Spin wait until the completion is finalized by another thread.
+ var sw = new SpinWait();
+ while (!_task.IsCompleted)
+ sw.SpinOnce();
+ }
+
+ /// <summary>
+ /// Attempts to transition the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>
+ /// state.
+ /// </summary>
+ /// <param name="exception">The exception to bind to this <see
+ /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
+ /// <returns>True if the operation was successful; otherwise, false.</returns>
+ /// <remarks>This operation will return false if the
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
+ /// of the three final states:
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="exception"/> argument is null.</exception>
+ /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
+ public bool TrySetException(Exception exception)
+ {
+ if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+
+ bool rval = _task.TrySetException(exception);
+ if (!rval && !_task.IsCompleted) SpinUntilCompleted();
+ return rval;
+ }
+
+ /// <summary>
+ /// Attempts to transition the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>
+ /// state.
+ /// </summary>
+ /// <param name="exceptions">The collection of exceptions to bind to this <see
+ /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
+ /// <returns>True if the operation was successful; otherwise, false.</returns>
+ /// <remarks>This operation will return false if the
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
+ /// of the three final states:
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </remarks>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="exceptions"/> argument is null.</exception>
+ /// <exception cref="T:System.ArgumentException">There are one or more null elements in <paramref name="exceptions"/>.</exception>
+ /// <exception cref="T:System.ArgumentException">The <paramref name="exceptions"/> collection is empty.</exception>
+ /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
+ public bool TrySetException(IEnumerable<Exception> exceptions)
+ {
+ if (exceptions == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exceptions);
+
+ List<Exception> defensiveCopy = new List<Exception>();
+ foreach (Exception e in exceptions)
+ {
+ if (e == null)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.TaskCompletionSourceT_TrySetException_NullException, ExceptionArgument.exceptions);
+ defensiveCopy.Add(e);
+ }
+
+ if (defensiveCopy.Count == 0)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.TaskCompletionSourceT_TrySetException_NoExceptions, ExceptionArgument.exceptions);
+
+ bool rval = _task.TrySetException(defensiveCopy);
+ if (!rval && !_task.IsCompleted) SpinUntilCompleted();
+ return rval;
+ }
+
+ /// <summary>
+ /// Transitions the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>
+ /// state.
+ /// </summary>
+ /// <param name="exception">The exception to bind to this <see
+ /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="exception"/> argument is null.</exception>
+ /// <exception cref="T:System.InvalidOperationException">
+ /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
+ /// of the three final states:
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
+ public void SetException(Exception exception)
+ {
+ if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+
+ if (!TrySetException(exception))
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
+ }
+ }
+
+ /// <summary>
+ /// Transitions the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>
+ /// state.
+ /// </summary>
+ /// <param name="exceptions">The collection of exceptions to bind to this <see
+ /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
+ /// <exception cref="T:System.ArgumentNullException">The <paramref name="exceptions"/> argument is null.</exception>
+ /// <exception cref="T:System.ArgumentException">There are one or more null elements in <paramref name="exceptions"/>.</exception>
+ /// <exception cref="T:System.InvalidOperationException">
+ /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
+ /// of the three final states:
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
+ public void SetException(IEnumerable<Exception> exceptions)
+ {
+ if (!TrySetException(exceptions))
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
+ }
+ }
+
+
+ /// <summary>
+ /// Attempts to transition the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>
+ /// state.
+ /// </summary>
+ /// <param name="result">The result value to bind to this <see
+ /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
+ /// <returns>True if the operation was successful; otherwise, false.</returns>
+ /// <remarks>This operation will return false if the
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
+ /// of the three final states:
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </remarks>
+ /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
+ public bool TrySetResult(TResult result)
+ {
+ bool rval = _task.TrySetResult(result);
+ if (!rval) SpinUntilCompleted();
+ return rval;
+ }
+
+ /// <summary>
+ /// Transitions the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>
+ /// state.
+ /// </summary>
+ /// <param name="result">The result value to bind to this <see
+ /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
+ /// <exception cref="T:System.InvalidOperationException">
+ /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
+ /// of the three final states:
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
+ public void SetResult(TResult result)
+ {
+ if (!TrySetResult(result))
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
+ }
+
+ /// <summary>
+ /// Attempts to transition the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>
+ /// state.
+ /// </summary>
+ /// <returns>True if the operation was successful; otherwise, false.</returns>
+ /// <remarks>This operation will return false if the
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
+ /// of the three final states:
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </remarks>
+ /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
+ public bool TrySetCanceled()
+ {
+ return TrySetCanceled(default(CancellationToken));
+ }
+
+ // Enables a token to be stored into the canceled task
+ public bool TrySetCanceled(CancellationToken cancellationToken)
+ {
+ bool rval = _task.TrySetCanceled(cancellationToken);
+ if (!rval && !_task.IsCompleted) SpinUntilCompleted();
+ return rval;
+ }
+
+ /// <summary>
+ /// Transitions the underlying
+ /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>
+ /// state.
+ /// </summary>
+ /// <exception cref="T:System.InvalidOperationException">
+ /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
+ /// of the three final states:
+ /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
+ /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
+ /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
+ /// </exception>
+ /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
+ public void SetCanceled()
+ {
+ if (!TrySetCanceled())
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskExtensions.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskExtensions.cs
new file mode 100644
index 0000000000..97f2013d79
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskExtensions.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Threading.Tasks
+{
+ /// <summary>Provides a set of static methods for working with specific kinds of <see cref="Task"/> instances.</summary>
+ public static class TaskExtensions
+ {
+ /// <summary>Creates a proxy <see cref="Task"/> that represents the asynchronous operation of a <see cref="Task{Task}"/>.</summary>
+ /// <param name="task">The <see cref="Task{Task}"/> to unwrap.</param>
+ /// <returns>A <see cref="Task"/> that represents the asynchronous operation of the provided <see cref="Task{Task}"/>.</returns>
+ public static Task Unwrap(this Task<Task> task)
+ {
+ if (task == null)
+ {
+ throw new ArgumentNullException(nameof(task));
+ }
+
+ // If the task hasn't completed or was faulted/canceled, wrap it in an unwrap promise. Otherwise,
+ // it completed successfully. Return its inner task to avoid unnecessary wrapping, or if the inner
+ // task is null, return a canceled task to match the same semantics as CreateUnwrapPromise.
+ return
+ !task.IsCompletedSuccessfully ? Task.CreateUnwrapPromise<VoidTaskResult>(task, lookForOce: false) :
+ task.Result ??
+ Task.FromCanceled(new CancellationToken(true));
+ }
+
+ /// <summary>Creates a proxy <see cref="Task{TResult}"/> that represents the asynchronous operation of a <see cref="Task{Task{TResult}}"/>.</summary>
+ /// <param name="task">The <see cref="Task{Task{TResult}}"/> to unwrap.</param>
+ /// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation of the provided <see cref="Task{Task{TResult}}"/>.</returns>
+ public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> task)
+ {
+ if (task == null)
+ {
+ throw new ArgumentNullException(nameof(task));
+ }
+
+ // If the task hasn't completed or was faulted/canceled, wrap it in an unwrap promise. Otherwise,
+ // it completed successfully. Return its inner task to avoid unnecessary wrapping, or if the inner
+ // task is null, return a canceled task to match the same semantics as CreateUnwrapPromise.
+ return
+ !task.IsCompletedSuccessfully ? Task.CreateUnwrapPromise<TResult>(task, lookForOce: false) :
+ task.Result ??
+ Task.FromCanceled<TResult>(new CancellationToken(true));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskSchedulerException.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskSchedulerException.cs
new file mode 100644
index 0000000000..85ec497219
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskSchedulerException.cs
@@ -0,0 +1,78 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+//
+//
+// An exception for task schedulers.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+
+namespace System.Threading.Tasks
+{
+ /// <summary>
+ /// Represents an exception used to communicate an invalid operation by a
+ /// <see cref="T:System.Threading.Tasks.TaskScheduler"/>.
+ /// </summary>
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class TaskSchedulerException : Exception
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/> class.
+ /// </summary>
+ public TaskSchedulerException() : base(SR.TaskSchedulerException_ctor_DefaultMessage) //
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/>
+ /// class with a specified error message.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ public TaskSchedulerException(string message) : base(message)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/>
+ /// class using the default error message and a reference to the inner exception that is the cause of
+ /// this exception.
+ /// </summary>
+ /// <param name="innerException">The exception that is the cause of the current exception.</param>
+ public TaskSchedulerException(Exception innerException)
+ : base(SR.TaskSchedulerException_ctor_DefaultMessage, innerException)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/>
+ /// class with a specified error message and a reference to the inner exception that is the cause of
+ /// this exception.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerException">The exception that is the cause of the current exception.</param>
+ public TaskSchedulerException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/>
+ /// class with serialized data.
+ /// </summary>
+ /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds
+ /// the serialized object data about the exception being thrown.</param>
+ /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that
+ /// contains contextual information about the source or destination. </param>
+ protected TaskSchedulerException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskToApm.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskToApm.cs
new file mode 100644
index 0000000000..add41f588e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskToApm.cs
@@ -0,0 +1,189 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Helper methods for using Tasks to implement the APM pattern.
+//
+// Example usage, wrapping a Task<int>-returning FooAsync method with Begin/EndFoo methods:
+//
+// public IAsyncResult BeginFoo(..., AsyncCallback callback, object state)
+// {
+// Task<int> t = FooAsync(...);
+// return TaskToApm.Begin(t, callback, state);
+// }
+// public int EndFoo(IAsyncResult asyncResult)
+// {
+// return TaskToApm.End<int>(asyncResult);
+// }
+
+using System.Diagnostics;
+
+namespace System.Threading.Tasks
+{
+ /// <summary>
+ /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern.
+ /// </summary>
+ internal static class TaskToApm
+ {
+ /// <summary>
+ /// Marshals the Task as an IAsyncResult, using the supplied callback and state
+ /// to implement the APM pattern.
+ /// </summary>
+ /// <param name="task">The Task to be marshaled.</param>
+ /// <param name="callback">The callback to be invoked upon completion.</param>
+ /// <param name="state">The state to be stored in the IAsyncResult.</param>
+ /// <returns>An IAsyncResult to represent the task's asynchronous operation.</returns>
+ public static IAsyncResult Begin(Task task, AsyncCallback callback, object state)
+ {
+ Debug.Assert(task != null);
+
+ // If the task has already completed, then since the Task's CompletedSynchronously==false
+ // and we want it to be true, we need to create a new IAsyncResult. (We also need the AsyncState to match.)
+ IAsyncResult asyncResult;
+ if (task.IsCompleted)
+ {
+ // Synchronous completion.
+ asyncResult = new TaskWrapperAsyncResult(task, state, completedSynchronously: true);
+ callback?.Invoke(asyncResult);
+ }
+ else
+ {
+ // For asynchronous completion we need to schedule a callback. Whether we can use the Task as the IAsyncResult
+ // depends on whether the Task's AsyncState has reference equality with the requested state.
+ asyncResult = task.AsyncState == state ? (IAsyncResult)task : new TaskWrapperAsyncResult(task, state, completedSynchronously: false);
+ if (callback != null)
+ {
+ InvokeCallbackWhenTaskCompletes(task, callback, asyncResult);
+ }
+ }
+ return asyncResult;
+ }
+
+ /// <summary>Processes an IAsyncResult returned by Begin.</summary>
+ /// <param name="asyncResult">The IAsyncResult to unwrap.</param>
+ public static void End(IAsyncResult asyncResult)
+ {
+ Task task;
+
+ // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task.
+ var twar = asyncResult as TaskWrapperAsyncResult;
+ if (twar != null)
+ {
+ task = twar.Task;
+ Debug.Assert(task != null, "TaskWrapperAsyncResult should never wrap a null Task.");
+ }
+ else
+ {
+ // Otherwise, the IAsyncResult should be a Task.
+ task = asyncResult as Task;
+ }
+
+ // Make sure we actually got a task, then complete the operation by waiting on it.
+ if (task == null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ task.GetAwaiter().GetResult();
+ }
+
+ /// <summary>Processes an IAsyncResult returned by Begin.</summary>
+ /// <param name="asyncResult">The IAsyncResult to unwrap.</param>
+ public static TResult End<TResult>(IAsyncResult asyncResult)
+ {
+ Task<TResult> task;
+
+ // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task.
+ var twar = asyncResult as TaskWrapperAsyncResult;
+ if (twar != null)
+ {
+ task = twar.Task as Task<TResult>;
+ Debug.Assert(twar.Task != null, "TaskWrapperAsyncResult should never wrap a null Task.");
+ }
+ else
+ {
+ // Otherwise, the IAsyncResult should be a Task<TResult>.
+ task = asyncResult as Task<TResult>;
+ }
+
+ // Make sure we actually got a task, then complete the operation by waiting on it.
+ if (task == null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ return task.GetAwaiter().GetResult();
+ }
+
+ /// <summary>Invokes the callback asynchronously when the task has completed.</summary>
+ /// <param name="antecedent">The Task to await.</param>
+ /// <param name="callback">The callback to invoke when the Task completes.</param>
+ /// <param name="asyncResult">The Task used as the IAsyncResult.</param>
+ private static void InvokeCallbackWhenTaskCompletes(Task antecedent, AsyncCallback callback, IAsyncResult asyncResult)
+ {
+ Debug.Assert(antecedent != null);
+ Debug.Assert(callback != null);
+ Debug.Assert(asyncResult != null);
+
+ // We use OnCompleted rather than ContinueWith in order to avoid running synchronously
+ // if the task has already completed by the time we get here. This is separated out into
+ // its own method currently so that we only pay for the closure if necessary.
+ antecedent.ConfigureAwait(continueOnCapturedContext: false)
+ .GetAwaiter()
+ .OnCompleted(() => callback(asyncResult));
+
+ // PERFORMANCE NOTE:
+ // Assuming we're in the default ExecutionContext, the "slow path" of an incomplete
+ // task will result in four allocations: the new IAsyncResult, the delegate+closure
+ // in this method, and the continuation object inside of OnCompleted (necessary
+ // to capture both the Action delegate and the ExecutionContext in a single object).
+ // In the future, if performance requirements drove a need, those four
+ // allocations could be reduced to one. This would be achieved by having TaskWrapperAsyncResult
+ // also implement ITaskCompletionAction (and optionally IThreadPoolWorkItem). It would need
+ // additional fields to store the AsyncCallback and an ExecutionContext. Once configured,
+ // it would be set into the Task as a continuation. Its Invoke method would then be run when
+ // the antecedent completed, and, doing all of the necessary work to flow ExecutionContext,
+ // it would invoke the AsyncCallback. It could also have a field on it for the antecedent,
+ // so that the End method would have access to the completed antecedent. For related examples,
+ // see other implementations of ITaskCompletionAction, and in particular ReadWriteTask
+ // used in Stream.Begin/EndXx's implementation.
+ }
+
+ /// <summary>
+ /// Provides a simple IAsyncResult that wraps a Task. This, in effect, allows
+ /// for overriding what's seen for the CompletedSynchronously and AsyncState values.
+ /// </summary>
+ private sealed class TaskWrapperAsyncResult : IAsyncResult
+ {
+ /// <summary>The wrapped Task.</summary>
+ internal readonly Task Task;
+ /// <summary>The new AsyncState value.</summary>
+ private readonly object _state;
+ /// <summary>The new CompletedSynchronously value.</summary>
+ private readonly bool _completedSynchronously;
+
+ /// <summary>Initializes the IAsyncResult with the Task to wrap and the overriding AsyncState and CompletedSynchronously values.</summary>
+ /// <param name="task">The Task to wrap.</param>
+ /// <param name="state">The new AsyncState value</param>
+ /// <param name="completedSynchronously">The new CompletedSynchronously value.</param>
+ internal TaskWrapperAsyncResult(Task task, object state, bool completedSynchronously)
+ {
+ Debug.Assert(task != null);
+ Debug.Assert(!completedSynchronously || task.IsCompleted, "If completedSynchronously is true, the task must be completed.");
+
+ this.Task = task;
+ _state = state;
+ _completedSynchronously = completedSynchronously;
+ }
+
+ // The IAsyncResult implementation.
+ // - IsCompleted and AsyncWaitHandle just pass through to the Task.
+ // - AsyncState and CompletedSynchronously return the corresponding values stored in this object.
+
+ object IAsyncResult.AsyncState { get { return _state; } }
+ bool IAsyncResult.CompletedSynchronously { get { return _completedSynchronously; } }
+ bool IAsyncResult.IsCompleted { get { return this.Task.IsCompleted; } }
+ WaitHandle IAsyncResult.AsyncWaitHandle { get { return ((IAsyncResult)this.Task).AsyncWaitHandle; } }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs
new file mode 100644
index 0000000000..8ee1a2026e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs
@@ -0,0 +1,849 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks.Sources;
+
+#if !netstandard
+using Internal.Runtime.CompilerServices;
+#endif
+
+namespace System.Threading.Tasks
+{
+ // TYPE SAFETY WARNING:
+ // This code uses Unsafe.As to cast _obj. This is done in order to minimize the costs associated with
+ // casting _obj to a variety of different types that can be stored in a ValueTask, e.g. Task<TResult>
+ // vs IValueTaskSource<TResult>. Previous attempts at this were faulty due to using a separate field
+ // to store information about the type of the object in _obj; this is faulty because if the ValueTask
+ // is stored into a field, concurrent read/writes can result in tearing the _obj from the type information
+ // stored in a separate field. This means we can rely only on the _obj field to determine how to handle
+ // it. As such, the pattern employed is to copy _obj into a local obj, and then check it for null and
+ // type test against Task/Task<TResult>. Since the ValueTask can only be constructed with null, Task,
+ // or IValueTaskSource, we can then be confident in knowing that if it doesn't match one of those values,
+ // it must be an IValueTaskSource, and we can use Unsafe.As. This could be defeated by other unsafe means,
+ // like private reflection or using Unsafe.As manually, but at that point you're already doing things
+ // that can violate type safety; we only care about getting correct behaviors when using "safe" code.
+ // There are still other race conditions in user's code that can result in errors, but such errors don't
+ // cause ValueTask to violate type safety.
+
+ /// <summary>Provides an awaitable result of an asynchronous operation.</summary>
+ /// <remarks>
+ /// <see cref="ValueTask"/>s are meant to be directly awaited. To do more complicated operations with them, a <see cref="Task"/>
+ /// should be extracted using <see cref="AsTask"/>. Such operations might include caching an instance to be awaited later,
+ /// registering multiple continuations with a single operation, awaiting the same task multiple times, and using combinators over
+ /// multiple operations.
+ /// </remarks>
+ [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))]
+ [StructLayout(LayoutKind.Auto)]
+ public readonly struct ValueTask : IEquatable<ValueTask>
+ {
+ /// <summary>A task canceled using `new CancellationToken(true)`.</summary>
+ private static readonly Task s_canceledTask =
+#if netstandard
+ Task.Delay(Timeout.Infinite, new CancellationToken(canceled: true));
+#else
+ Task.FromCanceled(new CancellationToken(canceled: true));
+#endif
+ /// <summary>A successfully completed task.</summary>
+ internal static Task CompletedTask
+#if netstandard
+ { get; } = Task.Delay(0);
+#else
+ => Task.CompletedTask;
+#endif
+
+ /// <summary>null if representing a successful synchronous completion, otherwise a <see cref="Task"/> or a <see cref="IValueTaskSource"/>.</summary>
+ internal readonly object _obj;
+ /// <summary>Opaque value passed through to the <see cref="IValueTaskSource"/>.</summary>
+ internal readonly short _token;
+ /// <summary>true to continue on the capture context; otherwise, true.</summary>
+ /// <remarks>Stored in the <see cref="ValueTask"/> rather than in the configured awaiter to utilize otherwise padding space.</remarks>
+ internal readonly bool _continueOnCapturedContext;
+
+ // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation.
+
+ /// <summary>Initialize the <see cref="ValueTask"/> with a <see cref="Task"/> that represents the operation.</summary>
+ /// <param name="task">The task.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ValueTask(Task task)
+ {
+ if (task == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task);
+ }
+
+ _obj = task;
+
+ _continueOnCapturedContext = true;
+ _token = 0;
+ }
+
+ /// <summary>Initialize the <see cref="ValueTask"/> with a <see cref="IValueTaskSource"/> object that represents the operation.</summary>
+ /// <param name="source">The source.</param>
+ /// <param name="token">Opaque value passed through to the <see cref="IValueTaskSource"/>.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ValueTask(IValueTaskSource source, short token)
+ {
+ if (source == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
+ _obj = source;
+ _token = token;
+
+ _continueOnCapturedContext = true;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ValueTask(object obj, short token, bool continueOnCapturedContext)
+ {
+ _obj = obj;
+ _token = token;
+ _continueOnCapturedContext = continueOnCapturedContext;
+ }
+
+ /// <summary>Returns the hash code for this instance.</summary>
+ public override int GetHashCode() => _obj?.GetHashCode() ?? 0;
+
+ /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="object"/>.</summary>
+ public override bool Equals(object obj) =>
+ obj is ValueTask &&
+ Equals((ValueTask)obj);
+
+ /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="ValueTask"/> value.</summary>
+ public bool Equals(ValueTask other) => _obj == other._obj && _token == other._token;
+
+ /// <summary>Returns a value indicating whether two <see cref="ValueTask"/> values are equal.</summary>
+ public static bool operator ==(ValueTask left, ValueTask right) =>
+ left.Equals(right);
+
+ /// <summary>Returns a value indicating whether two <see cref="ValueTask"/> values are not equal.</summary>
+ public static bool operator !=(ValueTask left, ValueTask right) =>
+ !left.Equals(right);
+
+ /// <summary>
+ /// Gets a <see cref="Task"/> object to represent this ValueTask.
+ /// </summary>
+ /// <remarks>
+ /// It will either return the wrapped task object if one exists, or it'll
+ /// manufacture a new task object to represent the result.
+ /// </remarks>
+ public Task AsTask()
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+ return
+ obj == null ? CompletedTask :
+ obj as Task ??
+ GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource>(obj));
+ }
+
+ /// <summary>Gets a <see cref="ValueTask"/> that may be used at any point in the future.</summary>
+ public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask());
+
+ /// <summary>Creates a <see cref="Task"/> to represent the <see cref="IValueTaskSource"/>.</summary>
+ /// <remarks>
+ /// The <see cref="IValueTaskSource"/> is passed in rather than reading and casting <see cref="_obj"/>
+ /// so that the caller can pass in an object it's already validated.
+ /// </remarks>
+ private Task GetTaskForValueTaskSource(IValueTaskSource t)
+ {
+ ValueTaskSourceStatus status = t.GetStatus(_token);
+ if (status != ValueTaskSourceStatus.Pending)
+ {
+ try
+ {
+ // Propagate any exceptions that may have occurred, then return
+ // an already successfully completed task.
+ t.GetResult(_token);
+ return CompletedTask;
+
+ // If status is Faulted or Canceled, GetResult should throw. But
+ // we can't guarantee every implementation will do the "right thing".
+ // If it doesn't throw, we just treat that as success and ignore
+ // the status.
+ }
+ catch (Exception exc)
+ {
+ if (status == ValueTaskSourceStatus.Canceled)
+ {
+#if !netstandard
+ if (exc is OperationCanceledException oce)
+ {
+ var task = new Task<VoidTaskResult>();
+ task.TrySetCanceled(oce.CancellationToken, oce);
+ return task;
+ }
+#endif
+ return s_canceledTask;
+ }
+ else
+ {
+#if netstandard
+ var tcs = new TaskCompletionSource<bool>();
+ tcs.TrySetException(exc);
+ return tcs.Task;
+#else
+ return Task.FromException(exc);
+#endif
+ }
+ }
+ }
+
+ var m = new ValueTaskSourceAsTask(t, _token);
+ return
+#if netstandard
+ m.Task;
+#else
+ m;
+#endif
+ }
+
+ /// <summary>Type used to create a <see cref="Task"/> to represent a <see cref="IValueTaskSource"/>.</summary>
+ private sealed class ValueTaskSourceAsTask :
+#if netstandard
+ TaskCompletionSource<bool>
+#else
+ Task<VoidTaskResult>
+#endif
+ {
+ private static readonly Action<object> s_completionAction = state =>
+ {
+ if (!(state is ValueTaskSourceAsTask vtst) ||
+ !(vtst._source is IValueTaskSource source))
+ {
+ // This could only happen if the IValueTaskSource passed the wrong state
+ // or if this callback were invoked multiple times such that the state
+ // was previously nulled out.
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state);
+ return;
+ }
+
+ vtst._source = null;
+ ValueTaskSourceStatus status = source.GetStatus(vtst._token);
+ try
+ {
+ source.GetResult(vtst._token);
+ vtst.TrySetResult(default);
+ }
+ catch (Exception exc)
+ {
+ if (status == ValueTaskSourceStatus.Canceled)
+ {
+#if netstandard
+ vtst.TrySetCanceled();
+#else
+ if (exc is OperationCanceledException oce)
+ {
+ vtst.TrySetCanceled(oce.CancellationToken, oce);
+ }
+ else
+ {
+ vtst.TrySetCanceled(new CancellationToken(true));
+ }
+#endif
+ }
+ else
+ {
+ vtst.TrySetException(exc);
+ }
+ }
+ };
+
+ /// <summary>The associated <see cref="IValueTaskSource"/>.</summary>
+ private IValueTaskSource _source;
+ /// <summary>The token to pass through to operations on <see cref="_source"/></summary>
+ private readonly short _token;
+
+ public ValueTaskSourceAsTask(IValueTaskSource source, short token)
+ {
+ _token = token;
+ _source = source;
+ source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None);
+ }
+ }
+
+ /// <summary>Gets whether the <see cref="ValueTask"/> represents a completed operation.</summary>
+ public bool IsCompleted
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj == null)
+ {
+ return true;
+ }
+
+ if (obj is Task t)
+ {
+ return t.IsCompleted;
+ }
+
+ return Unsafe.As<IValueTaskSource>(obj).GetStatus(_token) != ValueTaskSourceStatus.Pending;
+ }
+ }
+
+ /// <summary>Gets whether the <see cref="ValueTask"/> represents a successfully completed operation.</summary>
+ public bool IsCompletedSuccessfully
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj == null)
+ {
+ return true;
+ }
+
+ if (obj is Task t)
+ {
+ return
+#if netstandard
+ t.Status == TaskStatus.RanToCompletion;
+#else
+ t.IsCompletedSuccessfully;
+#endif
+ }
+
+ return Unsafe.As<IValueTaskSource>(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded;
+ }
+ }
+
+ /// <summary>Gets whether the <see cref="ValueTask"/> represents a failed operation.</summary>
+ public bool IsFaulted
+ {
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj == null)
+ {
+ return false;
+ }
+
+ if (obj is Task t)
+ {
+ return t.IsFaulted;
+ }
+
+ return Unsafe.As<IValueTaskSource>(obj).GetStatus(_token) == ValueTaskSourceStatus.Faulted;
+ }
+ }
+
+ /// <summary>Gets whether the <see cref="ValueTask"/> represents a canceled operation.</summary>
+ /// <remarks>
+ /// If the <see cref="ValueTask"/> is backed by a result or by a <see cref="IValueTaskSource"/>,
+ /// this will always return false. If it's backed by a <see cref="Task"/>, it'll return the
+ /// value of the task's <see cref="Task.IsCanceled"/> property.
+ /// </remarks>
+ public bool IsCanceled
+ {
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj == null)
+ {
+ return false;
+ }
+
+ if (obj is Task t)
+ {
+ return t.IsCanceled;
+ }
+
+ return Unsafe.As<IValueTaskSource>(obj).GetStatus(_token) == ValueTaskSourceStatus.Canceled;
+ }
+ }
+
+ /// <summary>Throws the exception that caused the <see cref="ValueTask"/> to fail. If it completed successfully, nothing is thrown.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [StackTraceHidden]
+ internal void ThrowIfCompletedUnsuccessfully()
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+
+ if (obj != null)
+ {
+ if (obj is Task t)
+ {
+#if netstandard
+ t.GetAwaiter().GetResult();
+#else
+ TaskAwaiter.ValidateEnd(t);
+#endif
+ }
+ else
+ {
+ Unsafe.As<IValueTaskSource>(obj).GetResult(_token);
+ }
+ }
+ }
+
+ /// <summary>Gets an awaiter for this <see cref="ValueTask"/>.</summary>
+ public ValueTaskAwaiter GetAwaiter() => new ValueTaskAwaiter(this);
+
+ /// <summary>Configures an awaiter for this <see cref="ValueTask"/>.</summary>
+ /// <param name="continueOnCapturedContext">
+ /// true to attempt to marshal the continuation back to the captured context; otherwise, false.
+ /// </param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) =>
+ new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _token, continueOnCapturedContext));
+ }
+
+ /// <summary>Provides a value type that can represent a synchronously available value or a task object.</summary>
+ /// <typeparam name="TResult">Specifies the type of the result.</typeparam>
+ /// <remarks>
+ /// <see cref="ValueTask{TResult}"/>s are meant to be directly awaited. To do more complicated operations with them, a <see cref="Task"/>
+ /// should be extracted using <see cref="AsTask"/> or <see cref="Preserve"/>. Such operations might include caching an instance to
+ /// be awaited later, registering multiple continuations with a single operation, awaiting the same task multiple times, and using
+ /// combinators over multiple operations.
+ /// </remarks>
+ [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))]
+ [StructLayout(LayoutKind.Auto)]
+ public readonly struct ValueTask<TResult> : IEquatable<ValueTask<TResult>>
+ {
+ /// <summary>A task canceled using `new CancellationToken(true)`. Lazily created only when first needed.</summary>
+ private static Task<TResult> s_canceledTask;
+ /// <summary>null if <see cref="_result"/> has the result, otherwise a <see cref="Task{TResult}"/> or a <see cref="IValueTaskSource{TResult}"/>.</summary>
+ internal readonly object _obj;
+ /// <summary>The result to be used if the operation completed successfully synchronously.</summary>
+ internal readonly TResult _result;
+ /// <summary>Opaque value passed through to the <see cref="IValueTaskSource{TResult}"/>.</summary>
+ internal readonly short _token;
+ /// <summary>true to continue on the captured context; otherwise, false.</summary>
+ /// <remarks>Stored in the <see cref="ValueTask{TResult}"/> rather than in the configured awaiter to utilize otherwise padding space.</remarks>
+ internal readonly bool _continueOnCapturedContext;
+
+ // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation
+ // with a result of default(TResult).
+
+ /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <typeparamref name="TResult"/> result value.</summary>
+ /// <param name="result">The result.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ValueTask(TResult result)
+ {
+ _result = result;
+
+ _obj = null;
+ _continueOnCapturedContext = true;
+ _token = 0;
+ }
+
+ /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <see cref="Task{TResult}"/> that represents the operation.</summary>
+ /// <param name="task">The task.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ValueTask(Task<TResult> task)
+ {
+ if (task == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task);
+ }
+
+ _obj = task;
+
+ _result = default;
+ _continueOnCapturedContext = true;
+ _token = 0;
+ }
+
+ /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <see cref="IValueTaskSource{TResult}"/> object that represents the operation.</summary>
+ /// <param name="source">The source.</param>
+ /// <param name="token">Opaque value passed through to the <see cref="IValueTaskSource"/>.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ValueTask(IValueTaskSource<TResult> source, short token)
+ {
+ if (source == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
+ _obj = source;
+ _token = token;
+
+ _result = default;
+ _continueOnCapturedContext = true;
+ }
+
+ /// <summary>Non-verified initialization of the struct to the specified values.</summary>
+ /// <param name="obj">The object.</param>
+ /// <param name="result">The result.</param>
+ /// <param name="token">The token.</param>
+ /// <param name="continueOnCapturedContext">true to continue on captured context; otherwise, false.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ValueTask(object obj, TResult result, short token, bool continueOnCapturedContext)
+ {
+ _obj = obj;
+ _result = result;
+ _token = token;
+ _continueOnCapturedContext = continueOnCapturedContext;
+ }
+
+
+ /// <summary>Returns the hash code for this instance.</summary>
+ public override int GetHashCode() =>
+ _obj != null ? _obj.GetHashCode() :
+ _result != null ? _result.GetHashCode() :
+ 0;
+
+ /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="object"/>.</summary>
+ public override bool Equals(object obj) =>
+ obj is ValueTask<TResult> &&
+ Equals((ValueTask<TResult>)obj);
+
+ /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="ValueTask{TResult}"/> value.</summary>
+ public bool Equals(ValueTask<TResult> other) =>
+ _obj != null || other._obj != null ?
+ _obj == other._obj && _token == other._token :
+ EqualityComparer<TResult>.Default.Equals(_result, other._result);
+
+ /// <summary>Returns a value indicating whether two <see cref="ValueTask{TResult}"/> values are equal.</summary>
+ public static bool operator ==(ValueTask<TResult> left, ValueTask<TResult> right) =>
+ left.Equals(right);
+
+ /// <summary>Returns a value indicating whether two <see cref="ValueTask{TResult}"/> values are not equal.</summary>
+ public static bool operator !=(ValueTask<TResult> left, ValueTask<TResult> right) =>
+ !left.Equals(right);
+
+ /// <summary>
+ /// Gets a <see cref="Task{TResult}"/> object to represent this ValueTask.
+ /// </summary>
+ /// <remarks>
+ /// It will either return the wrapped task object if one exists, or it'll
+ /// manufacture a new task object to represent the result.
+ /// </remarks>
+ public Task<TResult> AsTask()
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj == null)
+ {
+ return
+#if netstandard
+ Task.FromResult(_result);
+#else
+ AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result);
+#endif
+ }
+
+ if (obj is Task<TResult> t)
+ {
+ return t;
+ }
+
+ return GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource<TResult>>(obj));
+ }
+
+ /// <summary>Gets a <see cref="ValueTask{TResult}"/> that may be used at any point in the future.</summary>
+ public ValueTask<TResult> Preserve() => _obj == null ? this : new ValueTask<TResult>(AsTask());
+
+ /// <summary>Creates a <see cref="Task{TResult}"/> to represent the <see cref="IValueTaskSource{TResult}"/>.</summary>
+ /// <remarks>
+ /// The <see cref="IValueTaskSource{TResult}"/> is passed in rather than reading and casting <see cref="_obj"/>
+ /// so that the caller can pass in an object it's already validated.
+ /// </remarks>
+ private Task<TResult> GetTaskForValueTaskSource(IValueTaskSource<TResult> t)
+ {
+ ValueTaskSourceStatus status = t.GetStatus(_token);
+ if (status != ValueTaskSourceStatus.Pending)
+ {
+ try
+ {
+ // Get the result of the operation and return a task for it.
+ // If any exception occurred, propagate it
+ return
+#if netstandard
+ Task.FromResult(t.GetResult(_token));
+#else
+ AsyncTaskMethodBuilder<TResult>.GetTaskForResult(t.GetResult(_token));
+#endif
+
+ // If status is Faulted or Canceled, GetResult should throw. But
+ // we can't guarantee every implementation will do the "right thing".
+ // If it doesn't throw, we just treat that as success and ignore
+ // the status.
+ }
+ catch (Exception exc)
+ {
+ if (status == ValueTaskSourceStatus.Canceled)
+ {
+#if !netstandard
+ if (exc is OperationCanceledException oce)
+ {
+ var task = new Task<TResult>();
+ task.TrySetCanceled(oce.CancellationToken, oce);
+ return task;
+ }
+#endif
+
+ Task<TResult> canceledTask = s_canceledTask;
+ if (canceledTask == null)
+ {
+#if netstandard
+ var tcs = new TaskCompletionSource<TResult>();
+ tcs.TrySetCanceled();
+ canceledTask = tcs.Task;
+#else
+ canceledTask = Task.FromCanceled<TResult>(new CancellationToken(true));
+#endif
+ // Benign race condition to initialize cached task, as identity doesn't matter.
+ s_canceledTask = canceledTask;
+ }
+ return canceledTask;
+ }
+ else
+ {
+#if netstandard
+ var tcs = new TaskCompletionSource<TResult>();
+ tcs.TrySetException(exc);
+ return tcs.Task;
+#else
+ return Task.FromException<TResult>(exc);
+#endif
+ }
+ }
+ }
+
+ var m = new ValueTaskSourceAsTask(t, _token);
+ return
+#if netstandard
+ m.Task;
+#else
+ m;
+#endif
+ }
+
+ /// <summary>Type used to create a <see cref="Task{TResult}"/> to represent a <see cref="IValueTaskSource{TResult}"/>.</summary>
+ private sealed class ValueTaskSourceAsTask :
+#if netstandard
+ TaskCompletionSource<TResult>
+#else
+ Task<TResult>
+#endif
+ {
+ private static readonly Action<object> s_completionAction = state =>
+ {
+ if (!(state is ValueTaskSourceAsTask vtst) ||
+ !(vtst._source is IValueTaskSource<TResult> source))
+ {
+ // This could only happen if the IValueTaskSource<TResult> passed the wrong state
+ // or if this callback were invoked multiple times such that the state
+ // was previously nulled out.
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state);
+ return;
+ }
+
+ vtst._source = null;
+ ValueTaskSourceStatus status = source.GetStatus(vtst._token);
+ try
+ {
+ vtst.TrySetResult(source.GetResult(vtst._token));
+ }
+ catch (Exception exc)
+ {
+ if (status == ValueTaskSourceStatus.Canceled)
+ {
+#if netstandard
+ vtst.TrySetCanceled();
+#else
+ if (exc is OperationCanceledException oce)
+ {
+ vtst.TrySetCanceled(oce.CancellationToken, oce);
+ }
+ else
+ {
+ vtst.TrySetCanceled(new CancellationToken(true));
+ }
+#endif
+ }
+ else
+ {
+ vtst.TrySetException(exc);
+ }
+ }
+ };
+
+ /// <summary>The associated <see cref="IValueTaskSource"/>.</summary>
+ private IValueTaskSource<TResult> _source;
+ /// <summary>The token to pass through to operations on <see cref="_source"/></summary>
+ private readonly short _token;
+
+ public ValueTaskSourceAsTask(IValueTaskSource<TResult> source, short token)
+ {
+ _source = source;
+ _token = token;
+ source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None);
+ }
+ }
+
+ /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a completed operation.</summary>
+ public bool IsCompleted
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj == null)
+ {
+ return true;
+ }
+
+ if (obj is Task<TResult> t)
+ {
+ return t.IsCompleted;
+ }
+
+ return Unsafe.As<IValueTaskSource<TResult>>(obj).GetStatus(_token) != ValueTaskSourceStatus.Pending;
+ }
+ }
+
+ /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a successfully completed operation.</summary>
+ public bool IsCompletedSuccessfully
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj == null)
+ {
+ return true;
+ }
+
+ if (obj is Task<TResult> t)
+ {
+ return
+#if netstandard
+ t.Status == TaskStatus.RanToCompletion;
+#else
+ t.IsCompletedSuccessfully;
+#endif
+ }
+
+ return Unsafe.As<IValueTaskSource<TResult>>(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded;
+ }
+ }
+
+ /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a failed operation.</summary>
+ public bool IsFaulted
+ {
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj == null)
+ {
+ return false;
+ }
+
+ if (obj is Task<TResult> t)
+ {
+ return t.IsFaulted;
+ }
+
+ return Unsafe.As<IValueTaskSource<TResult>>(obj).GetStatus(_token) == ValueTaskSourceStatus.Faulted;
+ }
+ }
+
+ /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a canceled operation.</summary>
+ /// <remarks>
+ /// If the <see cref="ValueTask{TResult}"/> is backed by a result or by a <see cref="IValueTaskSource{TResult}"/>,
+ /// this will always return false. If it's backed by a <see cref="Task"/>, it'll return the
+ /// value of the task's <see cref="Task.IsCanceled"/> property.
+ /// </remarks>
+ public bool IsCanceled
+ {
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj == null)
+ {
+ return false;
+ }
+
+ if (obj is Task<TResult> t)
+ {
+ return t.IsCanceled;
+ }
+
+ return Unsafe.As<IValueTaskSource<TResult>>(obj).GetStatus(_token) == ValueTaskSourceStatus.Canceled;
+ }
+ }
+
+ /// <summary>Gets the result.</summary>
+ public TResult Result
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ object obj = _obj;
+ Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>);
+
+ if (obj == null)
+ {
+ return _result;
+ }
+
+ if (obj is Task<TResult> t)
+ {
+#if netstandard
+ return t.GetAwaiter().GetResult();
+#else
+ TaskAwaiter.ValidateEnd(t);
+ return t.ResultOnSuccess;
+#endif
+ }
+
+ return Unsafe.As<IValueTaskSource<TResult>>(obj).GetResult(_token);
+ }
+ }
+
+ /// <summary>Gets an awaiter for this <see cref="ValueTask{TResult}"/>.</summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ValueTaskAwaiter<TResult> GetAwaiter() => new ValueTaskAwaiter<TResult>(this);
+
+ /// <summary>Configures an awaiter for this <see cref="ValueTask{TResult}"/>.</summary>
+ /// <param name="continueOnCapturedContext">
+ /// true to attempt to marshal the continuation back to the captured context; otherwise, false.
+ /// </param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ConfiguredValueTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext) =>
+ new ConfiguredValueTaskAwaitable<TResult>(new ValueTask<TResult>(_obj, _result, _token, continueOnCapturedContext));
+
+ /// <summary>Gets a string-representation of this <see cref="ValueTask{TResult}"/>.</summary>
+ public override string ToString()
+ {
+ if (IsCompletedSuccessfully)
+ {
+ TResult result = Result;
+ if (result != null)
+ {
+ return result.ToString();
+ }
+ }
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadAbortException.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadAbortException.cs
new file mode 100644
index 0000000000..360e84d256
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadAbortException.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: An exception class which is thrown into a thread to cause it to
+** abort. This is a special non-catchable exception and results in
+** the thread's death. This is thrown by the VM only and can NOT be
+** thrown by any user thread, and subclassing this is useless.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class ThreadAbortException : SystemException
+ {
+ internal ThreadAbortException()
+ {
+ HResult = HResults.COR_E_THREADABORTED;
+ }
+
+ public object ExceptionState => null;
+
+ internal ThreadAbortException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadPriority.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadPriority.cs
new file mode 100644
index 0000000000..3b34bd5eac
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadPriority.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Threading
+{
+ public enum ThreadPriority
+ {
+ /*=========================================================================
+ ** Constants for thread priorities.
+ =========================================================================*/
+ Lowest = 0,
+ BelowNormal = 1,
+ Normal = 2,
+ AboveNormal = 3,
+ Highest = 4
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadStart.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadStart.cs
new file mode 100644
index 0000000000..5532539fc7
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadStart.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: This class is a Delegate which defines the start method
+** for starting a thread. That method must match this delegate.
+**
+**
+=============================================================================*/
+
+namespace System.Threading
+{
+ public delegate void ThreadStart();
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadStartException.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadStartException.cs
new file mode 100644
index 0000000000..5172555418
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadStartException.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class ThreadStartException : SystemException
+ {
+ internal ThreadStartException()
+ : base(SR.Arg_ThreadStartException)
+ {
+ HResult = HResults.COR_E_THREADSTART;
+ }
+
+ internal ThreadStartException(Exception reason)
+ : base(SR.Arg_ThreadStartException, reason)
+ {
+ HResult = HResults.COR_E_THREADSTART;
+ }
+
+ private ThreadStartException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadState.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadState.cs
new file mode 100644
index 0000000000..4bf3b5184d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadState.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Threading
+{
+ [Flags]
+ public enum ThreadState
+ {
+ /*=========================================================================
+ ** Constants for thread states.
+ =========================================================================*/
+ Running = 0,
+ StopRequested = 1,
+ SuspendRequested = 2,
+ Background = 4,
+ Unstarted = 8,
+ Stopped = 16,
+ WaitSleepJoin = 32,
+ Suspended = 64,
+ AbortRequested = 128,
+ Aborted = 256
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadStateException.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadStateException.cs
new file mode 100644
index 0000000000..6fc06a8516
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadStateException.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: An exception class to indicate that the Thread class is in an
+** invalid state for the method.
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class ThreadStateException : SystemException
+ {
+ public ThreadStateException()
+ : base(SR.Arg_ThreadStateException)
+ {
+ HResult = HResults.COR_E_THREADSTATE;
+ }
+
+ public ThreadStateException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_THREADSTATE;
+ }
+
+ public ThreadStateException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_THREADSTATE;
+ }
+
+ protected ThreadStateException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Timeout.cs b/src/System.Private.CoreLib/shared/System/Threading/Timeout.cs
new file mode 100644
index 0000000000..df1ea5f2bc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Timeout.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+using System;
+
+namespace System.Threading
+{
+ // A constant used by methods that take a timeout (Object.Wait, Thread.Sleep
+ // etc) to indicate that no timeout should occur.
+ //
+ public static class Timeout
+ {
+ public static readonly TimeSpan InfiniteTimeSpan = new TimeSpan(0, 0, 0, 0, Timeout.Infinite);
+
+ public const int Infinite = -1;
+ internal const uint UnsignedInfinite = unchecked((uint)-1);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/TimeoutHelper.cs b/src/System.Private.CoreLib/shared/System/Threading/TimeoutHelper.cs
new file mode 100644
index 0000000000..c96a4d00d6
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/TimeoutHelper.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Threading
+{
+ /// <summary>
+ /// A helper class to capture a start time using Environment.TickCout as a time in milliseconds, also updates a given timeout bu subtracting the current time from
+ /// the start time
+ /// </summary>
+ internal static class TimeoutHelper
+ {
+ /// <summary>
+ /// Returns the Environment.TickCount as a start time in milliseconds as a uint, TickCount tools over from positive to negative every ~ 25 days
+ /// then ~25 days to back to positive again, uint is sued to ignore the sign and double the range to 50 days
+ /// </summary>
+ /// <returns></returns>
+ public static uint GetTime()
+ {
+ return (uint)Environment.TickCount;
+ }
+
+ /// <summary>
+ /// Helper function to measure and update the elapsed time
+ /// </summary>
+ /// <param name="startTime"> The first time (in milliseconds) observed when the wait started</param>
+ /// <param name="originalWaitMillisecondsTimeout">The original wait timeout in milliseconds</param>
+ /// <returns>The new wait time in milliseconds, -1 if the time expired</returns>
+ public static int UpdateTimeOut(uint startTime, int originalWaitMillisecondsTimeout)
+ {
+ // The function must be called in case the time out is not infinite
+ Debug.Assert(originalWaitMillisecondsTimeout != Timeout.Infinite);
+
+ uint elapsedMilliseconds = (GetTime() - startTime);
+
+ // Check the elapsed milliseconds is greater than max int because this property is uint
+ if (elapsedMilliseconds > int.MaxValue)
+ {
+ return 0;
+ }
+
+ // Subtract the elapsed time from the current wait time
+ int currentWaitTimeout = originalWaitMillisecondsTimeout - (int)elapsedMilliseconds; ;
+ if (currentWaitTimeout <= 0)
+ {
+ return 0;
+ }
+
+ return currentWaitTimeout;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/WaitHandleCannotBeOpenedException.cs b/src/System.Private.CoreLib/shared/System/Threading/WaitHandleCannotBeOpenedException.cs
new file mode 100644
index 0000000000..e60e46d2be
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/WaitHandleCannotBeOpenedException.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class WaitHandleCannotBeOpenedException : ApplicationException
+ {
+ public WaitHandleCannotBeOpenedException() : base(SR.Threading_WaitHandleCannotBeOpenedException)
+ {
+ HResult = HResults.COR_E_WAITHANDLECANNOTBEOPENED;
+ }
+
+ public WaitHandleCannotBeOpenedException(String message) : base(message)
+ {
+ HResult = HResults.COR_E_WAITHANDLECANNOTBEOPENED;
+ }
+
+ public WaitHandleCannotBeOpenedException(String message, Exception innerException) : base(message, innerException)
+ {
+ HResult = HResults.COR_E_WAITHANDLECANNOTBEOPENED;
+ }
+
+ protected WaitHandleCannotBeOpenedException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeSpan.cs b/src/System.Private.CoreLib/shared/System/TimeSpan.cs
new file mode 100644
index 0000000000..4293ed505b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeSpan.cs
@@ -0,0 +1,564 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Globalization;
+
+namespace System
+{
+ // TimeSpan represents a duration of time. A TimeSpan can be negative
+ // or positive.
+ //
+ // TimeSpan is internally represented as a number of milliseconds. While
+ // this maps well into units of time such as hours and days, any
+ // periods longer than that aren't representable in a nice fashion.
+ // For instance, a month can be between 28 and 31 days, while a year
+ // can contain 365 or 364 days. A decade can have between 1 and 3 leapyears,
+ // depending on when you map the TimeSpan into the calendar. This is why
+ // we do not provide Years() or Months().
+ //
+ // Note: System.TimeSpan needs to interop with the WinRT structure
+ // type Windows::Foundation:TimeSpan. These types are currently binary-compatible in
+ // memory so no custom marshalling is required. If at any point the implementation
+ // details of this type should change, or new fields added, we need to remember to add
+ // an appropriate custom ILMarshaler to keep WInRT interop scenarios enabled.
+ //
+ [Serializable]
+ public struct TimeSpan : IComparable, IComparable<TimeSpan>, IEquatable<TimeSpan>, IFormattable, ISpanFormattable
+ {
+ public const long TicksPerMillisecond = 10000;
+ private const double MillisecondsPerTick = 1.0 / TicksPerMillisecond;
+
+ public const long TicksPerSecond = TicksPerMillisecond * 1000; // 10,000,000
+ private const double SecondsPerTick = 1.0 / TicksPerSecond; // 0.0001
+
+ public const long TicksPerMinute = TicksPerSecond * 60; // 600,000,000
+ private const double MinutesPerTick = 1.0 / TicksPerMinute; // 1.6666666666667e-9
+
+ public const long TicksPerHour = TicksPerMinute * 60; // 36,000,000,000
+ private const double HoursPerTick = 1.0 / TicksPerHour; // 2.77777777777777778e-11
+
+ public const long TicksPerDay = TicksPerHour * 24; // 864,000,000,000
+ private const double DaysPerTick = 1.0 / TicksPerDay; // 1.1574074074074074074e-12
+
+ private const int MillisPerSecond = 1000;
+ private const int MillisPerMinute = MillisPerSecond * 60; // 60,000
+ private const int MillisPerHour = MillisPerMinute * 60; // 3,600,000
+ private const int MillisPerDay = MillisPerHour * 24; // 86,400,000
+
+ internal const long MaxSeconds = Int64.MaxValue / TicksPerSecond;
+ internal const long MinSeconds = Int64.MinValue / TicksPerSecond;
+
+ internal const long MaxMilliSeconds = Int64.MaxValue / TicksPerMillisecond;
+ internal const long MinMilliSeconds = Int64.MinValue / TicksPerMillisecond;
+
+ internal const long TicksPerTenthSecond = TicksPerMillisecond * 100;
+
+ public static readonly TimeSpan Zero = new TimeSpan(0);
+
+ public static readonly TimeSpan MaxValue = new TimeSpan(Int64.MaxValue);
+ public static readonly TimeSpan MinValue = new TimeSpan(Int64.MinValue);
+
+ // internal so that DateTime doesn't have to call an extra get
+ // method for some arithmetic operations.
+ internal long _ticks; // Do not rename (binary serialization)
+
+ public TimeSpan(long ticks)
+ {
+ this._ticks = ticks;
+ }
+
+ public TimeSpan(int hours, int minutes, int seconds)
+ {
+ _ticks = TimeToTicks(hours, minutes, seconds);
+ }
+
+ public TimeSpan(int days, int hours, int minutes, int seconds)
+ : this(days, hours, minutes, seconds, 0)
+ {
+ }
+
+ public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds)
+ {
+ Int64 totalMilliSeconds = ((Int64)days * 3600 * 24 + (Int64)hours * 3600 + (Int64)minutes * 60 + seconds) * 1000 + milliseconds;
+ if (totalMilliSeconds > MaxMilliSeconds || totalMilliSeconds < MinMilliSeconds)
+ throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong);
+ _ticks = (long)totalMilliSeconds * TicksPerMillisecond;
+ }
+
+ public long Ticks
+ {
+ get { return _ticks; }
+ }
+
+ public int Days
+ {
+ get { return (int)(_ticks / TicksPerDay); }
+ }
+
+ public int Hours
+ {
+ get { return (int)((_ticks / TicksPerHour) % 24); }
+ }
+
+ public int Milliseconds
+ {
+ get { return (int)((_ticks / TicksPerMillisecond) % 1000); }
+ }
+
+ public int Minutes
+ {
+ get { return (int)((_ticks / TicksPerMinute) % 60); }
+ }
+
+ public int Seconds
+ {
+ get { return (int)((_ticks / TicksPerSecond) % 60); }
+ }
+
+ public double TotalDays
+ {
+ get { return ((double)_ticks) * DaysPerTick; }
+ }
+
+ public double TotalHours
+ {
+ get { return (double)_ticks * HoursPerTick; }
+ }
+
+ public double TotalMilliseconds
+ {
+ get
+ {
+ double temp = (double)_ticks * MillisecondsPerTick;
+ if (temp > MaxMilliSeconds)
+ return (double)MaxMilliSeconds;
+
+ if (temp < MinMilliSeconds)
+ return (double)MinMilliSeconds;
+
+ return temp;
+ }
+ }
+
+ public double TotalMinutes
+ {
+ get { return (double)_ticks * MinutesPerTick; }
+ }
+
+ public double TotalSeconds
+ {
+ get { return (double)_ticks * SecondsPerTick; }
+ }
+
+ public TimeSpan Add(TimeSpan ts)
+ {
+ long result = _ticks + ts._ticks;
+ // Overflow if signs of operands was identical and result's
+ // sign was opposite.
+ // >> 63 gives the sign bit (either 64 1's or 64 0's).
+ if ((_ticks >> 63 == ts._ticks >> 63) && (_ticks >> 63 != result >> 63))
+ throw new OverflowException(SR.Overflow_TimeSpanTooLong);
+ return new TimeSpan(result);
+ }
+
+
+ // Compares two TimeSpan values, returning an integer that indicates their
+ // relationship.
+ //
+ public static int Compare(TimeSpan t1, TimeSpan t2)
+ {
+ if (t1._ticks > t2._ticks) return 1;
+ if (t1._ticks < t2._ticks) return -1;
+ return 0;
+ }
+
+ // Returns a value less than zero if this object
+ public int CompareTo(Object value)
+ {
+ if (value == null) return 1;
+ if (!(value is TimeSpan))
+ throw new ArgumentException(SR.Arg_MustBeTimeSpan);
+ long t = ((TimeSpan)value)._ticks;
+ if (_ticks > t) return 1;
+ if (_ticks < t) return -1;
+ return 0;
+ }
+
+ public int CompareTo(TimeSpan value)
+ {
+ long t = value._ticks;
+ if (_ticks > t) return 1;
+ if (_ticks < t) return -1;
+ return 0;
+ }
+
+ public static TimeSpan FromDays(double value)
+ {
+ return Interval(value, MillisPerDay);
+ }
+
+ public TimeSpan Duration()
+ {
+ if (Ticks == TimeSpan.MinValue.Ticks)
+ throw new OverflowException(SR.Overflow_Duration);
+ return new TimeSpan(_ticks >= 0 ? _ticks : -_ticks);
+ }
+
+ public override bool Equals(Object value)
+ {
+ if (value is TimeSpan)
+ {
+ return _ticks == ((TimeSpan)value)._ticks;
+ }
+ return false;
+ }
+
+ public bool Equals(TimeSpan obj)
+ {
+ return _ticks == obj._ticks;
+ }
+
+ public static bool Equals(TimeSpan t1, TimeSpan t2)
+ {
+ return t1._ticks == t2._ticks;
+ }
+
+ public override int GetHashCode()
+ {
+ return (int)_ticks ^ (int)(_ticks >> 32);
+ }
+
+ public static TimeSpan FromHours(double value)
+ {
+ return Interval(value, MillisPerHour);
+ }
+
+ private static TimeSpan Interval(double value, int scale)
+ {
+ if (Double.IsNaN(value))
+ throw new ArgumentException(SR.Arg_CannotBeNaN);
+ double tmp = value * scale;
+ double millis = tmp + (value >= 0 ? 0.5 : -0.5);
+ if ((millis > Int64.MaxValue / TicksPerMillisecond) || (millis < Int64.MinValue / TicksPerMillisecond))
+ throw new OverflowException(SR.Overflow_TimeSpanTooLong);
+ return new TimeSpan((long)millis * TicksPerMillisecond);
+ }
+
+ public static TimeSpan FromMilliseconds(double value)
+ {
+ return Interval(value, 1);
+ }
+
+ public static TimeSpan FromMinutes(double value)
+ {
+ return Interval(value, MillisPerMinute);
+ }
+
+ public TimeSpan Negate()
+ {
+ if (Ticks == TimeSpan.MinValue.Ticks)
+ throw new OverflowException(SR.Overflow_NegateTwosCompNum);
+ return new TimeSpan(-_ticks);
+ }
+
+ public static TimeSpan FromSeconds(double value)
+ {
+ return Interval(value, MillisPerSecond);
+ }
+
+ public TimeSpan Subtract(TimeSpan ts)
+ {
+ long result = _ticks - ts._ticks;
+ // Overflow if signs of operands was different and result's
+ // sign was opposite from the first argument's sign.
+ // >> 63 gives the sign bit (either 64 1's or 64 0's).
+ if ((_ticks >> 63 != ts._ticks >> 63) && (_ticks >> 63 != result >> 63))
+ throw new OverflowException(SR.Overflow_TimeSpanTooLong);
+ return new TimeSpan(result);
+ }
+
+ public TimeSpan Multiply(double factor) => this * factor;
+
+ public TimeSpan Divide(double divisor) => this / divisor;
+
+ public double Divide(TimeSpan ts) => this / ts;
+
+ public static TimeSpan FromTicks(long value)
+ {
+ return new TimeSpan(value);
+ }
+
+ internal static long TimeToTicks(int hour, int minute, int second)
+ {
+ // totalSeconds is bounded by 2^31 * 2^12 + 2^31 * 2^8 + 2^31,
+ // which is less than 2^44, meaning we won't overflow totalSeconds.
+ long totalSeconds = (long)hour * 3600 + (long)minute * 60 + (long)second;
+ if (totalSeconds > MaxSeconds || totalSeconds < MinSeconds)
+ throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong);
+ return totalSeconds * TicksPerSecond;
+ }
+
+ // See System.Globalization.TimeSpanParse and System.Globalization.TimeSpanFormat
+ #region ParseAndFormat
+ private static void ValidateStyles(TimeSpanStyles style, String parameterName)
+ {
+ if (style != TimeSpanStyles.None && style != TimeSpanStyles.AssumeNegative)
+ throw new ArgumentException(SR.Argument_InvalidTimeSpanStyles, parameterName);
+ }
+ public static TimeSpan Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ /* Constructs a TimeSpan from a string. Leading and trailing white space characters are allowed. */
+ return TimeSpanParse.Parse(s, null);
+ }
+ public static TimeSpan Parse(String input, IFormatProvider formatProvider)
+ {
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ return TimeSpanParse.Parse(input, formatProvider);
+ }
+ public static TimeSpan Parse(ReadOnlySpan<char> input, IFormatProvider formatProvider = null)
+ {
+ return TimeSpanParse.Parse(input, formatProvider);
+ }
+ public static TimeSpan ParseExact(String input, String format, IFormatProvider formatProvider)
+ {
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
+ return TimeSpanParse.ParseExact(input, format, formatProvider, TimeSpanStyles.None);
+ }
+ public static TimeSpan ParseExact(String input, String[] formats, IFormatProvider formatProvider)
+ {
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None);
+ }
+ public static TimeSpan ParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles)
+ {
+ ValidateStyles(styles, nameof(styles));
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
+ return TimeSpanParse.ParseExact(input, format, formatProvider, styles);
+ }
+
+ public static TimeSpan ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles = TimeSpanStyles.None)
+ {
+ ValidateStyles(styles, nameof(styles));
+ return TimeSpanParse.ParseExact(input, format, formatProvider, styles);
+ }
+ public static TimeSpan ParseExact(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles)
+ {
+ ValidateStyles(styles, nameof(styles));
+ if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
+ return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, styles);
+ }
+ public static TimeSpan ParseExact(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles = TimeSpanStyles.None)
+ {
+ ValidateStyles(styles, nameof(styles));
+ return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, styles);
+ }
+ public static Boolean TryParse(String s, out TimeSpan result)
+ {
+ if (s == null)
+ {
+ result = default(TimeSpan);
+ return false;
+ }
+ return TimeSpanParse.TryParse(s, null, out result);
+ }
+ public static bool TryParse(ReadOnlySpan<char> s, out TimeSpan result)
+ {
+ return TimeSpanParse.TryParse(s, null, out result);
+ }
+
+ public static Boolean TryParse(String input, IFormatProvider formatProvider, out TimeSpan result)
+ {
+ if (input == null)
+ {
+ result = default(TimeSpan);
+ return false;
+ }
+ return TimeSpanParse.TryParse(input, formatProvider, out result);
+ }
+ public static bool TryParse(ReadOnlySpan<char> input, IFormatProvider formatProvider, out TimeSpan result)
+ {
+ return TimeSpanParse.TryParse(input, formatProvider, out result);
+ }
+ public static Boolean TryParseExact(String input, String format, IFormatProvider formatProvider, out TimeSpan result)
+ {
+ if (input == null || format == null)
+ {
+ result = default;
+ return false;
+ }
+ return TimeSpanParse.TryParseExact(input, format, formatProvider, TimeSpanStyles.None, out result);
+ }
+
+ public static bool TryParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, out TimeSpan result)
+ {
+ return TimeSpanParse.TryParseExact(input, format, formatProvider, TimeSpanStyles.None, out result);
+ }
+ public static Boolean TryParseExact(String input, String[] formats, IFormatProvider formatProvider, out TimeSpan result)
+ {
+ if (input == null)
+ {
+ result = default(TimeSpan);
+ return false;
+ }
+ return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None, out result);
+ }
+ public static bool TryParseExact(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, out TimeSpan result)
+ {
+ return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None, out result);
+ }
+
+ public static Boolean TryParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
+ {
+ ValidateStyles(styles, nameof(styles));
+ if (input == null || format == null)
+ {
+ result = default;
+ return false;
+ }
+
+ return TimeSpanParse.TryParseExact(input, format, formatProvider, styles, out result);
+ }
+
+ public static bool TryParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
+ {
+ ValidateStyles(styles, nameof(styles));
+ return TimeSpanParse.TryParseExact(input, format, formatProvider, styles, out result);
+ }
+ public static Boolean TryParseExact(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
+ {
+ ValidateStyles(styles, nameof(styles));
+ if (input == null)
+ {
+ result = default(TimeSpan);
+ return false;
+ }
+ return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, styles, out result);
+ }
+
+ public static bool TryParseExact(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
+ {
+ ValidateStyles(styles, nameof(styles));
+ return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, styles, out result);
+ }
+ public override String ToString()
+ {
+ return TimeSpanFormat.Format(this, null, null);
+ }
+ public String ToString(String format)
+ {
+ return TimeSpanFormat.Format(this, format, null);
+ }
+ public String ToString(String format, IFormatProvider formatProvider)
+ {
+ return TimeSpanFormat.Format(this, format, formatProvider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider formatProvider = null)
+ {
+ return TimeSpanFormat.TryFormat(this, destination, out charsWritten, format, formatProvider);
+ }
+ #endregion
+
+ public static TimeSpan operator -(TimeSpan t)
+ {
+ if (t._ticks == TimeSpan.MinValue._ticks)
+ throw new OverflowException(SR.Overflow_NegateTwosCompNum);
+ return new TimeSpan(-t._ticks);
+ }
+
+ public static TimeSpan operator -(TimeSpan t1, TimeSpan t2)
+ {
+ return t1.Subtract(t2);
+ }
+
+ public static TimeSpan operator +(TimeSpan t)
+ {
+ return t;
+ }
+
+ public static TimeSpan operator +(TimeSpan t1, TimeSpan t2)
+ {
+ return t1.Add(t2);
+ }
+
+ public static TimeSpan operator *(TimeSpan timeSpan, double factor)
+ {
+ if (double.IsNaN(factor))
+ {
+ throw new ArgumentException(SR.Arg_CannotBeNaN, nameof(factor));
+ }
+
+ // Rounding to the nearest tick is as close to the result we would have with unlimited
+ // precision as possible, and so likely to have the least potential to surprise.
+ double ticks = Math.Round(timeSpan.Ticks * factor);
+ if (ticks > long.MaxValue | ticks < long.MinValue)
+ {
+ throw new OverflowException(SR.Overflow_TimeSpanTooLong);
+ }
+
+ return FromTicks((long)ticks);
+ }
+
+ public static TimeSpan operator *(double factor, TimeSpan timeSpan) => timeSpan * factor;
+
+ public static TimeSpan operator /(TimeSpan timeSpan, double divisor)
+ {
+ if (double.IsNaN(divisor))
+ {
+ throw new ArgumentException(SR.Arg_CannotBeNaN, nameof(divisor));
+ }
+
+ double ticks = Math.Round(timeSpan.Ticks / divisor);
+ if (ticks > long.MaxValue | ticks < long.MinValue || double.IsNaN(ticks))
+ {
+ throw new OverflowException(SR.Overflow_TimeSpanTooLong);
+ }
+
+ return FromTicks((long)ticks);
+ }
+
+ // Using floating-point arithmetic directly means that infinities can be returned, which is reasonable
+ // if we consider TimeSpan.FromHours(1) / TimeSpan.Zero asks how many zero-second intervals there are in
+ // an hour for which infinity is the mathematic correct answer. Having TimeSpan.Zero / TimeSpan.Zero return NaN
+ // is perhaps less useful, but no less useful than an exception.
+ public static double operator /(TimeSpan t1, TimeSpan t2) => t1.Ticks / (double)t2.Ticks;
+
+ public static bool operator ==(TimeSpan t1, TimeSpan t2)
+ {
+ return t1._ticks == t2._ticks;
+ }
+
+ public static bool operator !=(TimeSpan t1, TimeSpan t2)
+ {
+ return t1._ticks != t2._ticks;
+ }
+
+ public static bool operator <(TimeSpan t1, TimeSpan t2)
+ {
+ return t1._ticks < t2._ticks;
+ }
+
+ public static bool operator <=(TimeSpan t1, TimeSpan t2)
+ {
+ return t1._ticks <= t2._ticks;
+ }
+
+ public static bool operator >(TimeSpan t1, TimeSpan t2)
+ {
+ return t1._ticks > t2._ticks;
+ }
+
+ public static bool operator >=(TimeSpan t1, TimeSpan t2)
+ {
+ return t1._ticks >= t2._ticks;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeZone.cs b/src/System.Private.CoreLib/shared/System/TimeZone.cs
new file mode 100644
index 0000000000..d4059babfc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeZone.cs
@@ -0,0 +1,280 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose:
+** This class is used to represent a TimeZone. It
+** has methods for converting a DateTime to UTC from local time
+** and to local time from UTC and methods for getting the
+** standard name and daylight name of the time zone.
+**
+** The only TimeZone that we support in version 1 is the
+** CurrentTimeZone as determined by the system timezone.
+**
+**
+============================================================*/
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Collections;
+using System.Globalization;
+
+namespace System
+{
+ [Obsolete("System.TimeZone has been deprecated. Please investigate the use of System.TimeZoneInfo instead.")]
+ public abstract class TimeZone
+ {
+ private static volatile TimeZone currentTimeZone = null;
+
+ // Private object for locking instead of locking on a public type for SQL reliability work.
+ private static Object s_InternalSyncObject;
+ private static Object InternalSyncObject
+ {
+ get
+ {
+ if (s_InternalSyncObject == null)
+ {
+ Object o = new Object();
+ Interlocked.CompareExchange<Object>(ref s_InternalSyncObject, o, null);
+ }
+ return s_InternalSyncObject;
+ }
+ }
+
+
+ protected TimeZone()
+ {
+ }
+
+ public static TimeZone CurrentTimeZone
+ {
+ get
+ {
+ //Grabbing the cached value is required at the top of this function so that
+ //we don't incur a race condition with the ResetTimeZone method below.
+ TimeZone tz = currentTimeZone;
+ if (tz == null)
+ {
+ lock (InternalSyncObject)
+ {
+ if (currentTimeZone == null)
+ {
+ currentTimeZone = new CurrentSystemTimeZone();
+ }
+ tz = currentTimeZone;
+ }
+ }
+ return (tz);
+ }
+ }
+
+ //This method is called by CultureInfo.ClearCachedData in response to control panel
+ //change events. It must be synchronized because otherwise there is a race condition
+ //with the CurrentTimeZone property above.
+ internal static void ResetTimeZone()
+ {
+ if (currentTimeZone != null)
+ {
+ lock (InternalSyncObject)
+ {
+ currentTimeZone = null;
+ }
+ }
+ }
+
+ public abstract String StandardName
+ {
+ get;
+ }
+
+ public abstract String DaylightName
+ {
+ get;
+ }
+
+ public abstract TimeSpan GetUtcOffset(DateTime time);
+
+ //
+ // Converts the specified datatime to the Universal time base on the current timezone
+ //
+ public virtual DateTime ToUniversalTime(DateTime time)
+ {
+ if (time.Kind == DateTimeKind.Utc)
+ {
+ return time;
+ }
+ long tickCount = time.Ticks - GetUtcOffset(time).Ticks;
+ if (tickCount > DateTime.MaxTicks)
+ {
+ return new DateTime(DateTime.MaxTicks, DateTimeKind.Utc);
+ }
+ if (tickCount < DateTime.MinTicks)
+ {
+ return new DateTime(DateTime.MinTicks, DateTimeKind.Utc);
+ }
+ return new DateTime(tickCount, DateTimeKind.Utc);
+ }
+
+ //
+ // Convert the specified datetime value from UTC to the local time based on the time zone.
+ //
+ public virtual DateTime ToLocalTime(DateTime time)
+ {
+ if (time.Kind == DateTimeKind.Local)
+ {
+ return time;
+ }
+ Boolean isAmbiguousLocalDst = false;
+ Int64 offset = ((CurrentSystemTimeZone)(TimeZone.CurrentTimeZone)).GetUtcOffsetFromUniversalTime(time, ref isAmbiguousLocalDst);
+ return new DateTime(time.Ticks + offset, DateTimeKind.Local, isAmbiguousLocalDst);
+ }
+
+ // Return an array of DaylightTime which reflects the daylight saving periods in a particular year.
+ // We currently only support having one DaylightSavingTime per year.
+ // If daylight saving time is not used in this timezone, null will be returned.
+ public abstract DaylightTime GetDaylightChanges(int year);
+
+ public virtual bool IsDaylightSavingTime(DateTime time)
+ {
+ return (IsDaylightSavingTime(time, GetDaylightChanges(time.Year)));
+ }
+
+ // Check if the specified time is in a daylight saving time. Allows the user to
+ // specify the array of Daylight Saving Times.
+ public static bool IsDaylightSavingTime(DateTime time, DaylightTime daylightTimes)
+ {
+ return CalculateUtcOffset(time, daylightTimes) != TimeSpan.Zero;
+ }
+
+ //
+ // NOTENOTE: Implementation detail
+ // In the transition from standard time to daylight saving time,
+ // if we convert local time to Universal time, we can have the
+ // following (take PST as an example):
+ // Local Universal UTC Offset
+ // ----- --------- ----------
+ // 01:00AM 09:00 -8:00
+ // 02:00 (=> 03:00) 10:00 -8:00 [This time doesn't actually exist, but it can be created from DateTime]
+ // 03:00 10:00 -7:00
+ // 04:00 11:00 -7:00
+ // 05:00 12:00 -7:00
+ //
+ // So from 02:00 - 02:59:59, we should return the standard offset, instead of the daylight saving offset.
+ //
+ // In the transition from daylight saving time to standard time,
+ // if we convert local time to Universal time, we can have the
+ // following (take PST as an example):
+ // Local Universal UTC Offset
+ // ----- --------- ----------
+ // 01:00AM 08:00 -7:00
+ // 02:00 (=> 01:00) 09:00 -8:00
+ // 02:00 10:00 -8:00
+ // 03:00 11:00 -8:00
+ // 04:00 12:00 -8:00
+ //
+ // So in this case, the 02:00 does exist after the first 2:00 rolls back to 01:00. We don't need to special case this.
+ // But note that there are two 01:00 in the local time.
+
+ //
+ // And imagine if the daylight saving offset is negative (although this does not exist in real life)
+ // In the transition from standard time to daylight saving time,
+ // if we convert local time to Universal time, we can have the
+ // following (take PST as an example, but the daylight saving offset is -01:00):
+ // Local Universal UTC Offset
+ // ----- --------- ----------
+ // 01:00AM 09:00 -8:00
+ // 02:00 (=> 01:00) 10:00 -9:00
+ // 02:00 11:00 -9:00
+ // 03:00 12:00 -9:00
+ // 04:00 13:00 -9:00
+ // 05:00 14:00 -9:00
+ //
+ // So in this case, the 02:00 does exist after the first 2:00 rolls back to 01:00. We don't need to special case this.
+ //
+ // In the transition from daylight saving time to standard time,
+ // if we convert local time to Universal time, we can have the
+ // following (take PST as an example, daylight saving offset is -01:00):
+ //
+ // Local Universal UTC Offset
+ // ----- --------- ----------
+ // 01:00AM 10:00 -9:00
+ // 02:00 (=> 03:00) 11:00 -9:00
+ // 03:00 11:00 -8:00
+ // 04:00 12:00 -8:00
+ // 05:00 13:00 -8:00
+ // 06:00 14:00 -8:00
+ //
+ // So from 02:00 - 02:59:59, we should return the daylight saving offset, instead of the standard offset.
+ //
+ internal static TimeSpan CalculateUtcOffset(DateTime time, DaylightTime daylightTimes)
+ {
+ if (daylightTimes == null)
+ {
+ return TimeSpan.Zero;
+ }
+ DateTimeKind kind = time.Kind;
+ if (kind == DateTimeKind.Utc)
+ {
+ return TimeSpan.Zero;
+ }
+
+ DateTime startTime;
+ DateTime endTime;
+
+ // startTime and endTime represent the period from either the start of DST to the end and includes the
+ // potentially overlapped times
+ startTime = daylightTimes.Start + daylightTimes.Delta;
+ endTime = daylightTimes.End;
+
+ // For normal time zones, the ambiguous hour is the last hour of daylight saving when you wind the
+ // clock back. It is theoretically possible to have a positive delta, (which would really be daylight
+ // reduction time), where you would have to wind the clock back in the begnning.
+ DateTime ambiguousStart;
+ DateTime ambiguousEnd;
+ if (daylightTimes.Delta.Ticks > 0)
+ {
+ ambiguousStart = endTime - daylightTimes.Delta;
+ ambiguousEnd = endTime;
+ }
+ else
+ {
+ ambiguousStart = startTime;
+ ambiguousEnd = startTime - daylightTimes.Delta;
+ }
+
+ Boolean isDst = false;
+ if (startTime > endTime)
+ {
+ // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year.
+ // Note, the summer in the southern hemisphere begins late in the year.
+ if (time >= startTime || time < endTime)
+ {
+ isDst = true;
+ }
+ }
+ else if (time >= startTime && time < endTime)
+ {
+ // In northern hemisphere, the daylight saving time starts in the middle of the year.
+ isDst = true;
+ }
+
+ // If this date was previously converted from a UTC date and we were able to detect that the local
+ // DateTime would be ambiguous, this data is stored in the DateTime to resolve this ambiguity.
+ if (isDst && time >= ambiguousStart && time < ambiguousEnd)
+ {
+ isDst = time.IsAmbiguousDaylightSavingTime();
+ }
+
+ if (isDst)
+ {
+ return daylightTimes.Delta;
+ }
+ return TimeSpan.Zero;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.AdjustmentRule.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.AdjustmentRule.cs
new file mode 100644
index 0000000000..0e949a30ec
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.AdjustmentRule.cs
@@ -0,0 +1,248 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ public sealed partial class TimeZoneInfo
+ {
+ [Serializable]
+ public sealed class AdjustmentRule : IEquatable<AdjustmentRule>, ISerializable, IDeserializationCallback
+ {
+ private readonly DateTime _dateStart;
+ private readonly DateTime _dateEnd;
+ private readonly TimeSpan _daylightDelta;
+ private readonly TransitionTime _daylightTransitionStart;
+ private readonly TransitionTime _daylightTransitionEnd;
+ private readonly TimeSpan _baseUtcOffsetDelta; // delta from the default Utc offset (utcOffset = defaultUtcOffset + _baseUtcOffsetDelta)
+ private readonly bool _noDaylightTransitions;
+
+ public DateTime DateStart => _dateStart;
+
+ public DateTime DateEnd => _dateEnd;
+
+ public TimeSpan DaylightDelta => _daylightDelta;
+
+ public TransitionTime DaylightTransitionStart => _daylightTransitionStart;
+
+ public TransitionTime DaylightTransitionEnd => _daylightTransitionEnd;
+
+ internal TimeSpan BaseUtcOffsetDelta => _baseUtcOffsetDelta;
+
+ /// <summary>
+ /// Gets a value indicating that this AdjustmentRule fixes the time zone offset
+ /// from DateStart to DateEnd without any daylight transitions in between.
+ /// </summary>
+ internal bool NoDaylightTransitions => _noDaylightTransitions;
+
+ internal bool HasDaylightSaving =>
+ DaylightDelta != TimeSpan.Zero ||
+ (DaylightTransitionStart != default(TransitionTime) && DaylightTransitionStart.TimeOfDay != DateTime.MinValue) ||
+ (DaylightTransitionEnd != default(TransitionTime) && DaylightTransitionEnd.TimeOfDay != DateTime.MinValue.AddMilliseconds(1));
+
+ public bool Equals(AdjustmentRule other) =>
+ other != null &&
+ _dateStart == other._dateStart &&
+ _dateEnd == other._dateEnd &&
+ _daylightDelta == other._daylightDelta &&
+ _baseUtcOffsetDelta == other._baseUtcOffsetDelta &&
+ _daylightTransitionEnd.Equals(other._daylightTransitionEnd) &&
+ _daylightTransitionStart.Equals(other._daylightTransitionStart);
+
+ public override int GetHashCode() => _dateStart.GetHashCode();
+
+ private AdjustmentRule(
+ DateTime dateStart,
+ DateTime dateEnd,
+ TimeSpan daylightDelta,
+ TransitionTime daylightTransitionStart,
+ TransitionTime daylightTransitionEnd,
+ TimeSpan baseUtcOffsetDelta,
+ bool noDaylightTransitions)
+ {
+ ValidateAdjustmentRule(dateStart, dateEnd, daylightDelta,
+ daylightTransitionStart, daylightTransitionEnd, noDaylightTransitions);
+
+ _dateStart = dateStart;
+ _dateEnd = dateEnd;
+ _daylightDelta = daylightDelta;
+ _daylightTransitionStart = daylightTransitionStart;
+ _daylightTransitionEnd = daylightTransitionEnd;
+ _baseUtcOffsetDelta = baseUtcOffsetDelta;
+ _noDaylightTransitions = noDaylightTransitions;
+ }
+
+ public static AdjustmentRule CreateAdjustmentRule(
+ DateTime dateStart,
+ DateTime dateEnd,
+ TimeSpan daylightDelta,
+ TransitionTime daylightTransitionStart,
+ TransitionTime daylightTransitionEnd)
+ {
+ return new AdjustmentRule(
+ dateStart,
+ dateEnd,
+ daylightDelta,
+ daylightTransitionStart,
+ daylightTransitionEnd,
+ baseUtcOffsetDelta: TimeSpan.Zero,
+ noDaylightTransitions: false);
+ }
+
+ internal static AdjustmentRule CreateAdjustmentRule(
+ DateTime dateStart,
+ DateTime dateEnd,
+ TimeSpan daylightDelta,
+ TransitionTime daylightTransitionStart,
+ TransitionTime daylightTransitionEnd,
+ TimeSpan baseUtcOffsetDelta,
+ bool noDaylightTransitions)
+ {
+ return new AdjustmentRule(
+ dateStart,
+ dateEnd,
+ daylightDelta,
+ daylightTransitionStart,
+ daylightTransitionEnd,
+ baseUtcOffsetDelta,
+ noDaylightTransitions);
+ }
+
+ //
+ // When Windows sets the daylight transition start Jan 1st at 12:00 AM, it means the year starts with the daylight saving on.
+ // We have to special case this value and not adjust it when checking if any date is in the daylight saving period.
+ //
+ internal bool IsStartDateMarkerForBeginningOfYear() =>
+ !NoDaylightTransitions &&
+ DaylightTransitionStart.Month == 1 && DaylightTransitionStart.Day == 1 && DaylightTransitionStart.TimeOfDay.Hour == 0 &&
+ DaylightTransitionStart.TimeOfDay.Minute == 0 && DaylightTransitionStart.TimeOfDay.Second == 0 &&
+ _dateStart.Year == _dateEnd.Year;
+
+ //
+ // When Windows sets the daylight transition end Jan 1st at 12:00 AM, it means the year ends with the daylight saving on.
+ // We have to special case this value and not adjust it when checking if any date is in the daylight saving period.
+ //
+ internal bool IsEndDateMarkerForEndOfYear() =>
+ !NoDaylightTransitions &&
+ DaylightTransitionEnd.Month == 1 && DaylightTransitionEnd.Day == 1 && DaylightTransitionEnd.TimeOfDay.Hour == 0 &&
+ DaylightTransitionEnd.TimeOfDay.Minute == 0 && DaylightTransitionEnd.TimeOfDay.Second == 0 &&
+ _dateStart.Year == _dateEnd.Year;
+
+ /// <summary>
+ /// Helper function that performs all of the validation checks for the factory methods and deserialization callback.
+ /// </summary>
+ private static void ValidateAdjustmentRule(
+ DateTime dateStart,
+ DateTime dateEnd,
+ TimeSpan daylightDelta,
+ TransitionTime daylightTransitionStart,
+ TransitionTime daylightTransitionEnd,
+ bool noDaylightTransitions)
+ {
+ if (dateStart.Kind != DateTimeKind.Unspecified && dateStart.Kind != DateTimeKind.Utc)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeKindMustBeUnspecifiedOrUtc, nameof(dateStart));
+ }
+
+ if (dateEnd.Kind != DateTimeKind.Unspecified && dateEnd.Kind != DateTimeKind.Utc)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeKindMustBeUnspecifiedOrUtc, nameof(dateEnd));
+ }
+
+ if (daylightTransitionStart.Equals(daylightTransitionEnd) && !noDaylightTransitions)
+ {
+ throw new ArgumentException(SR.Argument_TransitionTimesAreIdentical, nameof(daylightTransitionEnd));
+ }
+
+ if (dateStart > dateEnd)
+ {
+ throw new ArgumentException(SR.Argument_OutOfOrderDateTimes, nameof(dateStart));
+ }
+
+ // This cannot use UtcOffsetOutOfRange to account for the scenario where Samoa moved across the International Date Line,
+ // which caused their current BaseUtcOffset to be +13. But on the other side of the line it was UTC-11 (+1 for daylight).
+ // So when trying to describe DaylightDeltas for those times, the DaylightDelta needs
+ // to be -23 (what it takes to go from UTC+13 to UTC-10)
+ if (daylightDelta.TotalHours < -23.0 || daylightDelta.TotalHours > 14.0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(daylightDelta), daylightDelta, SR.ArgumentOutOfRange_UtcOffset);
+ }
+
+ if (daylightDelta.Ticks % TimeSpan.TicksPerMinute != 0)
+ {
+ throw new ArgumentException(SR.Argument_TimeSpanHasSeconds, nameof(daylightDelta));
+ }
+
+ if (dateStart != DateTime.MinValue && dateStart.Kind == DateTimeKind.Unspecified && dateStart.TimeOfDay != TimeSpan.Zero)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeHasTimeOfDay, nameof(dateStart));
+ }
+
+ if (dateEnd != DateTime.MaxValue && dateEnd.Kind == DateTimeKind.Unspecified && dateEnd.TimeOfDay != TimeSpan.Zero)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeHasTimeOfDay, nameof(dateEnd));
+ }
+ }
+
+ void IDeserializationCallback.OnDeserialization(object sender)
+ {
+ // OnDeserialization is called after each instance of this class is deserialized.
+ // This callback method performs AdjustmentRule validation after being deserialized.
+
+ try
+ {
+ ValidateAdjustmentRule(_dateStart, _dateEnd, _daylightDelta,
+ _daylightTransitionStart, _daylightTransitionEnd, _noDaylightTransitions);
+ }
+ catch (ArgumentException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+ }
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ info.AddValue("DateStart", _dateStart); // Do not rename (binary serialization)
+ info.AddValue("DateEnd", _dateEnd); // Do not rename (binary serialization)
+ info.AddValue("DaylightDelta", _daylightDelta); // Do not rename (binary serialization)
+ info.AddValue("DaylightTransitionStart", _daylightTransitionStart); // Do not rename (binary serialization)
+ info.AddValue("DaylightTransitionEnd", _daylightTransitionEnd); // Do not rename (binary serialization)
+ info.AddValue("BaseUtcOffsetDelta", _baseUtcOffsetDelta); // Do not rename (binary serialization)
+ info.AddValue("NoDaylightTransitions", _noDaylightTransitions); // Do not rename (binary serialization)
+ }
+
+ private AdjustmentRule(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ _dateStart = (DateTime)info.GetValue("DateStart", typeof(DateTime)); // Do not rename (binary serialization)
+ _dateEnd = (DateTime)info.GetValue("DateEnd", typeof(DateTime)); // Do not rename (binary serialization)
+ _daylightDelta = (TimeSpan)info.GetValue("DaylightDelta", typeof(TimeSpan)); // Do not rename (binary serialization)
+ _daylightTransitionStart = (TransitionTime)info.GetValue("DaylightTransitionStart", typeof(TransitionTime)); // Do not rename (binary serialization)
+ _daylightTransitionEnd = (TransitionTime)info.GetValue("DaylightTransitionEnd", typeof(TransitionTime)); // Do not rename (binary serialization)
+
+ object o = info.GetValueNoThrow("BaseUtcOffsetDelta", typeof(TimeSpan)); // Do not rename (binary serialization)
+ if (o != null)
+ {
+ _baseUtcOffsetDelta = (TimeSpan)o;
+ }
+
+ o = info.GetValueNoThrow("NoDaylightTransitions", typeof(bool)); // Do not rename (binary serialization)
+ if (o != null)
+ {
+ _noDaylightTransitions = (bool)o;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.StringSerializer.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.StringSerializer.cs
new file mode 100644
index 0000000000..e3e9ddbf58
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.StringSerializer.cs
@@ -0,0 +1,625 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Globalization;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace System
+{
+ public sealed partial class TimeZoneInfo
+ {
+ /// <summary>
+ /// Used to serialize and deserialize TimeZoneInfo objects based on the custom string serialization format.
+ /// </summary>
+ private struct StringSerializer
+ {
+ private enum State
+ {
+ Escaped = 0,
+ NotEscaped = 1,
+ StartOfToken = 2,
+ EndOfLine = 3
+ }
+
+ private readonly string _serializedText;
+ private int _currentTokenStartIndex;
+ private State _state;
+
+ // the majority of the strings contained in the OS time zones fit in 64 chars
+ private const int InitialCapacityForString = 64;
+ private const char Esc = '\\';
+ private const char Sep = ';';
+ private const char Lhs = '[';
+ private const char Rhs = ']';
+ private const string DateTimeFormat = "MM:dd:yyyy";
+ private const string TimeOfDayFormat = "HH:mm:ss.FFF";
+
+ /// <summary>
+ /// Creates the custom serialized string representation of a TimeZoneInfo instance.
+ /// </summary>
+ public static string GetSerializedString(TimeZoneInfo zone)
+ {
+ StringBuilder serializedText = StringBuilderCache.Acquire();
+
+ //
+ // <_id>;<_baseUtcOffset>;<_displayName>;<_standardDisplayName>;<_daylightDispayName>
+ //
+ SerializeSubstitute(zone.Id, serializedText);
+ serializedText.Append(Sep);
+ serializedText.Append(zone.BaseUtcOffset.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+ serializedText.Append(Sep);
+ SerializeSubstitute(zone.DisplayName, serializedText);
+ serializedText.Append(Sep);
+ SerializeSubstitute(zone.StandardName, serializedText);
+ serializedText.Append(Sep);
+ SerializeSubstitute(zone.DaylightName, serializedText);
+ serializedText.Append(Sep);
+
+ AdjustmentRule[] rules = zone.GetAdjustmentRules();
+ foreach (AdjustmentRule rule in rules)
+ {
+ serializedText.Append(Lhs);
+ serializedText.Append(rule.DateStart.ToString(DateTimeFormat, DateTimeFormatInfo.InvariantInfo));
+ serializedText.Append(Sep);
+ serializedText.Append(rule.DateEnd.ToString(DateTimeFormat, DateTimeFormatInfo.InvariantInfo));
+ serializedText.Append(Sep);
+ serializedText.Append(rule.DaylightDelta.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+ serializedText.Append(Sep);
+ // serialize the TransitionTime's
+ SerializeTransitionTime(rule.DaylightTransitionStart, serializedText);
+ serializedText.Append(Sep);
+ SerializeTransitionTime(rule.DaylightTransitionEnd, serializedText);
+ serializedText.Append(Sep);
+ if (rule.BaseUtcOffsetDelta != TimeSpan.Zero)
+ {
+ // Serialize it only when BaseUtcOffsetDelta has a value to reduce the impact of adding rule.BaseUtcOffsetDelta
+ serializedText.Append(rule.BaseUtcOffsetDelta.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+ serializedText.Append(Sep);
+ }
+ if (rule.NoDaylightTransitions)
+ {
+ // Serialize it only when NoDaylightTransitions is true to reduce the impact of adding rule.NoDaylightTransitions
+ serializedText.Append('1');
+ serializedText.Append(Sep);
+ }
+ serializedText.Append(Rhs);
+ }
+ serializedText.Append(Sep);
+
+ return StringBuilderCache.GetStringAndRelease(serializedText);
+ }
+
+ /// <summary>
+ /// Instantiates a TimeZoneInfo from a custom serialized string.
+ /// </summary>
+ public static TimeZoneInfo GetDeserializedTimeZoneInfo(string source)
+ {
+ StringSerializer s = new StringSerializer(source);
+
+ string id = s.GetNextStringValue();
+ TimeSpan baseUtcOffset = s.GetNextTimeSpanValue();
+ string displayName = s.GetNextStringValue();
+ string standardName = s.GetNextStringValue();
+ string daylightName = s.GetNextStringValue();
+ AdjustmentRule[] rules = s.GetNextAdjustmentRuleArrayValue();
+
+ try
+ {
+ return new TimeZoneInfo(id, baseUtcOffset, displayName, standardName, daylightName, rules, disableDaylightSavingTime: false);
+ }
+ catch (ArgumentException ex)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, ex);
+ }
+ catch (InvalidTimeZoneException ex)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, ex);
+ }
+ }
+
+ private StringSerializer(string str)
+ {
+ _serializedText = str;
+ _currentTokenStartIndex = 0;
+ _state = State.StartOfToken;
+ }
+
+ /// <summary>
+ /// Appends the String to the StringBuilder with all of the reserved chars escaped.
+ ///
+ /// ";" -> "\;"
+ /// "[" -> "\["
+ /// "]" -> "\]"
+ /// "\" -> "\\"
+ /// </summary>
+ private static void SerializeSubstitute(string text, StringBuilder serializedText)
+ {
+ foreach (char c in text)
+ {
+ if (c == Esc || c == Lhs || c == Rhs || c == Sep)
+ {
+ serializedText.Append('\\');
+ }
+ serializedText.Append(c);
+ }
+ }
+
+ /// <summary>
+ /// Helper method to serialize a TimeZoneInfo.TransitionTime object.
+ /// </summary>
+ private static void SerializeTransitionTime(TransitionTime time, StringBuilder serializedText)
+ {
+ serializedText.Append(Lhs);
+ serializedText.Append(time.IsFixedDateRule ? '1' : '0');
+ serializedText.Append(Sep);
+ serializedText.Append(time.TimeOfDay.ToString(TimeOfDayFormat, DateTimeFormatInfo.InvariantInfo));
+ serializedText.Append(Sep);
+ serializedText.Append(time.Month.ToString(CultureInfo.InvariantCulture));
+ serializedText.Append(Sep);
+ if (time.IsFixedDateRule)
+ {
+ serializedText.Append(time.Day.ToString(CultureInfo.InvariantCulture));
+ serializedText.Append(Sep);
+ }
+ else
+ {
+ serializedText.Append(time.Week.ToString(CultureInfo.InvariantCulture));
+ serializedText.Append(Sep);
+ serializedText.Append(((int)time.DayOfWeek).ToString(CultureInfo.InvariantCulture));
+ serializedText.Append(Sep);
+ }
+ serializedText.Append(Rhs);
+ }
+
+ /// <summary>
+ /// Helper function to determine if the passed in string token is allowed to be preceded by an escape sequence token.
+ /// </summary>
+ private static void VerifyIsEscapableCharacter(char c)
+ {
+ if (c != Esc && c != Sep && c != Lhs && c != Rhs)
+ {
+ throw new SerializationException(SR.Format(SR.Serialization_InvalidEscapeSequence, c));
+ }
+ }
+
+ /// <summary>
+ /// Helper function that reads past "v.Next" data fields. Receives a "depth" parameter indicating the
+ /// current relative nested bracket depth that _currentTokenStartIndex is at. The function ends
+ /// successfully when "depth" returns to zero (0).
+ /// </summary>
+ private void SkipVersionNextDataFields(int depth /* starting depth in the nested brackets ('[', ']')*/)
+ {
+ if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+ State tokenState = State.NotEscaped;
+
+ // walk the serialized text, building up the token as we go...
+ for (int i = _currentTokenStartIndex; i < _serializedText.Length; i++)
+ {
+ if (tokenState == State.Escaped)
+ {
+ VerifyIsEscapableCharacter(_serializedText[i]);
+ tokenState = State.NotEscaped;
+ }
+ else if (tokenState == State.NotEscaped)
+ {
+ switch (_serializedText[i])
+ {
+ case Esc:
+ tokenState = State.Escaped;
+ break;
+
+ case Lhs:
+ depth++;
+ break;
+ case Rhs:
+ depth--;
+ if (depth == 0)
+ {
+ _currentTokenStartIndex = i + 1;
+ if (_currentTokenStartIndex >= _serializedText.Length)
+ {
+ _state = State.EndOfLine;
+ }
+ else
+ {
+ _state = State.StartOfToken;
+ }
+ return;
+ }
+ break;
+
+ case '\0':
+ // invalid character
+ throw new SerializationException(SR.Serialization_InvalidData);
+
+ default:
+ break;
+ }
+ }
+ }
+
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ /// <summary>
+ /// Helper function that reads a string token from the serialized text. The function
+ /// updates <see cref="_currentTokenStartIndex"/> to point to the next token on exit.
+ /// Also <see cref="_state"/> is set to either <see cref="State.StartOfToken"/> or
+ /// <see cref="State.EndOfLine"/> on exit.
+ /// </summary>
+ private string GetNextStringValue()
+ {
+ // first verify the internal state of the object
+ if (_state == State.EndOfLine)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+ if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+ State tokenState = State.NotEscaped;
+ StringBuilder token = StringBuilderCache.Acquire(InitialCapacityForString);
+
+ // walk the serialized text, building up the token as we go...
+ for (int i = _currentTokenStartIndex; i < _serializedText.Length; i++)
+ {
+ if (tokenState == State.Escaped)
+ {
+ VerifyIsEscapableCharacter(_serializedText[i]);
+ token.Append(_serializedText[i]);
+ tokenState = State.NotEscaped;
+ }
+ else if (tokenState == State.NotEscaped)
+ {
+ switch (_serializedText[i])
+ {
+ case Esc:
+ tokenState = State.Escaped;
+ break;
+
+ case Lhs:
+ // '[' is an unexpected character
+ throw new SerializationException(SR.Serialization_InvalidData);
+
+ case Rhs:
+ // ']' is an unexpected character
+ throw new SerializationException(SR.Serialization_InvalidData);
+
+ case Sep:
+ _currentTokenStartIndex = i + 1;
+ if (_currentTokenStartIndex >= _serializedText.Length)
+ {
+ _state = State.EndOfLine;
+ }
+ else
+ {
+ _state = State.StartOfToken;
+ }
+ return StringBuilderCache.GetStringAndRelease(token);
+
+ case '\0':
+ // invalid character
+ throw new SerializationException(SR.Serialization_InvalidData);
+
+ default:
+ token.Append(_serializedText[i]);
+ break;
+ }
+ }
+ }
+ //
+ // we are at the end of the line
+ //
+ if (tokenState == State.Escaped)
+ {
+ // we are at the end of the serialized text but we are in an escaped state
+ throw new SerializationException(SR.Format(SR.Serialization_InvalidEscapeSequence, string.Empty));
+ }
+
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ /// <summary>
+ /// Helper function to read a DateTime token.
+ /// </summary>
+ private DateTime GetNextDateTimeValue(string format)
+ {
+ string token = GetNextStringValue();
+ DateTime time;
+ if (!DateTime.TryParseExact(token, format, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out time))
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+ return time;
+ }
+
+ /// <summary>
+ /// Helper function to read a TimeSpan token.
+ /// </summary>
+ private TimeSpan GetNextTimeSpanValue()
+ {
+ int token = GetNextInt32Value();
+ try
+ {
+ return new TimeSpan(hours: 0, minutes: token, seconds: 0);
+ }
+ catch (ArgumentOutOfRangeException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+ }
+
+ /// <summary>
+ /// Helper function to read an Int32 token.
+ /// </summary>
+ private int GetNextInt32Value()
+ {
+ string token = GetNextStringValue();
+ int value;
+ if (!int.TryParse(token, NumberStyles.AllowLeadingSign /* "[sign]digits" */, CultureInfo.InvariantCulture, out value))
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+ return value;
+ }
+
+ /// <summary>
+ /// Helper function to read an AdjustmentRule[] token.
+ /// </summary>
+ private AdjustmentRule[] GetNextAdjustmentRuleArrayValue()
+ {
+ List<AdjustmentRule> rules = new List<AdjustmentRule>(1);
+ int count = 0;
+
+ // individual AdjustmentRule array elements do not require semicolons
+ AdjustmentRule rule = GetNextAdjustmentRuleValue();
+ while (rule != null)
+ {
+ rules.Add(rule);
+ count++;
+
+ rule = GetNextAdjustmentRuleValue();
+ }
+
+ // the AdjustmentRule array must end with a separator
+ if (_state == State.EndOfLine)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+ if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ return count != 0 ? rules.ToArray() : null;
+ }
+
+ /// <summary>
+ /// Helper function to read an AdjustmentRule token.
+ /// </summary>
+ private AdjustmentRule GetNextAdjustmentRuleValue()
+ {
+ // first verify the internal state of the object
+ if (_state == State.EndOfLine)
+ {
+ return null;
+ }
+
+ if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ // check to see if the very first token we see is the separator
+ if (_serializedText[_currentTokenStartIndex] == Sep)
+ {
+ return null;
+ }
+
+ // verify the current token is a left-hand-side marker ("[")
+ if (_serializedText[_currentTokenStartIndex] != Lhs)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+ _currentTokenStartIndex++;
+
+ DateTime dateStart = GetNextDateTimeValue(DateTimeFormat);
+ DateTime dateEnd = GetNextDateTimeValue(DateTimeFormat);
+ TimeSpan daylightDelta = GetNextTimeSpanValue();
+ TransitionTime daylightStart = GetNextTransitionTimeValue();
+ TransitionTime daylightEnd = GetNextTransitionTimeValue();
+ TimeSpan baseUtcOffsetDelta = TimeSpan.Zero;
+ int noDaylightTransitions = 0;
+
+ // verify that the string is now at the right-hand-side marker ("]") ...
+
+ if (_state == State.EndOfLine || _currentTokenStartIndex >= _serializedText.Length)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ // Check if we have baseUtcOffsetDelta in the serialized string and then deserialize it
+ if ((_serializedText[_currentTokenStartIndex] >= '0' && _serializedText[_currentTokenStartIndex] <= '9') ||
+ _serializedText[_currentTokenStartIndex] == '-' || _serializedText[_currentTokenStartIndex] == '+')
+ {
+ baseUtcOffsetDelta = GetNextTimeSpanValue();
+ }
+
+ // Check if we have NoDaylightTransitions in the serialized string and then deserialize it
+ if ((_serializedText[_currentTokenStartIndex] >= '0' && _serializedText[_currentTokenStartIndex] <= '1'))
+ {
+ noDaylightTransitions = GetNextInt32Value();
+ }
+
+ if (_state == State.EndOfLine || _currentTokenStartIndex >= _serializedText.Length)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ if (_serializedText[_currentTokenStartIndex] != Rhs)
+ {
+ // skip ahead of any "v.Next" data at the end of the AdjustmentRule
+ //
+ // FUTURE: if the serialization format is extended in the future then this
+ // code section will need to be changed to read the new fields rather
+ // than just skipping the data at the end of the [AdjustmentRule].
+ SkipVersionNextDataFields(1);
+ }
+ else
+ {
+ _currentTokenStartIndex++;
+ }
+
+ // create the AdjustmentRule from the deserialized fields ...
+
+ AdjustmentRule rule;
+ try
+ {
+ rule = AdjustmentRule.CreateAdjustmentRule(dateStart, dateEnd, daylightDelta, daylightStart, daylightEnd, baseUtcOffsetDelta, noDaylightTransitions > 0);
+ }
+ catch (ArgumentException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+
+ // finally set the state to either EndOfLine or StartOfToken for the next caller
+ if (_currentTokenStartIndex >= _serializedText.Length)
+ {
+ _state = State.EndOfLine;
+ }
+ else
+ {
+ _state = State.StartOfToken;
+ }
+ return rule;
+ }
+
+ /// <summary>
+ /// Helper function to read a TransitionTime token.
+ /// </summary>
+ private TransitionTime GetNextTransitionTimeValue()
+ {
+ // first verify the internal state of the object
+
+ if (_state == State.EndOfLine ||
+ (_currentTokenStartIndex < _serializedText.Length && _serializedText[_currentTokenStartIndex] == Rhs))
+ {
+ //
+ // we are at the end of the line or we are starting at a "]" character
+ //
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ // verify the current token is a left-hand-side marker ("[")
+
+ if (_serializedText[_currentTokenStartIndex] != Lhs)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+ _currentTokenStartIndex++;
+
+ int isFixedDate = GetNextInt32Value();
+
+ if (isFixedDate != 0 && isFixedDate != 1)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ TransitionTime transition;
+
+ DateTime timeOfDay = GetNextDateTimeValue(TimeOfDayFormat);
+ timeOfDay = new DateTime(1, 1, 1, timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
+
+ int month = GetNextInt32Value();
+
+ if (isFixedDate == 1)
+ {
+ int day = GetNextInt32Value();
+
+ try
+ {
+ transition = TransitionTime.CreateFixedDateRule(timeOfDay, month, day);
+ }
+ catch (ArgumentException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+ }
+ else
+ {
+ int week = GetNextInt32Value();
+ int dayOfWeek = GetNextInt32Value();
+
+ try
+ {
+ transition = TransitionTime.CreateFloatingDateRule(timeOfDay, month, week, (DayOfWeek)dayOfWeek);
+ }
+ catch (ArgumentException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+ }
+
+ // verify that the string is now at the right-hand-side marker ("]") ...
+
+ if (_state == State.EndOfLine || _currentTokenStartIndex >= _serializedText.Length)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ if (_serializedText[_currentTokenStartIndex] != Rhs)
+ {
+ // skip ahead of any "v.Next" data at the end of the AdjustmentRule
+ //
+ // FUTURE: if the serialization format is extended in the future then this
+ // code section will need to be changed to read the new fields rather
+ // than just skipping the data at the end of the [TransitionTime].
+ SkipVersionNextDataFields(1);
+ }
+ else
+ {
+ _currentTokenStartIndex++;
+ }
+
+ // check to see if the string is now at the separator (";") ...
+ bool sepFound = false;
+ if (_currentTokenStartIndex < _serializedText.Length &&
+ _serializedText[_currentTokenStartIndex] == Sep)
+ {
+ // handle the case where we ended on a ";"
+ _currentTokenStartIndex++;
+ sepFound = true;
+ }
+
+ if (!sepFound)
+ {
+ // we MUST end on a separator
+ throw new SerializationException(SR.Serialization_InvalidData);
+ }
+
+ // finally set the state to either EndOfLine or StartOfToken for the next caller
+ if (_currentTokenStartIndex >= _serializedText.Length)
+ {
+ _state = State.EndOfLine;
+ }
+ else
+ {
+ _state = State.StartOfToken;
+ }
+ return transition;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.TransitionTime.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.TransitionTime.cs
new file mode 100644
index 0000000000..b93794262c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.TransitionTime.cs
@@ -0,0 +1,155 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ public sealed partial class TimeZoneInfo
+ {
+ [Serializable]
+ public readonly struct TransitionTime : IEquatable<TransitionTime>, ISerializable, IDeserializationCallback
+ {
+ private readonly DateTime _timeOfDay;
+ private readonly byte _month;
+ private readonly byte _week;
+ private readonly byte _day;
+ private readonly DayOfWeek _dayOfWeek;
+ private readonly bool _isFixedDateRule;
+
+ public DateTime TimeOfDay => _timeOfDay;
+
+ public int Month => _month;
+
+ public int Week => _week;
+
+ public int Day => _day;
+
+ public DayOfWeek DayOfWeek => _dayOfWeek;
+
+ public bool IsFixedDateRule => _isFixedDateRule;
+
+ public override bool Equals(object obj) =>
+ obj is TransitionTime && Equals((TransitionTime)obj);
+
+ public static bool operator ==(TransitionTime t1, TransitionTime t2) => t1.Equals(t2);
+
+ public static bool operator !=(TransitionTime t1, TransitionTime t2) => !t1.Equals(t2);
+
+ public bool Equals(TransitionTime other) =>
+ _isFixedDateRule == other._isFixedDateRule &&
+ _timeOfDay == other._timeOfDay &&
+ _month == other._month &&
+ (other._isFixedDateRule ?
+ _day == other._day :
+ _week == other._week && _dayOfWeek == other._dayOfWeek);
+
+ public override int GetHashCode() => (int)_month ^ (int)_week << 8;
+
+ private TransitionTime(DateTime timeOfDay, int month, int week, int day, DayOfWeek dayOfWeek, bool isFixedDateRule)
+ {
+ ValidateTransitionTime(timeOfDay, month, week, day, dayOfWeek);
+
+ _timeOfDay = timeOfDay;
+ _month = (byte)month;
+ _week = (byte)week;
+ _day = (byte)day;
+ _dayOfWeek = dayOfWeek;
+ _isFixedDateRule = isFixedDateRule;
+ }
+
+ public static TransitionTime CreateFixedDateRule(DateTime timeOfDay, int month, int day) =>
+ new TransitionTime(timeOfDay, month, 1, day, DayOfWeek.Sunday, isFixedDateRule: true);
+
+ public static TransitionTime CreateFloatingDateRule(DateTime timeOfDay, int month, int week, DayOfWeek dayOfWeek) =>
+ new TransitionTime(timeOfDay, month, week, 1, dayOfWeek, isFixedDateRule: false);
+
+ /// <summary>
+ /// Helper function that validates a TransitionTime instance.
+ /// </summary>
+ private static void ValidateTransitionTime(DateTime timeOfDay, int month, int week, int day, DayOfWeek dayOfWeek)
+ {
+ if (timeOfDay.Kind != DateTimeKind.Unspecified)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeKindMustBeUnspecified, nameof(timeOfDay));
+ }
+
+ // Month range 1-12
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_MonthParam);
+ }
+
+ // Day range 1-31
+ if (day < 1 || day > 31)
+ {
+ throw new ArgumentOutOfRangeException(nameof(day), SR.ArgumentOutOfRange_DayParam);
+ }
+
+ // Week range 1-5
+ if (week < 1 || week > 5)
+ {
+ throw new ArgumentOutOfRangeException(nameof(week), SR.ArgumentOutOfRange_Week);
+ }
+
+ // DayOfWeek range 0-6
+ if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dayOfWeek), SR.ArgumentOutOfRange_DayOfWeek);
+ }
+
+ timeOfDay.GetDatePart(out int timeOfDayYear, out int timeOfDayMonth, out int timeOfDayDay);
+ if (timeOfDayYear != 1 || timeOfDayMonth != 1 || timeOfDayDay != 1 || (timeOfDay.Ticks % TimeSpan.TicksPerMillisecond != 0))
+ {
+ throw new ArgumentException(SR.Argument_DateTimeHasTicks, nameof(timeOfDay));
+ }
+ }
+
+ void IDeserializationCallback.OnDeserialization(object sender)
+ {
+ // OnDeserialization is called after each instance of this class is deserialized.
+ // This callback method performs TransitionTime validation after being deserialized.
+
+ try
+ {
+ ValidateTransitionTime(_timeOfDay, _month, _week, _day, _dayOfWeek);
+ }
+ catch (ArgumentException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+ }
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ info.AddValue("TimeOfDay", _timeOfDay); // Do not rename (binary serialization)
+ info.AddValue("Month", _month); // Do not rename (binary serialization)
+ info.AddValue("Week", _week); // Do not rename (binary serialization)
+ info.AddValue("Day", _day); // Do not rename (binary serialization)
+ info.AddValue("DayOfWeek", _dayOfWeek); // Do not rename (binary serialization)
+ info.AddValue("IsFixedDateRule", _isFixedDateRule); // Do not rename (binary serialization)
+ }
+
+ private TransitionTime(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ _timeOfDay = (DateTime)info.GetValue("TimeOfDay", typeof(DateTime)); // Do not rename (binary serialization)
+ _month = (byte)info.GetValue("Month", typeof(byte)); // Do not rename (binary serialization)
+ _week = (byte)info.GetValue("Week", typeof(byte)); // Do not rename (binary serialization)
+ _day = (byte)info.GetValue("Day", typeof(byte)); // Do not rename (binary serialization)
+ _dayOfWeek = (DayOfWeek)info.GetValue("DayOfWeek", typeof(DayOfWeek)); // Do not rename (binary serialization)
+ _isFixedDateRule = (bool)info.GetValue("IsFixedDateRule", typeof(bool)); // Do not rename (binary serialization)
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs
new file mode 100644
index 0000000000..920131d898
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs
@@ -0,0 +1,1633 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Security;
+
+using Internal.IO;
+
+namespace System
+{
+ public sealed partial class TimeZoneInfo
+ {
+ private const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo/";
+ private const string ZoneTabFileName = "zone.tab";
+ private const string TimeZoneEnvironmentVariable = "TZ";
+ private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR";
+
+ private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
+ {
+ TZifHead t;
+ DateTime[] dts;
+ byte[] typeOfLocalTime;
+ TZifType[] transitionType;
+ string zoneAbbreviations;
+ bool[] StandardTime;
+ bool[] GmtTime;
+ string futureTransitionsPosixFormat;
+
+ // parse the raw TZif bytes; this method can throw ArgumentException when the data is malformed.
+ TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out StandardTime, out GmtTime, out futureTransitionsPosixFormat);
+
+ _id = id;
+ _displayName = LocalId;
+ _baseUtcOffset = TimeSpan.Zero;
+
+ // find the best matching baseUtcOffset and display strings based on the current utcNow value.
+ // NOTE: read the display strings from the tzfile now in case they can't be loaded later
+ // from the globalization data.
+ DateTime utcNow = DateTime.UtcNow;
+ for (int i = 0; i < dts.Length && dts[i] <= utcNow; i++)
+ {
+ int type = typeOfLocalTime[i];
+ if (!transitionType[type].IsDst)
+ {
+ _baseUtcOffset = transitionType[type].UtcOffset;
+ _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
+ }
+ else
+ {
+ _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
+ }
+ }
+
+ if (dts.Length == 0)
+ {
+ // time zones like Africa/Bujumbura and Etc/GMT* have no transition times but still contain
+ // TZifType entries that may contain a baseUtcOffset and display strings
+ for (int i = 0; i < transitionType.Length; i++)
+ {
+ if (!transitionType[i].IsDst)
+ {
+ _baseUtcOffset = transitionType[i].UtcOffset;
+ _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
+ }
+ else
+ {
+ _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
+ }
+ }
+ }
+ _displayName = _standardDisplayName;
+
+ GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Generic, ref _displayName);
+ GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Standard, ref _standardDisplayName);
+ GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, ref _daylightDisplayName);
+
+ // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
+ // with DateTimeOffset, SQL Server, and the W3C XML Specification
+ if (_baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
+ {
+ _baseUtcOffset = new TimeSpan(_baseUtcOffset.Hours, _baseUtcOffset.Minutes, 0);
+ }
+
+ if (!dstDisabled)
+ {
+ // only create the adjustment rule if DST is enabled
+ TZif_GenerateAdjustmentRules(out _adjustmentRules, _baseUtcOffset, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
+ }
+
+ ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime);
+ }
+
+ private void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, ref string displayName)
+ {
+ if (GlobalizationMode.Invariant)
+ {
+ displayName = _standardDisplayName;
+ return;
+ }
+
+ string timeZoneDisplayName;
+ bool result = Interop.CallStringMethod(
+ (locale, id, type, stringBuilder) => Interop.Globalization.GetTimeZoneDisplayName(
+ locale,
+ id,
+ type,
+ stringBuilder,
+ stringBuilder.Capacity),
+ CultureInfo.CurrentUICulture.Name,
+ _id,
+ nameType,
+ out timeZoneDisplayName);
+
+ // If there is an unknown error, don't set the displayName field.
+ // It will be set to the abbreviation that was read out of the tzfile.
+ if (result)
+ {
+ displayName = timeZoneDisplayName;
+ }
+ }
+
+ /// <summary>
+ /// Returns a cloned array of AdjustmentRule objects
+ /// </summary>
+ public AdjustmentRule[] GetAdjustmentRules()
+ {
+ if (_adjustmentRules == null)
+ {
+ return Array.Empty<AdjustmentRule>();
+ }
+
+ // The rules we use in Unix care mostly about the start and end dates but don't fill the transition start and end info.
+ // as the rules now is public, we should fill it properly so the caller doesn't have to know how we use it internally
+ // and can use it as it is used in Windows
+
+ AdjustmentRule[] rules = new AdjustmentRule[_adjustmentRules.Length];
+
+ for (int i = 0; i < _adjustmentRules.Length; i++)
+ {
+ var rule = _adjustmentRules[i];
+ var start = rule.DateStart.Kind == DateTimeKind.Utc ?
+ // At the daylight start we didn't start the daylight saving yet then we convert to Local time
+ // by adding the _baseUtcOffset to the UTC time
+ new DateTime(rule.DateStart.Ticks + _baseUtcOffset.Ticks, DateTimeKind.Unspecified) :
+ rule.DateStart;
+ var end = rule.DateEnd.Kind == DateTimeKind.Utc ?
+ // At the daylight saving end, the UTC time is mapped to local time which is already shifted by the daylight delta
+ // we calculate the local time by adding _baseUtcOffset + DaylightDelta to the UTC time
+ new DateTime(rule.DateEnd.Ticks + _baseUtcOffset.Ticks + rule.DaylightDelta.Ticks, DateTimeKind.Unspecified) :
+ rule.DateEnd;
+
+ var startTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, start.Hour, start.Minute, start.Second), start.Month, start.Day);
+ var endTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, end.Hour, end.Minute, end.Second), end.Month, end.Day);
+
+ rules[i] = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(start.Date, end.Date, rule.DaylightDelta, startTransition, endTransition);
+ }
+
+ return rules;
+ }
+
+ private static void PopulateAllSystemTimeZones(CachedData cachedData)
+ {
+ Debug.Assert(Monitor.IsEntered(cachedData));
+
+ string timeZoneDirectory = GetTimeZoneDirectory();
+ foreach (string timeZoneId in GetTimeZoneIds(timeZoneDirectory))
+ {
+ TimeZoneInfo value;
+ Exception ex;
+ TryGetTimeZone(timeZoneId, false, out value, out ex, cachedData, alwaysFallbackToLocalMachine: true); // populate the cache
+ }
+ }
+
+ /// <summary>
+ /// Helper function for retrieving the local system time zone.
+ /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException.
+ /// Assumes cachedData lock is taken.
+ /// </summary>
+ /// <returns>A new TimeZoneInfo instance.</returns>
+ private static TimeZoneInfo GetLocalTimeZone(CachedData cachedData)
+ {
+ Debug.Assert(Monitor.IsEntered(cachedData));
+
+ // Without Registry support, create the TimeZoneInfo from a TZ file
+ return GetLocalTimeZoneFromTzFile();
+ }
+
+ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out TimeZoneInfo value, out Exception e)
+ {
+ value = null;
+ e = null;
+
+ string timeZoneDirectory = GetTimeZoneDirectory();
+ string timeZoneFilePath = Path.Combine(timeZoneDirectory, id);
+ byte[] rawData;
+ try
+ {
+ rawData = File.ReadAllBytes(timeZoneFilePath);
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ e = ex;
+ return TimeZoneInfoResult.SecurityException;
+ }
+ catch (FileNotFoundException ex)
+ {
+ e = ex;
+ return TimeZoneInfoResult.TimeZoneNotFoundException;
+ }
+ catch (DirectoryNotFoundException ex)
+ {
+ e = ex;
+ return TimeZoneInfoResult.TimeZoneNotFoundException;
+ }
+ catch (IOException ex)
+ {
+ e = new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidFileData, id, timeZoneFilePath), ex);
+ return TimeZoneInfoResult.InvalidTimeZoneException;
+ }
+
+ value = GetTimeZoneFromTzData(rawData, id);
+
+ if (value == null)
+ {
+ e = new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidFileData, id, timeZoneFilePath));
+ return TimeZoneInfoResult.InvalidTimeZoneException;
+ }
+
+ return TimeZoneInfoResult.Success;
+ }
+
+ /// <summary>
+ /// Returns a collection of TimeZone Id values from the zone.tab file in the timeZoneDirectory.
+ /// </summary>
+ /// <remarks>
+ /// Lines that start with # are comments and are skipped.
+ /// </remarks>
+ private static List<string> GetTimeZoneIds(string timeZoneDirectory)
+ {
+ List<string> timeZoneIds = new List<string>();
+
+ try
+ {
+ using (StreamReader sr = new StreamReader(Path.Combine(timeZoneDirectory, ZoneTabFileName), Encoding.UTF8))
+ {
+ string zoneTabFileLine;
+ while ((zoneTabFileLine = sr.ReadLine()) != null)
+ {
+ if (!string.IsNullOrEmpty(zoneTabFileLine) && zoneTabFileLine[0] != '#')
+ {
+ // the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
+
+ int firstTabIndex = zoneTabFileLine.IndexOf('\t');
+ if (firstTabIndex != -1)
+ {
+ int secondTabIndex = zoneTabFileLine.IndexOf('\t', firstTabIndex + 1);
+ if (secondTabIndex != -1)
+ {
+ string timeZoneId;
+ int startIndex = secondTabIndex + 1;
+ int thirdTabIndex = zoneTabFileLine.IndexOf('\t', startIndex);
+ if (thirdTabIndex != -1)
+ {
+ int length = thirdTabIndex - startIndex;
+ timeZoneId = zoneTabFileLine.Substring(startIndex, length);
+ }
+ else
+ {
+ timeZoneId = zoneTabFileLine.Substring(startIndex);
+ }
+
+ if (!string.IsNullOrEmpty(timeZoneId))
+ {
+ timeZoneIds.Add(timeZoneId);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (IOException) { }
+ catch (UnauthorizedAccessException) { }
+
+ return timeZoneIds;
+ }
+
+ /// <summary>
+ /// Gets the tzfile raw data for the current 'local' time zone using the following rules.
+ /// 1. Read the TZ environment variable. If it is set, use it.
+ /// 2. Look for the data in /etc/localtime.
+ /// 3. Look for the data in GetTimeZoneDirectory()/localtime.
+ /// 4. Use UTC if all else fails.
+ /// </summary>
+ private static bool TryGetLocalTzFile(out byte[] rawData, out string id)
+ {
+ rawData = null;
+ id = null;
+ string tzVariable = GetTzEnvironmentVariable();
+
+ // If the env var is null, use the localtime file
+ if (tzVariable == null)
+ {
+ return
+ TryLoadTzFile("/etc/localtime", ref rawData, ref id) ||
+ TryLoadTzFile(Path.Combine(GetTimeZoneDirectory(), "localtime"), ref rawData, ref id);
+ }
+
+ // If it's empty, use UTC (TryGetLocalTzFile() should return false).
+ if (tzVariable.Length == 0)
+ {
+ return false;
+ }
+
+ // Otherwise, use the path from the env var. If it's not absolute, make it relative
+ // to the system timezone directory
+ string tzFilePath;
+ if (tzVariable[0] != '/')
+ {
+ id = tzVariable;
+ tzFilePath = Path.Combine(GetTimeZoneDirectory(), tzVariable);
+ }
+ else
+ {
+ tzFilePath = tzVariable;
+ }
+ return TryLoadTzFile(tzFilePath, ref rawData, ref id);
+ }
+
+ private static string GetTzEnvironmentVariable()
+ {
+ string result = Environment.GetEnvironmentVariable(TimeZoneEnvironmentVariable);
+ if (!string.IsNullOrEmpty(result))
+ {
+ if (result[0] == ':')
+ {
+ // strip off the ':' prefix
+ result = result.Substring(1);
+ }
+ }
+
+ return result;
+ }
+
+ private static bool TryLoadTzFile(string tzFilePath, ref byte[] rawData, ref string id)
+ {
+ if (File.Exists(tzFilePath))
+ {
+ try
+ {
+ rawData = File.ReadAllBytes(tzFilePath);
+ if (string.IsNullOrEmpty(id))
+ {
+ id = FindTimeZoneIdUsingReadLink(tzFilePath);
+
+ if (string.IsNullOrEmpty(id))
+ {
+ id = FindTimeZoneId(rawData);
+ }
+ }
+ return true;
+ }
+ catch (IOException) { }
+ catch (SecurityException) { }
+ catch (UnauthorizedAccessException) { }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Finds the time zone id by using 'readlink' on the path to see if tzFilePath is
+ /// a symlink to a file.
+ /// </summary>
+ private static string FindTimeZoneIdUsingReadLink(string tzFilePath)
+ {
+ string id = null;
+
+ string symlinkPath = Interop.Sys.ReadLink(tzFilePath);
+ if (symlinkPath != null)
+ {
+ // symlinkPath can be relative path, use Path to get the full absolute path.
+ symlinkPath = Path.GetFullPath(symlinkPath, Path.GetDirectoryName(tzFilePath));
+
+ string timeZoneDirectory = GetTimeZoneDirectory();
+ if (symlinkPath.StartsWith(timeZoneDirectory, StringComparison.Ordinal))
+ {
+ id = symlinkPath.Substring(timeZoneDirectory.Length);
+ }
+ }
+
+ return id;
+ }
+
+ /// <summary>
+ /// Enumerate files
+ /// </summary>
+ private static IEnumerable<string> EnumerateFilesRecursively(string path)
+ {
+ List<string> toExplore = null; // List used as a stack
+
+ string currentPath = path;
+ for(;;)
+ {
+ using (Microsoft.Win32.SafeHandles.SafeDirectoryHandle dirHandle = Interop.Sys.OpenDir(currentPath))
+ {
+ if (dirHandle.IsInvalid)
+ {
+ throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), currentPath, isDirectory: true);
+ }
+
+ // Read each entry from the enumerator
+ Interop.Sys.DirectoryEntry dirent;
+ while (Interop.Sys.ReadDir(dirHandle, out dirent) == 0)
+ {
+ if (dirent.InodeName == "." || dirent.InodeName == "..")
+ continue;
+
+ string fullPath = Path.Combine(currentPath, dirent.InodeName);
+
+ // Get from the dir entry whether the entry is a file or directory.
+ // We classify everything as a file unless we know it to be a directory.
+ bool isDir;
+ if (dirent.InodeType == Interop.Sys.NodeType.DT_DIR)
+ {
+ // We know it's a directory.
+ isDir = true;
+ }
+ else if (dirent.InodeType == Interop.Sys.NodeType.DT_LNK || dirent.InodeType == Interop.Sys.NodeType.DT_UNKNOWN)
+ {
+ // It's a symlink or unknown: stat to it to see if we can resolve it to a directory.
+ // If we can't (e.g. symlink to a file, broken symlink, etc.), we'll just treat it as a file.
+
+ Interop.Sys.FileStatus fileinfo;
+ if (Interop.Sys.Stat(fullPath, out fileinfo) >= 0)
+ {
+ isDir = (fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR;
+ }
+ else
+ {
+ isDir = false;
+ }
+ }
+ else
+ {
+ // Otherwise, treat it as a file. This includes regular files, FIFOs, etc.
+ isDir = false;
+ }
+
+ // Yield the result if the user has asked for it. In the case of directories,
+ // always explore it by pushing it onto the stack, regardless of whether
+ // we're returning directories.
+ if (isDir)
+ {
+ if (toExplore == null)
+ {
+ toExplore = new List<string>();
+ }
+ toExplore.Add(fullPath);
+ }
+ else
+ {
+ yield return fullPath;
+ }
+ }
+ }
+
+ if (toExplore == null || toExplore.Count == 0)
+ break;
+
+ currentPath = toExplore[toExplore.Count - 1];
+ toExplore.RemoveAt(toExplore.Count - 1);
+ }
+ }
+
+ /// <summary>
+ /// Find the time zone id by searching all the tzfiles for the one that matches rawData
+ /// and return its file name.
+ /// </summary>
+ private static string FindTimeZoneId(byte[] rawData)
+ {
+ // default to "Local" if we can't find the right tzfile
+ string id = LocalId;
+ string timeZoneDirectory = GetTimeZoneDirectory();
+ string localtimeFilePath = Path.Combine(timeZoneDirectory, "localtime");
+ string posixrulesFilePath = Path.Combine(timeZoneDirectory, "posixrules");
+ byte[] buffer = new byte[rawData.Length];
+
+ try
+ {
+ foreach (string filePath in EnumerateFilesRecursively(timeZoneDirectory))
+ {
+ // skip the localtime and posixrules file, since they won't give us the correct id
+ if (!string.Equals(filePath, localtimeFilePath, StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(filePath, posixrulesFilePath, StringComparison.OrdinalIgnoreCase))
+ {
+ if (CompareTimeZoneFile(filePath, buffer, rawData))
+ {
+ // if all bytes are the same, this must be the right tz file
+ id = filePath;
+
+ // strip off the root time zone directory
+ if (id.StartsWith(timeZoneDirectory, StringComparison.Ordinal))
+ {
+ id = id.Substring(timeZoneDirectory.Length);
+ }
+ break;
+ }
+ }
+ }
+ }
+ catch (IOException) { }
+ catch (SecurityException) { }
+ catch (UnauthorizedAccessException) { }
+
+ return id;
+ }
+
+ private static bool CompareTimeZoneFile(string filePath, byte[] buffer, byte[] rawData)
+ {
+ try
+ {
+ // bufferSize == 1 used to avoid unnecessary buffer in FileStream
+ using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1))
+ {
+ if (stream.Length == rawData.Length)
+ {
+ int index = 0;
+ int count = rawData.Length;
+
+ while (count > 0)
+ {
+ int n = stream.Read(buffer, index, count);
+ if (n == 0)
+ throw Error.GetEndOfFile();
+
+ int end = index + n;
+ for (; index < end; index++)
+ {
+ if (buffer[index] != rawData[index])
+ {
+ return false;
+ }
+ }
+
+ count -= n;
+ }
+
+ return true;
+ }
+ }
+ }
+ catch (IOException) { }
+ catch (SecurityException) { }
+ catch (UnauthorizedAccessException) { }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Helper function used by 'GetLocalTimeZone()' - this function wraps the call
+ /// for loading time zone data from computers without Registry support.
+ ///
+ /// The TryGetLocalTzFile() call returns a Byte[] containing the compiled tzfile.
+ /// </summary>
+ private static TimeZoneInfo GetLocalTimeZoneFromTzFile()
+ {
+ byte[] rawData;
+ string id;
+ if (TryGetLocalTzFile(out rawData, out id))
+ {
+ TimeZoneInfo result = GetTimeZoneFromTzData(rawData, id);
+ if (result != null)
+ {
+ return result;
+ }
+ }
+
+ // if we can't find a local time zone, return UTC
+ return Utc;
+ }
+
+ private static TimeZoneInfo GetTimeZoneFromTzData(byte[] rawData, string id)
+ {
+ if (rawData != null)
+ {
+ try
+ {
+ return new TimeZoneInfo(rawData, id, dstDisabled: false); // create a TimeZoneInfo instance from the TZif data w/ DST support
+ }
+ catch (ArgumentException) { }
+ catch (InvalidTimeZoneException) { }
+ try
+ {
+ return new TimeZoneInfo(rawData, id, dstDisabled: true); // create a TimeZoneInfo instance from the TZif data w/o DST support
+ }
+ catch (ArgumentException) { }
+ catch (InvalidTimeZoneException) { }
+ }
+
+ return null;
+ }
+
+ private static string GetTimeZoneDirectory()
+ {
+ string tzDirectory = Environment.GetEnvironmentVariable(TimeZoneDirectoryEnvironmentVariable);
+
+ if (tzDirectory == null)
+ {
+ tzDirectory = DefaultTimeZoneDirectory;
+ }
+ else if (!tzDirectory.EndsWith(Path.DirectorySeparatorChar))
+ {
+ tzDirectory += Path.DirectorySeparatorChar;
+ }
+
+ return tzDirectory;
+ }
+
+ /// <summary>
+ /// Helper function for retrieving a TimeZoneInfo object by <time_zone_name>.
+ /// This function wraps the logic necessary to keep the private
+ /// SystemTimeZones cache in working order
+ ///
+ /// This function will either return a valid TimeZoneInfo instance or
+ /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'.
+ /// </summary>
+ public static TimeZoneInfo FindSystemTimeZoneById(string id)
+ {
+ // Special case for Utc as it will not exist in the dictionary with the rest
+ // of the system time zones. There is no need to do this check for Local.Id
+ // since Local is a real time zone that exists in the dictionary cache
+ if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase))
+ {
+ return Utc;
+ }
+
+ if (id == null)
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+ else if (id.Length == 0 || id.Contains('\0'))
+ {
+ throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id));
+ }
+
+ TimeZoneInfo value;
+ Exception e;
+
+ TimeZoneInfoResult result;
+
+ CachedData cachedData = s_cachedData;
+
+ lock (cachedData)
+ {
+ result = TryGetTimeZone(id, false, out value, out e, cachedData, alwaysFallbackToLocalMachine: true);
+ }
+
+ if (result == TimeZoneInfoResult.Success)
+ {
+ return value;
+ }
+ else if (result == TimeZoneInfoResult.InvalidTimeZoneException)
+ {
+ Debug.Assert(e is InvalidTimeZoneException,
+ "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException");
+ throw e;
+ }
+ else if (result == TimeZoneInfoResult.SecurityException)
+ {
+ throw new SecurityException(SR.Format(SR.Security_CannotReadFileData, id), e);
+ }
+ else
+ {
+ throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e);
+ }
+ }
+
+ // DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone
+ internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst)
+ {
+ bool isDaylightSavings;
+ // Use the standard code path for Unix since there isn't a faster way of handling current-year-only time zones
+ return GetUtcOffsetFromUtc(time, Local, out isDaylightSavings, out isAmbiguousLocalDst);
+ }
+
+ // TZFILE(5) BSD File Formats Manual TZFILE(5)
+ //
+ // NAME
+ // tzfile -- timezone information
+ //
+ // SYNOPSIS
+ // #include "/usr/src/lib/libc/stdtime/tzfile.h"
+ //
+ // DESCRIPTION
+ // The time zone information files used by tzset(3) begin with the magic
+ // characters ``TZif'' to identify them as time zone information files, fol-
+ // lowed by sixteen bytes reserved for future use, followed by four four-
+ // byte values written in a ``standard'' byte order (the high-order byte of
+ // the value is written first). These values are, in order:
+ //
+ // tzh_ttisgmtcnt The number of UTC/local indicators stored in the file.
+ // tzh_ttisstdcnt The number of standard/wall indicators stored in the
+ // file.
+ // tzh_leapcnt The number of leap seconds for which data is stored in
+ // the file.
+ // tzh_timecnt The number of ``transition times'' for which data is
+ // stored in the file.
+ // tzh_typecnt The number of ``local time types'' for which data is
+ // stored in the file (must not be zero).
+ // tzh_charcnt The number of characters of ``time zone abbreviation
+ // strings'' stored in the file.
+ //
+ // The above header is followed by tzh_timecnt four-byte values of type
+ // long, sorted in ascending order. These values are written in ``stan-
+ // dard'' byte order. Each is used as a transition time (as returned by
+ // time(3)) at which the rules for computing local time change. Next come
+ // tzh_timecnt one-byte values of type unsigned char; each one tells which
+ // of the different types of ``local time'' types described in the file is
+ // associated with the same-indexed transition time. These values serve as
+ // indices into an array of ttinfo structures that appears next in the file;
+ // these structures are defined as follows:
+ //
+ // struct ttinfo {
+ // long tt_gmtoff;
+ // int tt_isdst;
+ // unsigned int tt_abbrind;
+ // };
+ //
+ // Each structure is written as a four-byte value for tt_gmtoff of type
+ // long, in a standard byte order, followed by a one-byte value for tt_isdst
+ // and a one-byte value for tt_abbrind. In each structure, tt_gmtoff gives
+ // the number of seconds to be added to UTC, tt_isdst tells whether tm_isdst
+ // should be set by localtime(3) and tt_abbrind serves as an index into the
+ // array of time zone abbreviation characters that follow the ttinfo struc-
+ // ture(s) in the file.
+ //
+ // Then there are tzh_leapcnt pairs of four-byte values, written in standard
+ // byte order; the first value of each pair gives the time (as returned by
+ // time(3)) at which a leap second occurs; the second gives the total number
+ // of leap seconds to be applied after the given time. The pairs of values
+ // are sorted in ascending order by time.b
+ //
+ // Then there are tzh_ttisstdcnt standard/wall indicators, each stored as a
+ // one-byte value; they tell whether the transition times associated with
+ // local time types were specified as standard time or wall clock time, and
+ // are used when a time zone file is used in handling POSIX-style time zone
+ // environment variables.
+ //
+ // Finally there are tzh_ttisgmtcnt UTC/local indicators, each stored as a
+ // one-byte value; they tell whether the transition times associated with
+ // local time types were specified as UTC or local time, and are used when a
+ // time zone file is used in handling POSIX-style time zone environment
+ // variables.
+ //
+ // localtime uses the first standard-time ttinfo structure in the file (or
+ // simply the first ttinfo structure in the absence of a standard-time
+ // structure) if either tzh_timecnt is zero or the time argument is less
+ // than the first transition time recorded in the file.
+ //
+ // SEE ALSO
+ // ctime(3), time2posix(3), zic(8)
+ //
+ // BSD September 13, 1994 BSD
+ //
+ //
+ //
+ // TIME(3) BSD Library Functions Manual TIME(3)
+ //
+ // NAME
+ // time -- get time of day
+ //
+ // LIBRARY
+ // Standard C Library (libc, -lc)
+ //
+ // SYNOPSIS
+ // #include <time.h>
+ //
+ // time_t
+ // time(time_t *tloc);
+ //
+ // DESCRIPTION
+ // The time() function returns the value of time in seconds since 0 hours, 0
+ // minutes, 0 seconds, January 1, 1970, Coordinated Universal Time, without
+ // including leap seconds. If an error occurs, time() returns the value
+ // (time_t)-1.
+ //
+ // The return value is also stored in *tloc, provided that tloc is non-null.
+ //
+ // ERRORS
+ // The time() function may fail for any of the reasons described in
+ // gettimeofday(2).
+ //
+ // SEE ALSO
+ // gettimeofday(2), ctime(3)
+ //
+ // STANDARDS
+ // The time function conforms to IEEE Std 1003.1-2001 (``POSIX.1'').
+ //
+ // BUGS
+ // Neither ISO/IEC 9899:1999 (``ISO C99'') nor IEEE Std 1003.1-2001
+ // (``POSIX.1'') requires time() to set errno on failure; thus, it is impos-
+ // sible for an application to distinguish the valid time value -1 (repre-
+ // senting the last UTC second of 1969) from the error return value.
+ //
+ // Systems conforming to earlier versions of the C and POSIX standards
+ // (including older versions of FreeBSD) did not set *tloc in the error
+ // case.
+ //
+ // HISTORY
+ // A time() function appeared in Version 6 AT&T UNIX.
+ //
+ // BSD July 18, 2003 BSD
+ //
+ //
+ private static void TZif_GenerateAdjustmentRules(out AdjustmentRule[] rules, TimeSpan baseUtcOffset, DateTime[] dts, byte[] typeOfLocalTime,
+ TZifType[] transitionType, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
+ {
+ rules = null;
+
+ if (dts.Length > 0)
+ {
+ int index = 0;
+ List<AdjustmentRule> rulesList = new List<AdjustmentRule>();
+
+ while (index <= dts.Length)
+ {
+ TZif_GenerateAdjustmentRule(ref index, baseUtcOffset, rulesList, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
+ }
+
+ rules = rulesList.ToArray();
+ if (rules != null && rules.Length == 0)
+ {
+ rules = null;
+ }
+ }
+ }
+
+ private static void TZif_GenerateAdjustmentRule(ref int index, TimeSpan timeZoneBaseUtcOffset, List<AdjustmentRule> rulesList, DateTime[] dts,
+ byte[] typeOfLocalTime, TZifType[] transitionTypes, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
+ {
+ // To generate AdjustmentRules, use the following approach:
+ // The first AdjustmentRule will go from DateTime.MinValue to the first transition time greater than DateTime.MinValue.
+ // Each middle AdjustmentRule wil go from dts[index-1] to dts[index].
+ // The last AdjustmentRule will go from dts[dts.Length-1] to Datetime.MaxValue.
+
+ // 0. Skip any DateTime.MinValue transition times. In newer versions of the tzfile, there
+ // is a "big bang" transition time, which is before the year 0001. Since any times before year 0001
+ // cannot be represented by DateTime, there is no reason to make AdjustmentRules for these unrepresentable time periods.
+ // 1. If there are no DateTime.MinValue times, the first AdjustmentRule goes from DateTime.MinValue
+ // to the first transition and uses the first standard transitionType (or the first transitionType if none of them are standard)
+ // 2. Create an AdjustmentRule for each transition, i.e. from dts[index - 1] to dts[index].
+ // This rule uses the transitionType[index - 1] and the whole AdjustmentRule only describes a single offset - either
+ // all daylight savings, or all stanard time.
+ // 3. After all the transitions are filled out, the last AdjustmentRule is created from either:
+ // a. a POSIX-style timezone description ("futureTransitionsPosixFormat"), if there is one or
+ // b. continue the last transition offset until DateTime.Max
+
+ while (index < dts.Length && dts[index] == DateTime.MinValue)
+ {
+ index++;
+ }
+
+ if (index == 0)
+ {
+ TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes);
+ DateTime endTransitionDate = dts[index];
+
+ TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
+ TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+ TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
+
+ AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+ DateTime.MinValue,
+ endTransitionDate.AddTicks(-1),
+ daylightDelta,
+ default(TransitionTime),
+ default(TransitionTime),
+ baseUtcDelta,
+ noDaylightTransitions: true);
+ rulesList.Add(r);
+ }
+ else if (index < dts.Length)
+ {
+ DateTime startTransitionDate = dts[index - 1];
+ TZifType startTransitionType = transitionTypes[typeOfLocalTime[index - 1]];
+
+ DateTime endTransitionDate = dts[index];
+
+ TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(startTransitionType.UtcOffset, timeZoneBaseUtcOffset);
+ TimeSpan daylightDelta = startTransitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+ TimeSpan baseUtcDelta = startTransitionType.IsDst ? TimeSpan.Zero : transitionOffset;
+
+ TransitionTime dstStart;
+ if (startTransitionType.IsDst)
+ {
+ // the TransitionTime fields are not used when AdjustmentRule.NoDaylightTransitions == true.
+ // However, there are some cases in the past where DST = true, and the daylight savings offset
+ // now equals what the current BaseUtcOffset is. In that case, the AdjustmentRule.DaylightOffset
+ // is going to be TimeSpan.Zero. But we still need to return 'true' from AdjustmentRule.HasDaylightSaving.
+ // To ensure we always return true from HasDaylightSaving, make a "special" dstStart that will make the logic
+ // in HasDaylightSaving return true.
+ dstStart = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1);
+ }
+ else
+ {
+ dstStart = default(TransitionTime);
+ }
+
+ AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+ startTransitionDate,
+ endTransitionDate.AddTicks(-1),
+ daylightDelta,
+ dstStart,
+ default(TransitionTime),
+ baseUtcDelta,
+ noDaylightTransitions: true);
+ rulesList.Add(r);
+ }
+ else
+ {
+ // create the AdjustmentRule that will be used for all DateTimes after the last transition
+
+ // NOTE: index == dts.Length
+ DateTime startTransitionDate = dts[index - 1];
+
+ if (!string.IsNullOrEmpty(futureTransitionsPosixFormat))
+ {
+ AdjustmentRule r = TZif_CreateAdjustmentRuleForPosixFormat(futureTransitionsPosixFormat, startTransitionDate, timeZoneBaseUtcOffset);
+ if (r != null)
+ {
+ rulesList.Add(r);
+ }
+ }
+ else
+ {
+ // just use the last transition as the rule which will be used until the end of time
+
+ TZifType transitionType = transitionTypes[typeOfLocalTime[index - 1]];
+ TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
+ TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+ TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
+
+ AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+ startTransitionDate,
+ DateTime.MaxValue,
+ daylightDelta,
+ default(TransitionTime),
+ default(TransitionTime),
+ baseUtcDelta,
+ noDaylightTransitions: true);
+ rulesList.Add(r);
+ }
+ }
+
+ index++;
+ }
+
+ private static TimeSpan TZif_CalculateTransitionOffsetFromBase(TimeSpan transitionOffset, TimeSpan timeZoneBaseUtcOffset)
+ {
+ TimeSpan result = transitionOffset - timeZoneBaseUtcOffset;
+
+ // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
+ // with DateTimeOffset, SQL Server, and the W3C XML Specification
+ if (result.Ticks % TimeSpan.TicksPerMinute != 0)
+ {
+ result = new TimeSpan(result.Hours, result.Minutes, 0);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Gets the first standard-time transition type, or simply the first transition type
+ /// if there are no standard transition types.
+ /// </summary>>
+ /// <remarks>
+ /// from 'man tzfile':
+ /// localtime(3) uses the first standard-time ttinfo structure in the file
+ /// (or simply the first ttinfo structure in the absence of a standard-time
+ /// structure) if either tzh_timecnt is zero or the time argument is less
+ /// than the first transition time recorded in the file.
+ /// </remarks>
+ private static TZifType TZif_GetEarlyDateTransitionType(TZifType[] transitionTypes)
+ {
+ foreach (TZifType transitionType in transitionTypes)
+ {
+ if (!transitionType.IsDst)
+ {
+ return transitionType;
+ }
+ }
+
+ if (transitionTypes.Length > 0)
+ {
+ return transitionTypes[0];
+ }
+
+ throw new InvalidTimeZoneException(SR.InvalidTimeZone_NoTTInfoStructures);
+ }
+
+ /// <summary>
+ /// Creates an AdjustmentRule given the POSIX TZ environment variable string.
+ /// </summary>
+ /// <remarks>
+ /// See http://man7.org/linux/man-pages/man3/tzset.3.html for the format and semantics of this POSX string.
+ /// </remarks>
+ private static AdjustmentRule TZif_CreateAdjustmentRuleForPosixFormat(string posixFormat, DateTime startTransitionDate, TimeSpan timeZoneBaseUtcOffset)
+ {
+ string standardName;
+ string standardOffset;
+ string daylightSavingsName;
+ string daylightSavingsOffset;
+ string start;
+ string startTime;
+ string end;
+ string endTime;
+
+ if (TZif_ParsePosixFormat(posixFormat, out standardName, out standardOffset, out daylightSavingsName,
+ out daylightSavingsOffset, out start, out startTime, out end, out endTime))
+ {
+ // a valid posixFormat has at least standardName and standardOffset
+
+ TimeSpan? parsedBaseOffset = TZif_ParseOffsetString(standardOffset);
+ if (parsedBaseOffset.HasValue)
+ {
+ TimeSpan baseOffset = parsedBaseOffset.Value.Negate(); // offsets are backwards in POSIX notation
+ baseOffset = TZif_CalculateTransitionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset);
+
+ // having a daylightSavingsName means there is a DST rule
+ if (!string.IsNullOrEmpty(daylightSavingsName))
+ {
+ TimeSpan? parsedDaylightSavings = TZif_ParseOffsetString(daylightSavingsOffset);
+ TimeSpan daylightSavingsTimeSpan;
+ if (!parsedDaylightSavings.HasValue)
+ {
+ // default DST to 1 hour if it isn't specified
+ daylightSavingsTimeSpan = new TimeSpan(1, 0, 0);
+ }
+ else
+ {
+ daylightSavingsTimeSpan = parsedDaylightSavings.Value.Negate(); // offsets are backwards in POSIX notation
+ daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, timeZoneBaseUtcOffset);
+ daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, baseOffset);
+ }
+
+ TransitionTime dstStart = TZif_CreateTransitionTimeFromPosixRule(start, startTime);
+ TransitionTime dstEnd = TZif_CreateTransitionTimeFromPosixRule(end, endTime);
+
+ return AdjustmentRule.CreateAdjustmentRule(
+ startTransitionDate,
+ DateTime.MaxValue,
+ daylightSavingsTimeSpan,
+ dstStart,
+ dstEnd,
+ baseOffset,
+ noDaylightTransitions: false);
+ }
+ else
+ {
+ // if there is no daylightSavingsName, the whole AdjustmentRule should be with no transitions - just the baseOffset
+ return AdjustmentRule.CreateAdjustmentRule(
+ startTransitionDate,
+ DateTime.MaxValue,
+ TimeSpan.Zero,
+ default(TransitionTime),
+ default(TransitionTime),
+ baseOffset,
+ noDaylightTransitions: true);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static TimeSpan? TZif_ParseOffsetString(string offset)
+ {
+ TimeSpan? result = null;
+
+ if (!string.IsNullOrEmpty(offset))
+ {
+ bool negative = offset[0] == '-';
+ if (negative || offset[0] == '+')
+ {
+ offset = offset.Substring(1);
+ }
+
+ // Try parsing just hours first.
+ // Note, TimeSpan.TryParseExact "%h" can't be used here because some time zones using values
+ // like "26" or "144" and TimeSpan parsing would turn that into 26 or 144 *days* instead of hours.
+ int hours;
+ if (int.TryParse(offset, out hours))
+ {
+ result = new TimeSpan(hours, 0, 0);
+ }
+ else
+ {
+ TimeSpan parsedTimeSpan;
+ if (TimeSpan.TryParseExact(offset, "g", CultureInfo.InvariantCulture, out parsedTimeSpan))
+ {
+ result = parsedTimeSpan;
+ }
+ }
+
+ if (result.HasValue && negative)
+ {
+ result = result.Value.Negate();
+ }
+ }
+
+ return result;
+ }
+
+ private static DateTime ParseTimeOfDay(string time)
+ {
+ DateTime timeOfDay;
+ TimeSpan? timeOffset = TZif_ParseOffsetString(time);
+ if (timeOffset.HasValue)
+ {
+ // This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed.
+ // Some time zones use time values like, "26", "144", or "-2".
+ // This allows the week to sometimes be week 4 and sometimes week 5 in the month.
+ // For now, strip off any 'days' in the offset, and just get the time of day correct
+ timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds);
+ if (timeOffset.Value < TimeSpan.Zero)
+ {
+ timeOfDay = new DateTime(1, 1, 2, 0, 0, 0);
+ }
+ else
+ {
+ timeOfDay = new DateTime(1, 1, 1, 0, 0, 0);
+ }
+
+ timeOfDay += timeOffset.Value;
+ }
+ else
+ {
+ // default to 2AM.
+ timeOfDay = new DateTime(1, 1, 1, 2, 0, 0);
+ }
+
+ return timeOfDay;
+ }
+
+ private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(string date, string time)
+ {
+ if (string.IsNullOrEmpty(date))
+ {
+ return default(TransitionTime);
+ }
+
+ if (date[0] == 'M')
+ {
+ // Mm.w.d
+ // This specifies day d of week w of month m. The day d must be between 0(Sunday) and 6.The week w must be between 1 and 5;
+ // week 1 is the first week in which day d occurs, and week 5 specifies the last d day in the month. The month m should be between 1 and 12.
+
+ int month;
+ int week;
+ DayOfWeek day;
+ if (!TZif_ParseMDateRule(date, out month, out week, out day))
+ {
+ throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_UnparseablePosixMDateString, date));
+ }
+
+ return TransitionTime.CreateFloatingDateRule(ParseTimeOfDay(time), month, week, day);
+ }
+ else
+ {
+ if (date[0] != 'J')
+ {
+ // should be n Julian day format which we don't support.
+ //
+ // This specifies the Julian day, with n between 0 and 365. February 29 is counted in leap years.
+ //
+ // n would be a relative number from the begining of the year. which should handle if the
+ // the year is a leap year or not.
+ //
+ // In leap year, n would be counted as:
+ //
+ // 0 30 31 59 60 90 335 365
+ // |-------Jan--------|-------Feb--------|-------Mar--------|....|-------Dec--------|
+ //
+ // while in non leap year we'll have
+ //
+ // 0 30 31 58 59 89 334 364
+ // |-------Jan--------|-------Feb--------|-------Mar--------|....|-------Dec--------|
+ //
+ //
+ // For example if n is specified as 60, this means in leap year the rule will start at Mar 1,
+ // while in non leap year the rule will start at Mar 2.
+ //
+ // If we need to support n format, we'll have to have a floating adjustment rule support this case.
+
+ throw new InvalidTimeZoneException(SR.InvalidTimeZone_NJulianDayNotSupported);
+ }
+
+ // Julian day
+ TZif_ParseJulianDay(date, out int month, out int day);
+ return TransitionTime.CreateFixedDateRule(ParseTimeOfDay(time), month, day);
+ }
+ }
+
+ /// <summary>
+ /// Parses a string like Jn or n into month and day values.
+ /// </summary>
+ /// <returns>
+ /// true if the parsing succeeded; otherwise, false.
+ /// </returns>
+ private static void TZif_ParseJulianDay(string date, out int month, out int day)
+ {
+ // Jn
+ // This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years.
+ Debug.Assert(date[0] == 'J');
+ Debug.Assert(!String.IsNullOrEmpty(date));
+ month = day = 0;
+
+ int index = 1;
+
+ if (index >= date.Length || ((uint)(date[index] - '0') > '9'-'0'))
+ {
+ throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay);
+ }
+
+ int julianDay = 0;
+
+ do
+ {
+ julianDay = julianDay * 10 + (int) (date[index] - '0');
+ index++;
+ } while (index < date.Length && ((uint)(date[index] - '0') <= '9'-'0'));
+
+ int[] days = GregorianCalendarHelper.DaysToMonth365;
+
+ if (julianDay == 0 || julianDay > days[days.Length - 1])
+ {
+ throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay);
+ }
+
+ int i = 1;
+ while (i < days.Length && julianDay > days[i])
+ {
+ i++;
+ }
+
+ Debug.Assert(i > 0 && i < days.Length);
+
+ month = i;
+ day = julianDay - days[i - 1];
+ }
+
+ /// <summary>
+ /// Parses a string like Mm.w.d into month, week and DayOfWeek values.
+ /// </summary>
+ /// <returns>
+ /// true if the parsing succeeded; otherwise, false.
+ /// </returns>
+ private static bool TZif_ParseMDateRule(string dateRule, out int month, out int week, out DayOfWeek dayOfWeek)
+ {
+ if (dateRule[0] == 'M')
+ {
+ int firstDotIndex = dateRule.IndexOf('.');
+ if (firstDotIndex > 0)
+ {
+ int secondDotIndex = dateRule.IndexOf('.', firstDotIndex + 1);
+ if (secondDotIndex > 0)
+ {
+ if (int.TryParse(dateRule.AsSpan(1, firstDotIndex - 1), out month) &&
+ int.TryParse(dateRule.AsSpan(firstDotIndex + 1, secondDotIndex - firstDotIndex - 1), out week) &&
+ int.TryParse(dateRule.AsSpan(secondDotIndex + 1), out int day))
+ {
+ dayOfWeek = (DayOfWeek)day;
+ return true;
+ }
+ }
+ }
+ }
+
+ month = 0;
+ week = 0;
+ dayOfWeek = default(DayOfWeek);
+ return false;
+ }
+
+ private static bool TZif_ParsePosixFormat(
+ string posixFormat,
+ out string standardName,
+ out string standardOffset,
+ out string daylightSavingsName,
+ out string daylightSavingsOffset,
+ out string start,
+ out string startTime,
+ out string end,
+ out string endTime)
+ {
+ standardName = null;
+ standardOffset = null;
+ daylightSavingsName = null;
+ daylightSavingsOffset = null;
+ start = null;
+ startTime = null;
+ end = null;
+ endTime = null;
+
+ int index = 0;
+ standardName = TZif_ParsePosixName(posixFormat, ref index);
+ standardOffset = TZif_ParsePosixOffset(posixFormat, ref index);
+
+ daylightSavingsName = TZif_ParsePosixName(posixFormat, ref index);
+ if (!string.IsNullOrEmpty(daylightSavingsName))
+ {
+ daylightSavingsOffset = TZif_ParsePosixOffset(posixFormat, ref index);
+
+ if (index < posixFormat.Length && posixFormat[index] == ',')
+ {
+ index++;
+ TZif_ParsePosixDateTime(posixFormat, ref index, out start, out startTime);
+
+ if (index < posixFormat.Length && posixFormat[index] == ',')
+ {
+ index++;
+ TZif_ParsePosixDateTime(posixFormat, ref index, out end, out endTime);
+ }
+ }
+ }
+
+ return !string.IsNullOrEmpty(standardName) && !string.IsNullOrEmpty(standardOffset);
+ }
+
+ private static string TZif_ParsePosixName(string posixFormat, ref int index)
+ {
+ bool isBracketEnclosed = index < posixFormat.Length && posixFormat[index] == '<';
+ if (isBracketEnclosed)
+ {
+ // move past the opening bracket
+ index++;
+
+ string result = TZif_ParsePosixString(posixFormat, ref index, c => c == '>');
+
+ // move past the closing bracket
+ if (index < posixFormat.Length && posixFormat[index] == '>')
+ {
+ index++;
+ }
+
+ return result;
+ }
+ else
+ {
+ return TZif_ParsePosixString(
+ posixFormat,
+ ref index,
+ c => char.IsDigit(c) || c == '+' || c == '-' || c == ',');
+ }
+ }
+
+ private static string TZif_ParsePosixOffset(string posixFormat, ref int index) =>
+ TZif_ParsePosixString(posixFormat, ref index, c => !char.IsDigit(c) && c != '+' && c != '-' && c != ':');
+
+ private static void TZif_ParsePosixDateTime(string posixFormat, ref int index, out string date, out string time)
+ {
+ time = null;
+
+ date = TZif_ParsePosixDate(posixFormat, ref index);
+ if (index < posixFormat.Length && posixFormat[index] == '/')
+ {
+ index++;
+ time = TZif_ParsePosixTime(posixFormat, ref index);
+ }
+ }
+
+ private static string TZif_ParsePosixDate(string posixFormat, ref int index) =>
+ TZif_ParsePosixString(posixFormat, ref index, c => c == '/' || c == ',');
+
+ private static string TZif_ParsePosixTime(string posixFormat, ref int index) =>
+ TZif_ParsePosixString(posixFormat, ref index, c => c == ',');
+
+ private static string TZif_ParsePosixString(string posixFormat, ref int index, Func<char, bool> breakCondition)
+ {
+ int startIndex = index;
+ for (; index < posixFormat.Length; index++)
+ {
+ char current = posixFormat[index];
+ if (breakCondition(current))
+ {
+ break;
+ }
+ }
+
+ return posixFormat.Substring(startIndex, index - startIndex);
+ }
+
+ // Returns the Substring from zoneAbbreviations starting at index and ending at '\0'
+ // zoneAbbreviations is expected to be in the form: "PST\0PDT\0PWT\0\PPT"
+ private static string TZif_GetZoneAbbreviation(string zoneAbbreviations, int index)
+ {
+ int lastIndex = zoneAbbreviations.IndexOf('\0', index);
+ return lastIndex > 0 ?
+ zoneAbbreviations.Substring(index, lastIndex - index) :
+ zoneAbbreviations.Substring(index);
+ }
+
+ // Converts an array of bytes into an int - always using standard byte order (Big Endian)
+ // per TZif file standard
+ private static unsafe int TZif_ToInt32(byte[] value, int startIndex)
+ {
+ fixed (byte* pbyte = &value[startIndex])
+ {
+ return (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
+ }
+ }
+
+ // Converts an array of bytes into a long - always using standard byte order (Big Endian)
+ // per TZif file standard
+ private static unsafe long TZif_ToInt64(byte[] value, int startIndex)
+ {
+ fixed (byte* pbyte = &value[startIndex])
+ {
+ int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
+ int i2 = (*(pbyte + 4) << 24) | (*(pbyte + 5) << 16) | (*(pbyte + 6) << 8) | (*(pbyte + 7));
+ return (uint)i2 | ((long)i1 << 32);
+ }
+ }
+
+ private static long TZif_ToUnixTime(byte[] value, int startIndex, TZVersion version) =>
+ version != TZVersion.V1 ?
+ TZif_ToInt64(value, startIndex) :
+ TZif_ToInt32(value, startIndex);
+
+ private static DateTime TZif_UnixTimeToDateTime(long unixTime) =>
+ unixTime < DateTimeOffset.UnixMinSeconds ? DateTime.MinValue :
+ unixTime > DateTimeOffset.UnixMaxSeconds ? DateTime.MaxValue :
+ DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
+
+ private static void TZif_ParseRaw(byte[] data, out TZifHead t, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
+ out string zoneAbbreviations, out bool[] StandardTime, out bool[] GmtTime, out string futureTransitionsPosixFormat)
+ {
+ // initialize the out parameters in case the TZifHead ctor throws
+ dts = null;
+ typeOfLocalTime = null;
+ transitionType = null;
+ zoneAbbreviations = string.Empty;
+ StandardTime = null;
+ GmtTime = null;
+ futureTransitionsPosixFormat = null;
+
+ // read in the 44-byte TZ header containing the count/length fields
+ //
+ int index = 0;
+ t = new TZifHead(data, index);
+ index += TZifHead.Length;
+
+ int timeValuesLength = 4; // the first version uses 4-bytes to specify times
+ if (t.Version != TZVersion.V1)
+ {
+ // move index past the V1 information to read the V2 information
+ index += (int)((timeValuesLength * t.TimeCount) + t.TimeCount + (6 * t.TypeCount) + ((timeValuesLength + 4) * t.LeapCount) + t.IsStdCount + t.IsGmtCount + t.CharCount);
+
+ // read the V2 header
+ t = new TZifHead(data, index);
+ index += TZifHead.Length;
+ timeValuesLength = 8; // the second version uses 8-bytes
+ }
+
+ // initialize the containers for the rest of the TZ data
+ dts = new DateTime[t.TimeCount];
+ typeOfLocalTime = new byte[t.TimeCount];
+ transitionType = new TZifType[t.TypeCount];
+ zoneAbbreviations = string.Empty;
+ StandardTime = new bool[t.TypeCount];
+ GmtTime = new bool[t.TypeCount];
+
+ // read in the UTC transition points and convert them to Windows
+ //
+ for (int i = 0; i < t.TimeCount; i++)
+ {
+ long unixTime = TZif_ToUnixTime(data, index, t.Version);
+ dts[i] = TZif_UnixTimeToDateTime(unixTime);
+ index += timeValuesLength;
+ }
+
+ // read in the Type Indices; there is a 1:1 mapping of UTC transition points to Type Indices
+ // these indices directly map to the array index in the transitionType array below
+ //
+ for (int i = 0; i < t.TimeCount; i++)
+ {
+ typeOfLocalTime[i] = data[index];
+ index += 1;
+ }
+
+ // read in the Type table. Each 6-byte entry represents
+ // {UtcOffset, IsDst, AbbreviationIndex}
+ //
+ // each AbbreviationIndex is a character index into the zoneAbbreviations string below
+ //
+ for (int i = 0; i < t.TypeCount; i++)
+ {
+ transitionType[i] = new TZifType(data, index);
+ index += 6;
+ }
+
+ // read in the Abbreviation ASCII string. This string will be in the form:
+ // "PST\0PDT\0PWT\0\PPT"
+ //
+ Encoding enc = Encoding.UTF8;
+ zoneAbbreviations = enc.GetString(data, index, (int)t.CharCount);
+ index += (int)t.CharCount;
+
+ // skip ahead of the Leap-Seconds Adjustment data. In a future release, consider adding
+ // support for Leap-Seconds
+ //
+ index += (int)(t.LeapCount * (timeValuesLength + 4)); // skip the leap second transition times
+
+ // read in the Standard Time table. There should be a 1:1 mapping between Type-Index and Standard
+ // Time table entries.
+ //
+ // TRUE = transition time is standard time
+ // FALSE = transition time is wall clock time
+ // ABSENT = transition time is wall clock time
+ //
+ for (int i = 0; i < t.IsStdCount && i < t.TypeCount && index < data.Length; i++)
+ {
+ StandardTime[i] = (data[index++] != 0);
+ }
+
+ // read in the GMT Time table. There should be a 1:1 mapping between Type-Index and GMT Time table
+ // entries.
+ //
+ // TRUE = transition time is UTC
+ // FALSE = transition time is local time
+ // ABSENT = transition time is local time
+ //
+ for (int i = 0; i < t.IsGmtCount && i < t.TypeCount && index < data.Length; i++)
+ {
+ GmtTime[i] = (data[index++] != 0);
+ }
+
+ if (t.Version != TZVersion.V1)
+ {
+ // read the POSIX-style format, which should be wrapped in newlines with the last newline at the end of the file
+ if (data[index++] == '\n' && data[data.Length - 1] == '\n')
+ {
+ futureTransitionsPosixFormat = enc.GetString(data, index, data.Length - index - 1);
+ }
+ }
+ }
+
+ private struct TZifType
+ {
+ public const int Length = 6;
+
+ public readonly TimeSpan UtcOffset;
+ public readonly bool IsDst;
+ public readonly byte AbbreviationIndex;
+
+ public TZifType(byte[] data, int index)
+ {
+ if (data == null || data.Length < index + Length)
+ {
+ throw new ArgumentException(SR.Argument_TimeZoneInfoInvalidTZif, nameof(data));
+ }
+ UtcOffset = new TimeSpan(0, 0, TZif_ToInt32(data, index + 00));
+ IsDst = (data[index + 4] != 0);
+ AbbreviationIndex = data[index + 5];
+ }
+ }
+
+ private struct TZifHead
+ {
+ public const int Length = 44;
+
+ public readonly uint Magic; // TZ_MAGIC "TZif"
+ public readonly TZVersion Version; // 1 byte for a \0 or 2 or 3
+ // public byte[15] Reserved; // reserved for future use
+ public readonly uint IsGmtCount; // number of transition time flags
+ public readonly uint IsStdCount; // number of transition time flags
+ public readonly uint LeapCount; // number of leap seconds
+ public readonly uint TimeCount; // number of transition times
+ public readonly uint TypeCount; // number of local time types
+ public readonly uint CharCount; // number of abbreviated characters
+
+ public TZifHead(byte[] data, int index)
+ {
+ if (data == null || data.Length < Length)
+ {
+ throw new ArgumentException("bad data", nameof(data));
+ }
+
+ Magic = (uint)TZif_ToInt32(data, index + 00);
+
+ if (Magic != 0x545A6966)
+ {
+ // 0x545A6966 = {0x54, 0x5A, 0x69, 0x66} = "TZif"
+ throw new ArgumentException(SR.Argument_TimeZoneInfoBadTZif, nameof(data));
+ }
+
+ byte version = data[index + 04];
+ Version =
+ version == '2' ? TZVersion.V2 :
+ version == '3' ? TZVersion.V3 :
+ TZVersion.V1; // default/fallback to V1 to guard against future, unsupported version numbers
+
+ // skip the 15 byte reserved field
+
+ // don't use the BitConverter class which parses data
+ // based on the Endianess of the machine architecture.
+ // this data is expected to always be in "standard byte order",
+ // regardless of the machine it is being processed on.
+
+ IsGmtCount = (uint)TZif_ToInt32(data, index + 20);
+ IsStdCount = (uint)TZif_ToInt32(data, index + 24);
+ LeapCount = (uint)TZif_ToInt32(data, index + 28);
+ TimeCount = (uint)TZif_ToInt32(data, index + 32);
+ TypeCount = (uint)TZif_ToInt32(data, index + 36);
+ CharCount = (uint)TZif_ToInt32(data, index + 40);
+ }
+ }
+
+ private enum TZVersion : byte
+ {
+ V1 = 0,
+ V2,
+ V3,
+ // when adding more versions, ensure all the logic using TZVersion is still correct
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs
new file mode 100644
index 0000000000..03f54a5432
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs
@@ -0,0 +1,999 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Security;
+using System.Text;
+using System.Threading;
+
+using Microsoft.Win32;
+using Microsoft.Win32.SafeHandles;
+
+using Internal.Runtime.CompilerServices;
+
+using REG_TZI_FORMAT = Interop.Kernel32.REG_TZI_FORMAT;
+using TIME_ZONE_INFORMATION = Interop.Kernel32.TIME_ZONE_INFORMATION;
+using TIME_DYNAMIC_ZONE_INFORMATION = Interop.Kernel32.TIME_DYNAMIC_ZONE_INFORMATION;
+
+namespace System
+{
+ public sealed partial class TimeZoneInfo
+ {
+ // registry constants for the 'Time Zones' hive
+ //
+ private const string TimeZonesRegistryHive = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones";
+ private const string DisplayValue = "Display";
+ private const string DaylightValue = "Dlt";
+ private const string StandardValue = "Std";
+ private const string MuiDisplayValue = "MUI_Display";
+ private const string MuiDaylightValue = "MUI_Dlt";
+ private const string MuiStandardValue = "MUI_Std";
+ private const string TimeZoneInfoValue = "TZI";
+ private const string FirstEntryValue = "FirstEntry";
+ private const string LastEntryValue = "LastEntry";
+
+ private const int MaxKeyLength = 255;
+
+#pragma warning disable 0420
+ private sealed partial class CachedData
+ {
+ private static TimeZoneInfo GetCurrentOneYearLocal()
+ {
+ // load the data from the OS
+ TIME_ZONE_INFORMATION timeZoneInformation;
+ uint result = Interop.Kernel32.GetTimeZoneInformation(out timeZoneInformation);
+ return result == Interop.Kernel32.TIME_ZONE_ID_INVALID ?
+ CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId) :
+ GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled: false);
+ }
+
+ private volatile OffsetAndRule _oneYearLocalFromUtc;
+
+ public OffsetAndRule GetOneYearLocalFromUtc(int year)
+ {
+ OffsetAndRule oneYearLocFromUtc = _oneYearLocalFromUtc;
+ if (oneYearLocFromUtc == null || oneYearLocFromUtc.Year != year)
+ {
+ TimeZoneInfo currentYear = GetCurrentOneYearLocal();
+ AdjustmentRule rule = currentYear._adjustmentRules == null ? null : currentYear._adjustmentRules[0];
+ oneYearLocFromUtc = new OffsetAndRule(year, currentYear.BaseUtcOffset, rule);
+ _oneYearLocalFromUtc = oneYearLocFromUtc;
+ }
+ return oneYearLocFromUtc;
+ }
+ }
+#pragma warning restore 0420
+
+ private sealed class OffsetAndRule
+ {
+ public readonly int Year;
+ public readonly TimeSpan Offset;
+ public readonly AdjustmentRule Rule;
+
+ public OffsetAndRule(int year, TimeSpan offset, AdjustmentRule rule)
+ {
+ Year = year;
+ Offset = offset;
+ Rule = rule;
+ }
+ }
+
+ /// <summary>
+ /// Returns a cloned array of AdjustmentRule objects
+ /// </summary>
+ public AdjustmentRule[] GetAdjustmentRules()
+ {
+ if (_adjustmentRules == null)
+ {
+ return Array.Empty<AdjustmentRule>();
+ }
+
+ return (AdjustmentRule[])_adjustmentRules.Clone();
+ }
+
+ private static void PopulateAllSystemTimeZones(CachedData cachedData)
+ {
+ Debug.Assert(Monitor.IsEntered(cachedData));
+
+ using (RegistryKey reg = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive, writable: false))
+ {
+ if (reg != null)
+ {
+ foreach (string keyName in reg.GetSubKeyNames())
+ {
+ TimeZoneInfo value;
+ Exception ex;
+ TryGetTimeZone(keyName, false, out value, out ex, cachedData); // populate the cache
+ }
+ }
+ }
+ }
+
+ private TimeZoneInfo(in TIME_ZONE_INFORMATION zone, bool dstDisabled)
+ {
+ string standardName = zone.GetStandardName();
+ if (standardName.Length == 0)
+ {
+ _id = LocalId; // the ID must contain at least 1 character - initialize _id to "Local"
+ }
+ else
+ {
+ _id = standardName;
+ }
+ _baseUtcOffset = new TimeSpan(0, -(zone.Bias), 0);
+
+ if (!dstDisabled)
+ {
+ // only create the adjustment rule if DST is enabled
+ REG_TZI_FORMAT regZone = new REG_TZI_FORMAT(zone);
+ AdjustmentRule rule = CreateAdjustmentRuleFromTimeZoneInformation(regZone, DateTime.MinValue.Date, DateTime.MaxValue.Date, zone.Bias);
+ if (rule != null)
+ {
+ _adjustmentRules = new[] { rule };
+ }
+ }
+
+ ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime);
+ _displayName = standardName;
+ _standardDisplayName = standardName;
+ _daylightDisplayName = zone.GetDaylightName();
+ }
+
+ /// <summary>
+ /// Helper function to check if the current TimeZoneInformation struct does not support DST.
+ /// This check returns true when the DaylightDate == StandardDate.
+ /// This check is only meant to be used for "Local".
+ /// </summary>
+ private static bool CheckDaylightSavingTimeNotSupported(in TIME_ZONE_INFORMATION timeZone) =>
+ timeZone.DaylightDate.Equals(timeZone.StandardDate);
+
+ /// <summary>
+ /// Converts a REG_TZI_FORMAT struct to an AdjustmentRule.
+ /// </summary>
+ private static AdjustmentRule CreateAdjustmentRuleFromTimeZoneInformation(in REG_TZI_FORMAT timeZoneInformation, DateTime startDate, DateTime endDate, int defaultBaseUtcOffset)
+ {
+ bool supportsDst = timeZoneInformation.StandardDate.Month != 0;
+
+ if (!supportsDst)
+ {
+ if (timeZoneInformation.Bias == defaultBaseUtcOffset)
+ {
+ // this rule will not contain any information to be used to adjust dates. just ignore it
+ return null;
+ }
+
+ return AdjustmentRule.CreateAdjustmentRule(
+ startDate,
+ endDate,
+ TimeSpan.Zero, // no daylight saving transition
+ TransitionTime.CreateFixedDateRule(DateTime.MinValue, 1, 1),
+ TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(1), 1, 1),
+ new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0), // Bias delta is all what we need from this rule
+ noDaylightTransitions: false);
+ }
+
+ //
+ // Create an AdjustmentRule with TransitionTime objects
+ //
+ TransitionTime daylightTransitionStart;
+ if (!TransitionTimeFromTimeZoneInformation(timeZoneInformation, out daylightTransitionStart, readStartDate: true))
+ {
+ return null;
+ }
+
+ TransitionTime daylightTransitionEnd;
+ if (!TransitionTimeFromTimeZoneInformation(timeZoneInformation, out daylightTransitionEnd, readStartDate: false))
+ {
+ return null;
+ }
+
+ if (daylightTransitionStart.Equals(daylightTransitionEnd))
+ {
+ // this happens when the time zone does support DST but the OS has DST disabled
+ return null;
+ }
+
+ return AdjustmentRule.CreateAdjustmentRule(
+ startDate,
+ endDate,
+ new TimeSpan(0, -timeZoneInformation.DaylightBias, 0),
+ daylightTransitionStart,
+ daylightTransitionEnd,
+ new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0),
+ noDaylightTransitions: false);
+ }
+
+ /// <summary>
+ /// Helper function that searches the registry for a time zone entry
+ /// that matches the TimeZoneInformation struct.
+ /// </summary>
+ private static string FindIdFromTimeZoneInformation(in TIME_ZONE_INFORMATION timeZone, out bool dstDisabled)
+ {
+ dstDisabled = false;
+
+ using (RegistryKey key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive, writable: false))
+ {
+ if (key == null)
+ {
+ return null;
+ }
+
+ foreach (string keyName in key.GetSubKeyNames())
+ {
+ if (TryCompareTimeZoneInformationToRegistry(timeZone, keyName, out dstDisabled))
+ {
+ return keyName;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Helper function for retrieving the local system time zone.
+ /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException.
+ /// Assumes cachedData lock is taken.
+ /// </summary>
+ /// <returns>A new TimeZoneInfo instance.</returns>
+ private static TimeZoneInfo GetLocalTimeZone(CachedData cachedData)
+ {
+ Debug.Assert(Monitor.IsEntered(cachedData));
+
+ //
+ // Try using the "kernel32!GetDynamicTimeZoneInformation" API to get the "id"
+ //
+ var dynamicTimeZoneInformation = new TIME_DYNAMIC_ZONE_INFORMATION();
+
+ // call kernel32!GetDynamicTimeZoneInformation...
+ uint result = Interop.Kernel32.GetDynamicTimeZoneInformation(out dynamicTimeZoneInformation);
+ if (result == Interop.Kernel32.TIME_ZONE_ID_INVALID)
+ {
+ // return a dummy entry
+ return CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId);
+ }
+
+ // check to see if we can use the key name returned from the API call
+ string dynamicTimeZoneKeyName = dynamicTimeZoneInformation.GetTimeZoneKeyName();
+ if (dynamicTimeZoneKeyName.Length != 0)
+ {
+ TimeZoneInfo zone;
+ Exception ex;
+
+ if (TryGetTimeZone(dynamicTimeZoneKeyName, dynamicTimeZoneInformation.DynamicDaylightTimeDisabled != 0, out zone, out ex, cachedData) == TimeZoneInfoResult.Success)
+ {
+ // successfully loaded the time zone from the registry
+ return zone;
+ }
+ }
+
+ var timeZoneInformation = new TIME_ZONE_INFORMATION(dynamicTimeZoneInformation);
+
+ // the key name was not returned or it pointed to a bogus entry - search for the entry ourselves
+ string id = FindIdFromTimeZoneInformation(timeZoneInformation, out bool dstDisabled);
+
+ if (id != null)
+ {
+ TimeZoneInfo zone;
+ Exception ex;
+ if (TryGetTimeZone(id, dstDisabled, out zone, out ex, cachedData) == TimeZoneInfoResult.Success)
+ {
+ // successfully loaded the time zone from the registry
+ return zone;
+ }
+ }
+
+ // We could not find the data in the registry. Fall back to using
+ // the data from the Win32 API
+ return GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled);
+ }
+
+ /// <summary>
+ /// Helper function used by 'GetLocalTimeZone()' - this function wraps a bunch of
+ /// try/catch logic for handling the TimeZoneInfo private constructor that takes
+ /// a TIME_ZONE_INFORMATION structure.
+ /// </summary>
+ private static TimeZoneInfo GetLocalTimeZoneFromWin32Data(in TIME_ZONE_INFORMATION timeZoneInformation, bool dstDisabled)
+ {
+ // first try to create the TimeZoneInfo with the original 'dstDisabled' flag
+ try
+ {
+ return new TimeZoneInfo(timeZoneInformation, dstDisabled);
+ }
+ catch (ArgumentException) { }
+ catch (InvalidTimeZoneException) { }
+
+ // if 'dstDisabled' was false then try passing in 'true' as a last ditch effort
+ if (!dstDisabled)
+ {
+ try
+ {
+ return new TimeZoneInfo(timeZoneInformation, dstDisabled: true);
+ }
+ catch (ArgumentException) { }
+ catch (InvalidTimeZoneException) { }
+ }
+
+ // the data returned from Windows is completely bogus; return a dummy entry
+ return CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId);
+ }
+
+ /// <summary>
+ /// Helper function for retrieving a TimeZoneInfo object by <time_zone_name>.
+ /// This function wraps the logic necessary to keep the private
+ /// SystemTimeZones cache in working order
+ ///
+ /// This function will either return a valid TimeZoneInfo instance or
+ /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'.
+ /// </summary>
+ public static TimeZoneInfo FindSystemTimeZoneById(string id)
+ {
+ // Special case for Utc as it will not exist in the dictionary with the rest
+ // of the system time zones. There is no need to do this check for Local.Id
+ // since Local is a real time zone that exists in the dictionary cache
+ if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase))
+ {
+ return Utc;
+ }
+
+ if (id == null)
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+ if (id.Length == 0 || id.Length > MaxKeyLength || id.Contains('\0'))
+ {
+ throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id));
+ }
+
+ TimeZoneInfo value;
+ Exception e;
+
+ TimeZoneInfoResult result;
+
+ CachedData cachedData = s_cachedData;
+
+ lock (cachedData)
+ {
+ result = TryGetTimeZone(id, false, out value, out e, cachedData);
+ }
+
+ if (result == TimeZoneInfoResult.Success)
+ {
+ return value;
+ }
+ else if (result == TimeZoneInfoResult.InvalidTimeZoneException)
+ {
+ throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidRegistryData, id), e);
+ }
+ else if (result == TimeZoneInfoResult.SecurityException)
+ {
+ throw new SecurityException(SR.Format(SR.Security_CannotReadRegistryData, id), e);
+ }
+ else
+ {
+ throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e);
+ }
+ }
+
+ // DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone
+ internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst)
+ {
+ bool isDaylightSavings = false;
+ isAmbiguousLocalDst = false;
+ TimeSpan baseOffset;
+ int timeYear = time.Year;
+
+ OffsetAndRule match = s_cachedData.GetOneYearLocalFromUtc(timeYear);
+ baseOffset = match.Offset;
+
+ if (match.Rule != null)
+ {
+ baseOffset = baseOffset + match.Rule.BaseUtcOffsetDelta;
+ if (match.Rule.HasDaylightSaving)
+ {
+ isDaylightSavings = GetIsDaylightSavingsFromUtc(time, timeYear, match.Offset, match.Rule, null, out isAmbiguousLocalDst, Local);
+ baseOffset += (isDaylightSavings ? match.Rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
+ }
+ }
+ return baseOffset;
+ }
+
+ /// <summary>
+ /// Converts a REG_TZI_FORMAT struct to a TransitionTime
+ /// - When the argument 'readStart' is true the corresponding daylightTransitionTimeStart field is read
+ /// - When the argument 'readStart' is false the corresponding dayightTransitionTimeEnd field is read
+ /// </summary>
+ private static bool TransitionTimeFromTimeZoneInformation(in REG_TZI_FORMAT timeZoneInformation, out TransitionTime transitionTime, bool readStartDate)
+ {
+ //
+ // SYSTEMTIME -
+ //
+ // If the time zone does not support daylight saving time or if the caller needs
+ // to disable daylight saving time, the wMonth member in the SYSTEMTIME structure
+ // must be zero. If this date is specified, the DaylightDate value in the
+ // TIME_ZONE_INFORMATION structure must also be specified. Otherwise, the system
+ // assumes the time zone data is invalid and no changes will be applied.
+ //
+ bool supportsDst = (timeZoneInformation.StandardDate.Month != 0);
+
+ if (!supportsDst)
+ {
+ transitionTime = default(TransitionTime);
+ return false;
+ }
+
+ //
+ // SYSTEMTIME -
+ //
+ // * FixedDateRule -
+ // If the Year member is not zero, the transition date is absolute; it will only occur one time
+ //
+ // * FloatingDateRule -
+ // To select the correct day in the month, set the Year member to zero, the Hour and Minute
+ // members to the transition time, the DayOfWeek member to the appropriate weekday, and the
+ // Day member to indicate the occurence of the day of the week within the month (first through fifth).
+ //
+ // Using this notation, specify the 2:00a.m. on the first Sunday in April as follows:
+ // Hour = 2,
+ // Month = 4,
+ // DayOfWeek = 0,
+ // Day = 1.
+ //
+ // Specify 2:00a.m. on the last Thursday in October as follows:
+ // Hour = 2,
+ // Month = 10,
+ // DayOfWeek = 4,
+ // Day = 5.
+ //
+ if (readStartDate)
+ {
+ //
+ // read the "daylightTransitionStart"
+ //
+ if (timeZoneInformation.DaylightDate.Year == 0)
+ {
+ transitionTime = TransitionTime.CreateFloatingDateRule(
+ new DateTime(1, /* year */
+ 1, /* month */
+ 1, /* day */
+ timeZoneInformation.DaylightDate.Hour,
+ timeZoneInformation.DaylightDate.Minute,
+ timeZoneInformation.DaylightDate.Second,
+ timeZoneInformation.DaylightDate.Milliseconds),
+ timeZoneInformation.DaylightDate.Month,
+ timeZoneInformation.DaylightDate.Day, /* Week 1-5 */
+ (DayOfWeek)timeZoneInformation.DaylightDate.DayOfWeek);
+ }
+ else
+ {
+ transitionTime = TransitionTime.CreateFixedDateRule(
+ new DateTime(1, /* year */
+ 1, /* month */
+ 1, /* day */
+ timeZoneInformation.DaylightDate.Hour,
+ timeZoneInformation.DaylightDate.Minute,
+ timeZoneInformation.DaylightDate.Second,
+ timeZoneInformation.DaylightDate.Milliseconds),
+ timeZoneInformation.DaylightDate.Month,
+ timeZoneInformation.DaylightDate.Day);
+ }
+ }
+ else
+ {
+ //
+ // read the "daylightTransitionEnd"
+ //
+ if (timeZoneInformation.StandardDate.Year == 0)
+ {
+ transitionTime = TransitionTime.CreateFloatingDateRule(
+ new DateTime(1, /* year */
+ 1, /* month */
+ 1, /* day */
+ timeZoneInformation.StandardDate.Hour,
+ timeZoneInformation.StandardDate.Minute,
+ timeZoneInformation.StandardDate.Second,
+ timeZoneInformation.StandardDate.Milliseconds),
+ timeZoneInformation.StandardDate.Month,
+ timeZoneInformation.StandardDate.Day, /* Week 1-5 */
+ (DayOfWeek)timeZoneInformation.StandardDate.DayOfWeek);
+ }
+ else
+ {
+ transitionTime = TransitionTime.CreateFixedDateRule(
+ new DateTime(1, /* year */
+ 1, /* month */
+ 1, /* day */
+ timeZoneInformation.StandardDate.Hour,
+ timeZoneInformation.StandardDate.Minute,
+ timeZoneInformation.StandardDate.Second,
+ timeZoneInformation.StandardDate.Milliseconds),
+ timeZoneInformation.StandardDate.Month,
+ timeZoneInformation.StandardDate.Day);
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Helper function that takes:
+ /// 1. A string representing a <time_zone_name> registry key name.
+ /// 2. A REG_TZI_FORMAT struct containing the default rule.
+ /// 3. An AdjustmentRule[] out-parameter.
+ /// </summary>
+ private static bool TryCreateAdjustmentRules(string id, in REG_TZI_FORMAT defaultTimeZoneInformation, out AdjustmentRule[] rules, out Exception e, int defaultBaseUtcOffset)
+ {
+ rules = null;
+ e = null;
+
+ try
+ {
+ // Optional, Dynamic Time Zone Registry Data
+ // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ //
+ // HKLM
+ // Software
+ // Microsoft
+ // Windows NT
+ // CurrentVersion
+ // Time Zones
+ // <time_zone_name>
+ // Dynamic DST
+ // * "FirstEntry" REG_DWORD "1980"
+ // First year in the table. If the current year is less than this value,
+ // this entry will be used for DST boundaries
+ // * "LastEntry" REG_DWORD "2038"
+ // Last year in the table. If the current year is greater than this value,
+ // this entry will be used for DST boundaries"
+ // * "<year1>" REG_BINARY REG_TZI_FORMAT
+ // * "<year2>" REG_BINARY REG_TZI_FORMAT
+ // * "<year3>" REG_BINARY REG_TZI_FORMAT
+ //
+ using (RegistryKey dynamicKey = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id + "\\Dynamic DST", writable: false))
+ {
+ if (dynamicKey == null)
+ {
+ AdjustmentRule rule = CreateAdjustmentRuleFromTimeZoneInformation(
+ defaultTimeZoneInformation, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset);
+ if (rule != null)
+ {
+ rules = new[] { rule };
+ }
+ return true;
+ }
+
+ //
+ // loop over all of the "<time_zone_name>\Dynamic DST" hive entries
+ //
+ // read FirstEntry {MinValue - (year1, 12, 31)}
+ // read MiddleEntry {(yearN, 1, 1) - (yearN, 12, 31)}
+ // read LastEntry {(yearN, 1, 1) - MaxValue }
+
+ // read the FirstEntry and LastEntry key values (ex: "1980", "2038")
+ int first = (int)dynamicKey.GetValue(FirstEntryValue, -1, RegistryValueOptions.None);
+ int last = (int)dynamicKey.GetValue(LastEntryValue, -1, RegistryValueOptions.None);
+
+ if (first == -1 || last == -1 || first > last)
+ {
+ return false;
+ }
+
+ // read the first year entry
+ REG_TZI_FORMAT dtzi;
+
+ if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, first.ToString(CultureInfo.InvariantCulture), out dtzi))
+ {
+ return false;
+ }
+
+ if (first == last)
+ {
+ // there is just 1 dynamic rule for this time zone.
+ AdjustmentRule rule = CreateAdjustmentRuleFromTimeZoneInformation(dtzi, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset);
+ if (rule != null)
+ {
+ rules = new[] { rule };
+ }
+ return true;
+ }
+
+ List<AdjustmentRule> rulesList = new List<AdjustmentRule>(1);
+
+ // there are more than 1 dynamic rules for this time zone.
+ AdjustmentRule firstRule = CreateAdjustmentRuleFromTimeZoneInformation(
+ dtzi,
+ DateTime.MinValue.Date, // MinValue
+ new DateTime(first, 12, 31), // December 31, <FirstYear>
+ defaultBaseUtcOffset);
+
+ if (firstRule != null)
+ {
+ rulesList.Add(firstRule);
+ }
+
+ // read the middle year entries
+ for (int i = first + 1; i < last; i++)
+ {
+ if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, i.ToString(CultureInfo.InvariantCulture), out dtzi))
+ {
+ return false;
+ }
+ AdjustmentRule middleRule = CreateAdjustmentRuleFromTimeZoneInformation(
+ dtzi,
+ new DateTime(i, 1, 1), // January 01, <Year>
+ new DateTime(i, 12, 31), // December 31, <Year>
+ defaultBaseUtcOffset);
+
+ if (middleRule != null)
+ {
+ rulesList.Add(middleRule);
+ }
+ }
+
+ // read the last year entry
+ if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, last.ToString(CultureInfo.InvariantCulture), out dtzi))
+ {
+ return false;
+ }
+ AdjustmentRule lastRule = CreateAdjustmentRuleFromTimeZoneInformation(
+ dtzi,
+ new DateTime(last, 1, 1), // January 01, <LastYear>
+ DateTime.MaxValue.Date, // MaxValue
+ defaultBaseUtcOffset);
+
+ if (lastRule != null)
+ {
+ rulesList.Add(lastRule);
+ }
+
+ // convert the List to an AdjustmentRule array
+ if (rulesList.Count != 0)
+ {
+ rules = rulesList.ToArray();
+ }
+ } // end of: using (RegistryKey dynamicKey...
+ }
+ catch (InvalidCastException ex)
+ {
+ // one of the RegistryKey.GetValue calls could not be cast to an expected value type
+ e = ex;
+ return false;
+ }
+ catch (ArgumentOutOfRangeException ex)
+ {
+ e = ex;
+ return false;
+ }
+ catch (ArgumentException ex)
+ {
+ e = ex;
+ return false;
+ }
+ return true;
+ }
+
+ private static unsafe bool TryGetTimeZoneEntryFromRegistry(RegistryKey key, string name, out REG_TZI_FORMAT dtzi)
+ {
+ byte[] regValue = key.GetValue(name, null, RegistryValueOptions.None) as byte[];
+ if (regValue == null || regValue.Length != sizeof(REG_TZI_FORMAT))
+ {
+ dtzi = default;
+ return false;
+ }
+ fixed (byte * pBytes = &regValue[0])
+ dtzi = *(REG_TZI_FORMAT *)pBytes;
+ return true;
+ }
+
+ /// <summary>
+ /// Helper function that compares the StandardBias and StandardDate portion a
+ /// TimeZoneInformation struct to a time zone registry entry.
+ /// </summary>
+ private static bool TryCompareStandardDate(in TIME_ZONE_INFORMATION timeZone, in REG_TZI_FORMAT registryTimeZoneInfo) =>
+ timeZone.Bias == registryTimeZoneInfo.Bias &&
+ timeZone.StandardBias == registryTimeZoneInfo.StandardBias &&
+ timeZone.StandardDate.Equals(registryTimeZoneInfo.StandardDate);
+
+ /// <summary>
+ /// Helper function that compares a TimeZoneInformation struct to a time zone registry entry.
+ /// </summary>
+ private static bool TryCompareTimeZoneInformationToRegistry(in TIME_ZONE_INFORMATION timeZone, string id, out bool dstDisabled)
+ {
+ dstDisabled = false;
+
+ using (RegistryKey key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id, writable: false))
+ {
+ if (key == null)
+ {
+ return false;
+ }
+
+ REG_TZI_FORMAT registryTimeZoneInfo;
+ if (!TryGetTimeZoneEntryFromRegistry(key, TimeZoneInfoValue, out registryTimeZoneInfo))
+ {
+ return false;
+ }
+
+ //
+ // first compare the bias and standard date information between the data from the Win32 API
+ // and the data from the registry...
+ //
+ bool result = TryCompareStandardDate(timeZone, registryTimeZoneInfo);
+
+ if (!result)
+ {
+ return false;
+ }
+
+ result = dstDisabled || CheckDaylightSavingTimeNotSupported(timeZone) ||
+ //
+ // since Daylight Saving Time is not "disabled", do a straight comparision between
+ // the Win32 API data and the registry data ...
+ //
+ (timeZone.DaylightBias == registryTimeZoneInfo.DaylightBias &&
+ timeZone.DaylightDate.Equals(registryTimeZoneInfo.DaylightDate));
+
+ // Finally compare the "StandardName" string value...
+ //
+ // we do not compare "DaylightName" as this TimeZoneInformation field may contain
+ // either "StandardName" or "DaylightName" depending on the time of year and current machine settings
+ //
+ if (result)
+ {
+ string registryStandardName = key.GetValue(StandardValue, string.Empty, RegistryValueOptions.None) as string;
+ result = string.Equals(registryStandardName, timeZone.GetStandardName(), StringComparison.Ordinal);
+ }
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Helper function for retrieving a localized string resource via MUI.
+ /// The function expects a string in the form: "@resource.dll, -123"
+ ///
+ /// "resource.dll" is a language-neutral portable executable (LNPE) file in
+ /// the %windir%\system32 directory. The OS is queried to find the best-fit
+ /// localized resource file for this LNPE (ex: %windir%\system32\en-us\resource.dll.mui).
+ /// If a localized resource file exists, we LoadString resource ID "123" and
+ /// return it to our caller.
+ /// </summary>
+ private static string TryGetLocalizedNameByMuiNativeResource(string resource)
+ {
+ if (string.IsNullOrEmpty(resource))
+ {
+ return string.Empty;
+ }
+
+ // parse "@tzres.dll, -100"
+ //
+ // filePath = "C:\Windows\System32\tzres.dll"
+ // resourceId = -100
+ //
+ string[] resources = resource.Split(',');
+ if (resources.Length != 2)
+ {
+ return string.Empty;
+ }
+
+ string filePath;
+ int resourceId;
+
+ // get the path to Windows\System32
+ string system32 = Environment.SystemDirectory;
+
+ // trim the string "@tzres.dll" => "tzres.dll"
+ string tzresDll = resources[0].TrimStart('@');
+
+ try
+ {
+ filePath = Path.Combine(system32, tzresDll);
+ }
+ catch (ArgumentException)
+ {
+ // there were probably illegal characters in the path
+ return string.Empty;
+ }
+
+ if (!int.TryParse(resources[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out resourceId))
+ {
+ return string.Empty;
+ }
+ resourceId = -resourceId;
+
+ try
+ {
+ StringBuilder fileMuiPath = StringBuilderCache.Acquire(Interop.Kernel32.MAX_PATH);
+ fileMuiPath.Length = Interop.Kernel32.MAX_PATH;
+ int fileMuiPathLength = Interop.Kernel32.MAX_PATH;
+ int languageLength = 0;
+ long enumerator = 0;
+
+ bool succeeded = Interop.Kernel32.GetFileMUIPath(
+ Interop.Kernel32.MUI_PREFERRED_UI_LANGUAGES,
+ filePath, null /* language */, ref languageLength,
+ fileMuiPath, ref fileMuiPathLength, ref enumerator);
+ if (!succeeded)
+ {
+ StringBuilderCache.Release(fileMuiPath);
+ return string.Empty;
+ }
+ return TryGetLocalizedNameByNativeResource(StringBuilderCache.GetStringAndRelease(fileMuiPath), resourceId);
+ }
+ catch (EntryPointNotFoundException)
+ {
+ return string.Empty;
+ }
+ }
+
+ /// <summary>
+ /// Helper function for retrieving a localized string resource via a native resource DLL.
+ /// The function expects a string in the form: "C:\Windows\System32\en-us\resource.dll"
+ ///
+ /// "resource.dll" is a language-specific resource DLL.
+ /// If the localized resource DLL exists, LoadString(resource) is returned.
+ /// </summary>
+ private static string TryGetLocalizedNameByNativeResource(string filePath, int resource)
+ {
+ using (SafeLibraryHandle handle =
+ Interop.Kernel32.LoadLibraryEx(filePath, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_AS_DATAFILE))
+ {
+ if (!handle.IsInvalid)
+ {
+ const int LoadStringMaxLength = 500;
+
+ StringBuilder localizedResource = StringBuilderCache.Acquire(LoadStringMaxLength);
+
+ int result = Interop.User32.LoadString(handle, resource,
+ localizedResource, LoadStringMaxLength);
+
+ if (result != 0)
+ {
+ return StringBuilderCache.GetStringAndRelease(localizedResource);
+ }
+ }
+ }
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Helper function for retrieving the DisplayName, StandardName, and DaylightName from the registry
+ ///
+ /// The function first checks the MUI_ key-values, and if they exist, it loads the strings from the MUI
+ /// resource dll(s). When the keys do not exist, the function falls back to reading from the standard
+ /// key-values
+ /// </summary>
+ private static void GetLocalizedNamesByRegistryKey(RegistryKey key, out string displayName, out string standardName, out string daylightName)
+ {
+ displayName = string.Empty;
+ standardName = string.Empty;
+ daylightName = string.Empty;
+
+ // read the MUI_ registry keys
+ string displayNameMuiResource = key.GetValue(MuiDisplayValue, string.Empty, RegistryValueOptions.None) as string;
+ string standardNameMuiResource = key.GetValue(MuiStandardValue, string.Empty, RegistryValueOptions.None) as string;
+ string daylightNameMuiResource = key.GetValue(MuiDaylightValue, string.Empty, RegistryValueOptions.None) as string;
+
+ // try to load the strings from the native resource DLL(s)
+ if (!string.IsNullOrEmpty(displayNameMuiResource))
+ {
+ displayName = TryGetLocalizedNameByMuiNativeResource(displayNameMuiResource);
+ }
+
+ if (!string.IsNullOrEmpty(standardNameMuiResource))
+ {
+ standardName = TryGetLocalizedNameByMuiNativeResource(standardNameMuiResource);
+ }
+
+ if (!string.IsNullOrEmpty(daylightNameMuiResource))
+ {
+ daylightName = TryGetLocalizedNameByMuiNativeResource(daylightNameMuiResource);
+ }
+
+ // fallback to using the standard registry keys
+ if (string.IsNullOrEmpty(displayName))
+ {
+ displayName = key.GetValue(DisplayValue, string.Empty, RegistryValueOptions.None) as string;
+ }
+ if (string.IsNullOrEmpty(standardName))
+ {
+ standardName = key.GetValue(StandardValue, string.Empty, RegistryValueOptions.None) as string;
+ }
+ if (string.IsNullOrEmpty(daylightName))
+ {
+ daylightName = key.GetValue(DaylightValue, string.Empty, RegistryValueOptions.None) as string;
+ }
+ }
+
+ /// <summary>
+ /// Helper function that takes a string representing a <time_zone_name> registry key name
+ /// and returns a TimeZoneInfo instance.
+ /// </summary>
+ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out TimeZoneInfo value, out Exception e)
+ {
+ e = null;
+
+ // Standard Time Zone Registry Data
+ // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ // HKLM
+ // Software
+ // Microsoft
+ // Windows NT
+ // CurrentVersion
+ // Time Zones
+ // <time_zone_name>
+ // * STD, REG_SZ "Standard Time Name"
+ // (For OS installed zones, this will always be English)
+ // * MUI_STD, REG_SZ "@tzres.dll,-1234"
+ // Indirect string to localized resource for Standard Time,
+ // add "%windir%\system32\" after "@"
+ // * DLT, REG_SZ "Daylight Time Name"
+ // (For OS installed zones, this will always be English)
+ // * MUI_DLT, REG_SZ "@tzres.dll,-1234"
+ // Indirect string to localized resource for Daylight Time,
+ // add "%windir%\system32\" after "@"
+ // * Display, REG_SZ "Display Name like (GMT-8:00) Pacific Time..."
+ // * MUI_Display, REG_SZ "@tzres.dll,-1234"
+ // Indirect string to localized resource for the Display,
+ // add "%windir%\system32\" after "@"
+ // * TZI, REG_BINARY REG_TZI_FORMAT
+ //
+ using (RegistryKey key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id, writable: false))
+ {
+ if (key == null)
+ {
+ value = null;
+ return TimeZoneInfoResult.TimeZoneNotFoundException;
+ }
+
+ REG_TZI_FORMAT defaultTimeZoneInformation;
+ if (!TryGetTimeZoneEntryFromRegistry(key, TimeZoneInfoValue, out defaultTimeZoneInformation))
+ {
+ // the registry value could not be cast to a byte array
+ value = null;
+ return TimeZoneInfoResult.InvalidTimeZoneException;
+ }
+
+ AdjustmentRule[] adjustmentRules;
+ if (!TryCreateAdjustmentRules(id, defaultTimeZoneInformation, out adjustmentRules, out e, defaultTimeZoneInformation.Bias))
+ {
+ value = null;
+ return TimeZoneInfoResult.InvalidTimeZoneException;
+ }
+
+ GetLocalizedNamesByRegistryKey(key, out string displayName, out string standardName, out string daylightName);
+
+ try
+ {
+ value = new TimeZoneInfo(
+ id,
+ new TimeSpan(0, -(defaultTimeZoneInformation.Bias), 0),
+ displayName,
+ standardName,
+ daylightName,
+ adjustmentRules,
+ disableDaylightSavingTime: false);
+
+ return TimeZoneInfoResult.Success;
+ }
+ catch (ArgumentException ex)
+ {
+ // TimeZoneInfo constructor can throw ArgumentException and InvalidTimeZoneException
+ value = null;
+ e = ex;
+ return TimeZoneInfoResult.InvalidTimeZoneException;
+ }
+ catch (InvalidTimeZoneException ex)
+ {
+ // TimeZoneInfo constructor can throw ArgumentException and InvalidTimeZoneException
+ value = null;
+ e = ex;
+ return TimeZoneInfoResult.InvalidTimeZoneException;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.cs
new file mode 100644
index 0000000000..6e27376b68
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.cs
@@ -0,0 +1,1993 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace System
+{
+ //
+ // DateTime uses TimeZoneInfo under the hood for IsDaylightSavingTime, IsAmbiguousTime, and GetUtcOffset.
+ // These TimeZoneInfo APIs can throw ArgumentException when an Invalid-Time is passed in. To avoid this
+ // unwanted behavior in DateTime public APIs, DateTime internally passes the
+ // TimeZoneInfoOptions.NoThrowOnInvalidTime flag to internal TimeZoneInfo APIs.
+ //
+ // In the future we can consider exposing similar options on the public TimeZoneInfo APIs if there is enough
+ // demand for this alternate behavior.
+ //
+ [Flags]
+ internal enum TimeZoneInfoOptions
+ {
+ None = 1,
+ NoThrowOnInvalidTime = 2
+ };
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
+ {
+ private enum TimeZoneInfoResult
+ {
+ Success = 0,
+ TimeZoneNotFoundException = 1,
+ InvalidTimeZoneException = 2,
+ SecurityException = 3
+ };
+
+ private readonly string _id;
+ private readonly string _displayName;
+ private readonly string _standardDisplayName;
+ private readonly string _daylightDisplayName;
+ private readonly TimeSpan _baseUtcOffset;
+ private readonly bool _supportsDaylightSavingTime;
+ private readonly AdjustmentRule[] _adjustmentRules;
+
+ // constants for TimeZoneInfo.Local and TimeZoneInfo.Utc
+ private const string UtcId = "UTC";
+ private const string LocalId = "Local";
+
+ private static readonly TimeZoneInfo s_utcTimeZone = CreateCustomTimeZone(UtcId, TimeSpan.Zero, UtcId, UtcId);
+
+ private static CachedData s_cachedData = new CachedData();
+
+ //
+ // All cached data are encapsulated in a helper class to allow consistent view even when the data are refreshed using ClearCachedData()
+ //
+ // For example, TimeZoneInfo.Local can be cleared by another thread calling TimeZoneInfo.ClearCachedData. Without the consistent snapshot,
+ // there is a chance that the internal ConvertTime calls will throw since 'source' won't be reference equal to the new TimeZoneInfo.Local.
+ //
+#pragma warning disable 0420
+ private sealed partial class CachedData
+ {
+ private volatile TimeZoneInfo _localTimeZone;
+
+ private TimeZoneInfo CreateLocal()
+ {
+ lock (this)
+ {
+ TimeZoneInfo timeZone = _localTimeZone;
+ if (timeZone == null)
+ {
+ timeZone = GetLocalTimeZone(this);
+
+ // this step is to break the reference equality
+ // between TimeZoneInfo.Local and a second time zone
+ // such as "Pacific Standard Time"
+ timeZone = new TimeZoneInfo(
+ timeZone._id,
+ timeZone._baseUtcOffset,
+ timeZone._displayName,
+ timeZone._standardDisplayName,
+ timeZone._daylightDisplayName,
+ timeZone._adjustmentRules,
+ disableDaylightSavingTime: false);
+
+ _localTimeZone = timeZone;
+ }
+ return timeZone;
+ }
+ }
+
+ public TimeZoneInfo Local
+ {
+ get
+ {
+ TimeZoneInfo timeZone = _localTimeZone;
+ if (timeZone == null)
+ {
+ timeZone = CreateLocal();
+ }
+ return timeZone;
+ }
+ }
+
+ /// <summary>
+ /// Helper function that returns the corresponding DateTimeKind for this TimeZoneInfo.
+ /// </summary>
+ public DateTimeKind GetCorrespondingKind(TimeZoneInfo timeZone)
+ {
+ // We check reference equality to see if 'this' is the same as
+ // TimeZoneInfo.Local or TimeZoneInfo.Utc. This check is needed to
+ // support setting the DateTime Kind property to 'Local' or
+ // 'Utc' on the ConverTime(...) return value.
+ //
+ // Using reference equality instead of value equality was a
+ // performance based design compromise. The reference equality
+ // has much greater performance, but it reduces the number of
+ // returned DateTime's that can be properly set as 'Local' or 'Utc'.
+ //
+ // For example, the user could be converting to the TimeZoneInfo returned
+ // by FindSystemTimeZoneById("Pacific Standard Time") and their local
+ // machine may be in Pacific time. If we used value equality to determine
+ // the corresponding Kind then this conversion would be tagged as 'Local';
+ // where as we are currently tagging the returned DateTime as 'Unspecified'
+ // in this example. Only when the user passes in TimeZoneInfo.Local or
+ // TimeZoneInfo.Utc to the ConvertTime(...) methods will this check succeed.
+ //
+ return
+ ReferenceEquals(timeZone, s_utcTimeZone) ? DateTimeKind.Utc :
+ ReferenceEquals(timeZone, _localTimeZone) ? DateTimeKind.Local :
+ DateTimeKind.Unspecified;
+ }
+
+ public Dictionary<string, TimeZoneInfo> _systemTimeZones;
+ public ReadOnlyCollection<TimeZoneInfo> _readOnlySystemTimeZones;
+ public bool _allSystemTimeZonesRead;
+ };
+#pragma warning restore 0420
+
+ // used by GetUtcOffsetFromUtc (DateTime.Now, DateTime.ToLocalTime) for max/min whole-day range checks
+ private static readonly DateTime s_maxDateOnly = new DateTime(9999, 12, 31);
+ private static readonly DateTime s_minDateOnly = new DateTime(1, 1, 2);
+
+ public string Id => _id;
+
+ public string DisplayName => _displayName ?? string.Empty;
+
+ public string StandardName => _standardDisplayName ?? string.Empty;
+
+ public string DaylightName => _daylightDisplayName ?? string.Empty;
+
+ public TimeSpan BaseUtcOffset => _baseUtcOffset;
+
+ public bool SupportsDaylightSavingTime => _supportsDaylightSavingTime;
+
+ /// <summary>
+ /// Returns an array of TimeSpan objects representing all of
+ /// possible UTC offset values for this ambiguous time.
+ /// </summary>
+ public TimeSpan[] GetAmbiguousTimeOffsets(DateTimeOffset dateTimeOffset)
+ {
+ if (!SupportsDaylightSavingTime)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeOffsetIsNotAmbiguous, nameof(dateTimeOffset));
+ }
+
+ DateTime adjustedTime = ConvertTime(dateTimeOffset, this).DateTime;
+
+ bool isAmbiguous = false;
+ int? ruleIndex;
+ AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex);
+ if (rule != null && rule.HasDaylightSaving)
+ {
+ DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex);
+ isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime);
+ }
+
+ if (!isAmbiguous)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeOffsetIsNotAmbiguous, nameof(dateTimeOffset));
+ }
+
+ // the passed in dateTime is ambiguous in this TimeZoneInfo instance
+ TimeSpan[] timeSpans = new TimeSpan[2];
+
+ TimeSpan actualUtcOffset = _baseUtcOffset + rule.BaseUtcOffsetDelta;
+
+ // the TimeSpan array must be sorted from least to greatest
+ if (rule.DaylightDelta > TimeSpan.Zero)
+ {
+ timeSpans[0] = actualUtcOffset; // FUTURE: + rule.StandardDelta;
+ timeSpans[1] = actualUtcOffset + rule.DaylightDelta;
+ }
+ else
+ {
+ timeSpans[0] = actualUtcOffset + rule.DaylightDelta;
+ timeSpans[1] = actualUtcOffset; // FUTURE: + rule.StandardDelta;
+ }
+ return timeSpans;
+ }
+
+ /// <summary>
+ /// Returns an array of TimeSpan objects representing all of
+ /// possible UTC offset values for this ambiguous time.
+ /// </summary>
+ public TimeSpan[] GetAmbiguousTimeOffsets(DateTime dateTime)
+ {
+ if (!SupportsDaylightSavingTime)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeIsNotAmbiguous, nameof(dateTime));
+ }
+
+ DateTime adjustedTime;
+ if (dateTime.Kind == DateTimeKind.Local)
+ {
+ CachedData cachedData = s_cachedData;
+ adjustedTime = ConvertTime(dateTime, cachedData.Local, this, TimeZoneInfoOptions.None, cachedData);
+ }
+ else if (dateTime.Kind == DateTimeKind.Utc)
+ {
+ CachedData cachedData = s_cachedData;
+ adjustedTime = ConvertTime(dateTime, s_utcTimeZone, this, TimeZoneInfoOptions.None, cachedData);
+ }
+ else
+ {
+ adjustedTime = dateTime;
+ }
+
+ bool isAmbiguous = false;
+ int? ruleIndex;
+ AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex);
+ if (rule != null && rule.HasDaylightSaving)
+ {
+ DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex);
+ isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime);
+ }
+
+ if (!isAmbiguous)
+ {
+ throw new ArgumentException(SR.Argument_DateTimeIsNotAmbiguous, nameof(dateTime));
+ }
+
+ // the passed in dateTime is ambiguous in this TimeZoneInfo instance
+ TimeSpan[] timeSpans = new TimeSpan[2];
+ TimeSpan actualUtcOffset = _baseUtcOffset + rule.BaseUtcOffsetDelta;
+
+ // the TimeSpan array must be sorted from least to greatest
+ if (rule.DaylightDelta > TimeSpan.Zero)
+ {
+ timeSpans[0] = actualUtcOffset; // FUTURE: + rule.StandardDelta;
+ timeSpans[1] = actualUtcOffset + rule.DaylightDelta;
+ }
+ else
+ {
+ timeSpans[0] = actualUtcOffset + rule.DaylightDelta;
+ timeSpans[1] = actualUtcOffset; // FUTURE: + rule.StandardDelta;
+ }
+ return timeSpans;
+ }
+
+ // note the time is already adjusted
+ private AdjustmentRule GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTime, out int? ruleIndex)
+ {
+ AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex);
+ if (rule != null && rule.NoDaylightTransitions && !rule.HasDaylightSaving)
+ {
+ // When using NoDaylightTransitions rules, each rule is only for one offset.
+ // When looking for the Daylight savings rules, and we found the non-DST rule,
+ // then we get the rule right before this rule.
+ return GetPreviousAdjustmentRule(rule, ruleIndex);
+ }
+
+ return rule;
+ }
+
+ /// <summary>
+ /// Gets the AdjustmentRule that is immediately preceding the specified rule.
+ /// If the specified rule is the first AdjustmentRule, or it isn't in _adjustmentRules,
+ /// then the specified rule is returned.
+ /// </summary>
+ private AdjustmentRule GetPreviousAdjustmentRule(AdjustmentRule rule, int? ruleIndex)
+ {
+ Debug.Assert(rule.NoDaylightTransitions, "GetPreviousAdjustmentRule should only be used with NoDaylightTransitions rules.");
+
+ if (ruleIndex.HasValue && 0 < ruleIndex.Value && ruleIndex.Value < _adjustmentRules.Length)
+ {
+ return _adjustmentRules[ruleIndex.Value - 1];
+ }
+
+ AdjustmentRule result = rule;
+ for (int i = 1; i < _adjustmentRules.Length; i++)
+ {
+ // use ReferenceEquals here instead of AdjustmentRule.Equals because
+ // ReferenceEquals is much faster. This is safe because all the callers
+ // of GetPreviousAdjustmentRule pass in a rule that was retrieved from
+ // _adjustmentRules. A different approach will be needed if this ever changes.
+ if (ReferenceEquals(rule, _adjustmentRules[i]))
+ {
+ result = _adjustmentRules[i - 1];
+ break;
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance.
+ /// </summary>
+ public TimeSpan GetUtcOffset(DateTimeOffset dateTimeOffset) =>
+ GetUtcOffsetFromUtc(dateTimeOffset.UtcDateTime, this);
+
+ /// <summary>
+ /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance.
+ /// </summary>
+ public TimeSpan GetUtcOffset(DateTime dateTime) =>
+ GetUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime, s_cachedData);
+
+ // Shortcut for TimeZoneInfo.Local.GetUtcOffset
+ internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
+ {
+ CachedData cachedData = s_cachedData;
+ return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData);
+ }
+
+ /// <summary>
+ /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance.
+ /// </summary>
+ internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) =>
+ GetUtcOffset(dateTime, flags, s_cachedData);
+
+ private TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags, CachedData cachedData)
+ {
+ if (dateTime.Kind == DateTimeKind.Local)
+ {
+ if (cachedData.GetCorrespondingKind(this) != DateTimeKind.Local)
+ {
+ //
+ // normal case of converting from Local to Utc and then getting the offset from the UTC DateTime
+ //
+ DateTime adjustedTime = ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, flags);
+ return GetUtcOffsetFromUtc(adjustedTime, this);
+ }
+
+ //
+ // Fall through for TimeZoneInfo.Local.GetUtcOffset(date)
+ // to handle an edge case with Invalid-Times for DateTime formatting:
+ //
+ // Consider the invalid PST time "2007-03-11T02:00:00.0000000-08:00"
+ //
+ // By directly calling GetUtcOffset instead of converting to UTC and then calling GetUtcOffsetFromUtc
+ // the correct invalid offset of "-08:00" is returned. In the normal case of converting to UTC as an
+ // interim-step, the invalid time is adjusted into a *valid* UTC time which causes a change in output:
+ //
+ // 1) invalid PST time "2007-03-11T02:00:00.0000000-08:00"
+ // 2) converted to UTC "2007-03-11T10:00:00.0000000Z"
+ // 3) offset returned "2007-03-11T03:00:00.0000000-07:00"
+ //
+ }
+ else if (dateTime.Kind == DateTimeKind.Utc)
+ {
+ if (cachedData.GetCorrespondingKind(this) == DateTimeKind.Utc)
+ {
+ return _baseUtcOffset;
+ }
+ else
+ {
+ //
+ // passing in a UTC dateTime to a non-UTC TimeZoneInfo instance is a
+ // special Loss-Less case.
+ //
+ return GetUtcOffsetFromUtc(dateTime, this);
+ }
+ }
+
+ return GetUtcOffset(dateTime, this, flags);
+ }
+
+ /// <summary>
+ /// Returns true if the time is during the ambiguous time period
+ /// for the current TimeZoneInfo instance.
+ /// </summary>
+ public bool IsAmbiguousTime(DateTimeOffset dateTimeOffset)
+ {
+ if (!_supportsDaylightSavingTime)
+ {
+ return false;
+ }
+
+ DateTimeOffset adjustedTime = ConvertTime(dateTimeOffset, this);
+ return IsAmbiguousTime(adjustedTime.DateTime);
+ }
+
+ /// <summary>
+ /// Returns true if the time is during the ambiguous time period
+ /// for the current TimeZoneInfo instance.
+ /// </summary>
+ public bool IsAmbiguousTime(DateTime dateTime) =>
+ IsAmbiguousTime(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+
+ /// <summary>
+ /// Returns true if the time is during the ambiguous time period
+ /// for the current TimeZoneInfo instance.
+ /// </summary>
+ internal bool IsAmbiguousTime(DateTime dateTime, TimeZoneInfoOptions flags)
+ {
+ if (!_supportsDaylightSavingTime)
+ {
+ return false;
+ }
+
+ CachedData cachedData = s_cachedData;
+ DateTime adjustedTime =
+ dateTime.Kind == DateTimeKind.Local ? ConvertTime(dateTime, cachedData.Local, this, flags, cachedData) :
+ dateTime.Kind == DateTimeKind.Utc ? ConvertTime(dateTime, s_utcTimeZone, this, flags, cachedData) :
+ dateTime;
+
+ int? ruleIndex;
+ AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex);
+ if (rule != null && rule.HasDaylightSaving)
+ {
+ DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex);
+ return GetIsAmbiguousTime(adjustedTime, rule, daylightTime);
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance.
+ /// </summary>
+ public bool IsDaylightSavingTime(DateTimeOffset dateTimeOffset)
+ {
+ bool isDaylightSavingTime;
+ GetUtcOffsetFromUtc(dateTimeOffset.UtcDateTime, this, out isDaylightSavingTime);
+ return isDaylightSavingTime;
+ }
+
+ /// <summary>
+ /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance.
+ /// </summary>
+ public bool IsDaylightSavingTime(DateTime dateTime) =>
+ IsDaylightSavingTime(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime, s_cachedData);
+
+ /// <summary>
+ /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance.
+ /// </summary>
+ internal bool IsDaylightSavingTime(DateTime dateTime, TimeZoneInfoOptions flags) =>
+ IsDaylightSavingTime(dateTime, flags, s_cachedData);
+
+ private bool IsDaylightSavingTime(DateTime dateTime, TimeZoneInfoOptions flags, CachedData cachedData)
+ {
+ //
+ // dateTime.Kind is UTC, then time will be converted from UTC
+ // into current instance's timezone
+ // dateTime.Kind is Local, then time will be converted from Local
+ // into current instance's timezone
+ // dateTime.Kind is UnSpecified, then time is already in
+ // current instance's timezone
+ //
+ // Our DateTime handles ambiguous times, (one is in the daylight and
+ // one is in standard.) If a new DateTime is constructed during ambiguous
+ // time, it is defaulted to "Standard" (i.e. this will return false).
+ // For Invalid times, we will return false
+
+ if (!_supportsDaylightSavingTime || _adjustmentRules == null)
+ {
+ return false;
+ }
+
+ DateTime adjustedTime;
+ //
+ // handle any Local/Utc special cases...
+ //
+ if (dateTime.Kind == DateTimeKind.Local)
+ {
+ adjustedTime = ConvertTime(dateTime, cachedData.Local, this, flags, cachedData);
+ }
+ else if (dateTime.Kind == DateTimeKind.Utc)
+ {
+ if (cachedData.GetCorrespondingKind(this) == DateTimeKind.Utc)
+ {
+ // simple always false case: TimeZoneInfo.Utc.IsDaylightSavingTime(dateTime, flags);
+ return false;
+ }
+ else
+ {
+ //
+ // passing in a UTC dateTime to a non-UTC TimeZoneInfo instance is a
+ // special Loss-Less case.
+ //
+ bool isDaylightSavings;
+ GetUtcOffsetFromUtc(dateTime, this, out isDaylightSavings);
+ return isDaylightSavings;
+ }
+ }
+ else
+ {
+ adjustedTime = dateTime;
+ }
+
+ //
+ // handle the normal cases...
+ //
+ int? ruleIndex;
+ AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex);
+ if (rule != null && rule.HasDaylightSaving)
+ {
+ DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex);
+ return GetIsDaylightSavings(adjustedTime, rule, daylightTime, flags);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Returns true when dateTime falls into a "hole in time".
+ /// </summary>
+ public bool IsInvalidTime(DateTime dateTime)
+ {
+ bool isInvalid = false;
+
+ if ((dateTime.Kind == DateTimeKind.Unspecified) ||
+ (dateTime.Kind == DateTimeKind.Local && s_cachedData.GetCorrespondingKind(this) == DateTimeKind.Local))
+ {
+ // only check Unspecified and (Local when this TimeZoneInfo instance is Local)
+ int? ruleIndex;
+ AdjustmentRule rule = GetAdjustmentRuleForTime(dateTime, out ruleIndex);
+
+ if (rule != null && rule.HasDaylightSaving)
+ {
+ DaylightTimeStruct daylightTime = GetDaylightTime(dateTime.Year, rule, ruleIndex);
+ isInvalid = GetIsInvalidTime(dateTime, rule, daylightTime);
+ }
+ else
+ {
+ isInvalid = false;
+ }
+ }
+
+ return isInvalid;
+ }
+
+ /// <summary>
+ /// Clears data from static members.
+ /// </summary>
+ public static void ClearCachedData()
+ {
+ // Clear a fresh instance of cached data
+ s_cachedData = new CachedData();
+ }
+
+ /// <summary>
+ /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone.
+ /// </summary>
+ public static DateTimeOffset ConvertTimeBySystemTimeZoneId(DateTimeOffset dateTimeOffset, string destinationTimeZoneId) =>
+ ConvertTime(dateTimeOffset, FindSystemTimeZoneById(destinationTimeZoneId));
+
+ /// <summary>
+ /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone.
+ /// </summary>
+ public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string destinationTimeZoneId) =>
+ ConvertTime(dateTime, FindSystemTimeZoneById(destinationTimeZoneId));
+
+ /// <summary>
+ /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone.
+ /// </summary>
+ public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
+ {
+ if (dateTime.Kind == DateTimeKind.Local && string.Equals(sourceTimeZoneId, Local.Id, StringComparison.OrdinalIgnoreCase))
+ {
+ // TimeZoneInfo.Local can be cleared by another thread calling TimeZoneInfo.ClearCachedData.
+ // Take snapshot of cached data to guarantee this method will not be impacted by the ClearCachedData call.
+ // Without the snapshot, there is a chance that ConvertTime will throw since 'source' won't
+ // be reference equal to the new TimeZoneInfo.Local
+ //
+ CachedData cachedData = s_cachedData;
+ return ConvertTime(dateTime, cachedData.Local, FindSystemTimeZoneById(destinationTimeZoneId), TimeZoneInfoOptions.None, cachedData);
+ }
+ else if (dateTime.Kind == DateTimeKind.Utc && string.Equals(sourceTimeZoneId, Utc.Id, StringComparison.OrdinalIgnoreCase))
+ {
+ return ConvertTime(dateTime, s_utcTimeZone, FindSystemTimeZoneById(destinationTimeZoneId), TimeZoneInfoOptions.None, s_cachedData);
+ }
+ else
+ {
+ return ConvertTime(dateTime, FindSystemTimeZoneById(sourceTimeZoneId), FindSystemTimeZoneById(destinationTimeZoneId));
+ }
+ }
+
+ /// <summary>
+ /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone
+ /// </summary>
+ public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
+ {
+ if (destinationTimeZone == null)
+ {
+ throw new ArgumentNullException(nameof(destinationTimeZone));
+ }
+
+ // calculate the destination time zone offset
+ DateTime utcDateTime = dateTimeOffset.UtcDateTime;
+ TimeSpan destinationOffset = GetUtcOffsetFromUtc(utcDateTime, destinationTimeZone);
+
+ // check for overflow
+ long ticks = utcDateTime.Ticks + destinationOffset.Ticks;
+
+ return
+ ticks > DateTimeOffset.MaxValue.Ticks ? DateTimeOffset.MaxValue :
+ ticks < DateTimeOffset.MinValue.Ticks ? DateTimeOffset.MinValue :
+ new DateTimeOffset(ticks, destinationOffset);
+ }
+
+ /// <summary>
+ /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone
+ /// </summary>
+ public static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo destinationTimeZone)
+ {
+ if (destinationTimeZone == null)
+ {
+ throw new ArgumentNullException(nameof(destinationTimeZone));
+ }
+
+ // Special case to give a way clearing the cache without exposing ClearCachedData()
+ if (dateTime.Ticks == 0)
+ {
+ ClearCachedData();
+ }
+ CachedData cachedData = s_cachedData;
+ TimeZoneInfo sourceTimeZone = dateTime.Kind == DateTimeKind.Utc ? s_utcTimeZone : cachedData.Local;
+ return ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, cachedData);
+ }
+
+ /// <summary>
+ /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone
+ /// </summary>
+ public static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone) =>
+ ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, s_cachedData);
+
+ /// <summary>
+ /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone
+ /// </summary>
+ internal static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone, TimeZoneInfoOptions flags) =>
+ ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, flags, s_cachedData);
+
+ private static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone, TimeZoneInfoOptions flags, CachedData cachedData)
+ {
+ if (sourceTimeZone == null)
+ {
+ throw new ArgumentNullException(nameof(sourceTimeZone));
+ }
+
+ if (destinationTimeZone == null)
+ {
+ throw new ArgumentNullException(nameof(destinationTimeZone));
+ }
+
+ DateTimeKind sourceKind = cachedData.GetCorrespondingKind(sourceTimeZone);
+ if (((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) && (dateTime.Kind != DateTimeKind.Unspecified) && (dateTime.Kind != sourceKind))
+ {
+ throw new ArgumentException(SR.Argument_ConvertMismatch, nameof(sourceTimeZone));
+ }
+
+ //
+ // check to see if the DateTime is in an invalid time range. This check
+ // requires the current AdjustmentRule and DaylightTime - which are also
+ // needed to calculate 'sourceOffset' in the normal conversion case.
+ // By calculating the 'sourceOffset' here we improve the
+ // performance for the normal case at the expense of the 'ArgumentException'
+ // case and Loss-less Local special cases.
+ //
+ int? sourceRuleIndex;
+ AdjustmentRule sourceRule = sourceTimeZone.GetAdjustmentRuleForTime(dateTime, out sourceRuleIndex);
+ TimeSpan sourceOffset = sourceTimeZone.BaseUtcOffset;
+
+ if (sourceRule != null)
+ {
+ sourceOffset = sourceOffset + sourceRule.BaseUtcOffsetDelta;
+ if (sourceRule.HasDaylightSaving)
+ {
+ bool sourceIsDaylightSavings = false;
+ DaylightTimeStruct sourceDaylightTime = sourceTimeZone.GetDaylightTime(dateTime.Year, sourceRule, sourceRuleIndex);
+
+ // 'dateTime' might be in an invalid time range since it is in an AdjustmentRule
+ // period that supports DST
+ if (((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) && GetIsInvalidTime(dateTime, sourceRule, sourceDaylightTime))
+ {
+ throw new ArgumentException(SR.Argument_DateTimeIsInvalid, nameof(dateTime));
+ }
+ sourceIsDaylightSavings = GetIsDaylightSavings(dateTime, sourceRule, sourceDaylightTime, flags);
+
+ // adjust the sourceOffset according to the Adjustment Rule / Daylight Saving Rule
+ sourceOffset += (sourceIsDaylightSavings ? sourceRule.DaylightDelta : TimeSpan.Zero /*FUTURE: sourceRule.StandardDelta*/);
+ }
+ }
+
+ DateTimeKind targetKind = cachedData.GetCorrespondingKind(destinationTimeZone);
+
+ // handle the special case of Loss-less Local->Local and UTC->UTC)
+ if (dateTime.Kind != DateTimeKind.Unspecified && sourceKind != DateTimeKind.Unspecified && sourceKind == targetKind)
+ {
+ return dateTime;
+ }
+
+ long utcTicks = dateTime.Ticks - sourceOffset.Ticks;
+
+ // handle the normal case by converting from 'source' to UTC and then to 'target'
+ bool isAmbiguousLocalDst;
+ DateTime targetConverted = ConvertUtcToTimeZone(utcTicks, destinationTimeZone, out isAmbiguousLocalDst);
+
+ if (targetKind == DateTimeKind.Local)
+ {
+ // Because the ticks conversion between UTC and local is lossy, we need to capture whether the
+ // time is in a repeated hour so that it can be passed to the DateTime constructor.
+ return new DateTime(targetConverted.Ticks, DateTimeKind.Local, isAmbiguousLocalDst);
+ }
+ else
+ {
+ return new DateTime(targetConverted.Ticks, targetKind);
+ }
+ }
+
+ /// <summary>
+ /// Converts the value of a DateTime object from Coordinated Universal Time (UTC) to the destinationTimeZone.
+ /// </summary>
+ public static DateTime ConvertTimeFromUtc(DateTime dateTime, TimeZoneInfo destinationTimeZone) =>
+ ConvertTime(dateTime, s_utcTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, s_cachedData);
+
+ /// <summary>
+ /// Converts the value of a DateTime object to Coordinated Universal Time (UTC).
+ /// </summary>
+ public static DateTime ConvertTimeToUtc(DateTime dateTime)
+ {
+ if (dateTime.Kind == DateTimeKind.Utc)
+ {
+ return dateTime;
+ }
+ CachedData cachedData = s_cachedData;
+ return ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, TimeZoneInfoOptions.None, cachedData);
+ }
+
+ /// <summary>
+ /// Converts the value of a DateTime object to Coordinated Universal Time (UTC).
+ /// </summary>
+ internal static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
+ {
+ if (dateTime.Kind == DateTimeKind.Utc)
+ {
+ return dateTime;
+ }
+ CachedData cachedData = s_cachedData;
+ return ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, flags, cachedData);
+ }
+
+ /// <summary>
+ /// Converts the value of a DateTime object to Coordinated Universal Time (UTC).
+ /// </summary>
+ public static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfo sourceTimeZone) =>
+ ConvertTime(dateTime, sourceTimeZone, s_utcTimeZone, TimeZoneInfoOptions.None, s_cachedData);
+
+ /// <summary>
+ /// Returns value equality. Equals does not compare any localizable
+ /// String objects (DisplayName, StandardName, DaylightName).
+ /// </summary>
+ public bool Equals(TimeZoneInfo other) =>
+ other != null &&
+ string.Equals(_id, other._id, StringComparison.OrdinalIgnoreCase) &&
+ HasSameRules(other);
+
+ public override bool Equals(object obj) => Equals(obj as TimeZoneInfo);
+
+ public static TimeZoneInfo FromSerializedString(string source)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+ if (source.Length == 0)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidSerializedString, source), nameof(source));
+ }
+
+ return StringSerializer.GetDeserializedTimeZoneInfo(source);
+ }
+
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(_id);
+
+ /// <summary>
+ /// Returns a ReadOnlyCollection<TimeZoneInfo> containing all valid TimeZone's
+ /// from the local machine. The entries in the collection are sorted by
+ /// <see cref="DisplayName"/>.
+ /// This method does *not* throw TimeZoneNotFoundException or InvalidTimeZoneException.
+ /// </summary>
+ public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones()
+ {
+ CachedData cachedData = s_cachedData;
+
+ lock (cachedData)
+ {
+ if (cachedData._readOnlySystemTimeZones == null)
+ {
+ PopulateAllSystemTimeZones(cachedData);
+ cachedData._allSystemTimeZonesRead = true;
+
+ List<TimeZoneInfo> list;
+ if (cachedData._systemTimeZones != null)
+ {
+ // return a collection of the cached system time zones
+ list = new List<TimeZoneInfo>(cachedData._systemTimeZones.Values);
+ }
+ else
+ {
+ // return an empty collection
+ list = new List<TimeZoneInfo>();
+ }
+
+ // sort and copy the TimeZoneInfo's into a ReadOnlyCollection for the user
+ list.Sort((x, y) =>
+ {
+ // sort by BaseUtcOffset first and by DisplayName second - this is similar to the Windows Date/Time control panel
+ int comparison = x.BaseUtcOffset.CompareTo(y.BaseUtcOffset);
+ return comparison == 0 ? string.CompareOrdinal(x.DisplayName, y.DisplayName) : comparison;
+ });
+
+ cachedData._readOnlySystemTimeZones = new ReadOnlyCollection<TimeZoneInfo>(list);
+ }
+ }
+ return cachedData._readOnlySystemTimeZones;
+ }
+
+ /// <summary>
+ /// Value equality on the "adjustmentRules" array
+ /// </summary>
+ public bool HasSameRules(TimeZoneInfo other)
+ {
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
+ }
+
+ // check the utcOffset and supportsDaylightSavingTime members
+ if (_baseUtcOffset != other._baseUtcOffset ||
+ _supportsDaylightSavingTime != other._supportsDaylightSavingTime)
+ {
+ return false;
+ }
+
+ bool sameRules;
+ AdjustmentRule[] currentRules = _adjustmentRules;
+ AdjustmentRule[] otherRules = other._adjustmentRules;
+
+ sameRules =
+ (currentRules == null && otherRules == null) ||
+ (currentRules != null && otherRules != null);
+
+ if (!sameRules)
+ {
+ // AdjustmentRule array mismatch
+ return false;
+ }
+
+ if (currentRules != null)
+ {
+ if (currentRules.Length != otherRules.Length)
+ {
+ // AdjustmentRule array length mismatch
+ return false;
+ }
+
+ for (int i = 0; i < currentRules.Length; i++)
+ {
+ if (!(currentRules[i]).Equals(otherRules[i]))
+ {
+ // AdjustmentRule value-equality mismatch
+ return false;
+ }
+ }
+ }
+ return sameRules;
+ }
+
+ /// <summary>
+ /// Returns a TimeZoneInfo instance that represents the local time on the machine.
+ /// Accessing this property may throw InvalidTimeZoneException or COMException
+ /// if the machine is in an unstable or corrupt state.
+ /// </summary>
+ public static TimeZoneInfo Local => s_cachedData.Local;
+
+ //
+ // ToSerializedString -
+ //
+ // "TimeZoneInfo" := TimeZoneInfo Data;[AdjustmentRule Data 1];...;[AdjustmentRule Data N]
+ //
+ // "TimeZoneInfo Data" := <_id>;<_baseUtcOffset>;<_displayName>;
+ // <_standardDisplayName>;<_daylightDispayName>;
+ //
+ // "AdjustmentRule Data" := <DateStart>;<DateEnd>;<DaylightDelta>;
+ // [TransitionTime Data DST Start]
+ // [TransitionTime Data DST End]
+ //
+ // "TransitionTime Data" += <DaylightStartTimeOfDat>;<Month>;<Week>;<DayOfWeek>;<Day>
+ //
+ public string ToSerializedString() => StringSerializer.GetSerializedString(this);
+
+ /// <summary>
+ /// Returns the <see cref="DisplayName"/>: "(GMT-08:00) Pacific Time (US &amp; Canada); Tijuana"
+ /// </summary>
+ public override string ToString() => DisplayName;
+
+ /// <summary>
+ /// Returns a TimeZoneInfo instance that represents Universal Coordinated Time (UTC)
+ /// </summary>
+ public static TimeZoneInfo Utc => s_utcTimeZone;
+
+ private TimeZoneInfo(
+ string id,
+ TimeSpan baseUtcOffset,
+ string displayName,
+ string standardDisplayName,
+ string daylightDisplayName,
+ AdjustmentRule[] adjustmentRules,
+ bool disableDaylightSavingTime)
+ {
+ bool adjustmentRulesSupportDst;
+ ValidateTimeZoneInfo(id, baseUtcOffset, adjustmentRules, out adjustmentRulesSupportDst);
+
+ _id = id;
+ _baseUtcOffset = baseUtcOffset;
+ _displayName = displayName;
+ _standardDisplayName = standardDisplayName;
+ _daylightDisplayName = disableDaylightSavingTime ? null : daylightDisplayName;
+ _supportsDaylightSavingTime = adjustmentRulesSupportDst && !disableDaylightSavingTime;
+ _adjustmentRules = adjustmentRules;
+ }
+
+ /// <summary>
+ /// Returns a simple TimeZoneInfo instance that does not support Daylight Saving Time.
+ /// </summary>
+ public static TimeZoneInfo CreateCustomTimeZone(
+ string id,
+ TimeSpan baseUtcOffset,
+ string displayName,
+ string standardDisplayName)
+ {
+ return new TimeZoneInfo(
+ id,
+ baseUtcOffset,
+ displayName,
+ standardDisplayName,
+ standardDisplayName,
+ adjustmentRules: null,
+ disableDaylightSavingTime: false);
+ }
+
+ /// <summary>
+ /// Returns a TimeZoneInfo instance that may support Daylight Saving Time.
+ /// </summary>
+ public static TimeZoneInfo CreateCustomTimeZone(
+ string id,
+ TimeSpan baseUtcOffset,
+ string displayName,
+ string standardDisplayName,
+ string daylightDisplayName,
+ AdjustmentRule[] adjustmentRules)
+ {
+ return CreateCustomTimeZone(
+ id,
+ baseUtcOffset,
+ displayName,
+ standardDisplayName,
+ daylightDisplayName,
+ adjustmentRules,
+ disableDaylightSavingTime: false);
+ }
+
+ /// <summary>
+ /// Returns a TimeZoneInfo instance that may support Daylight Saving Time.
+ /// </summary>
+ public static TimeZoneInfo CreateCustomTimeZone(
+ string id,
+ TimeSpan baseUtcOffset,
+ string displayName,
+ string standardDisplayName,
+ string daylightDisplayName,
+ AdjustmentRule[] adjustmentRules,
+ bool disableDaylightSavingTime)
+ {
+ if (!disableDaylightSavingTime && adjustmentRules?.Length > 0)
+ {
+ adjustmentRules = (AdjustmentRule[])adjustmentRules.Clone();
+ }
+
+ return new TimeZoneInfo(
+ id,
+ baseUtcOffset,
+ displayName,
+ standardDisplayName,
+ daylightDisplayName,
+ adjustmentRules,
+ disableDaylightSavingTime);
+ }
+
+ void IDeserializationCallback.OnDeserialization(object sender)
+ {
+ try
+ {
+ bool adjustmentRulesSupportDst;
+ ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out adjustmentRulesSupportDst);
+
+ if (adjustmentRulesSupportDst != _supportsDaylightSavingTime)
+ {
+ throw new SerializationException(SR.Format(SR.Serialization_CorruptField, "SupportsDaylightSavingTime"));
+ }
+ }
+ catch (ArgumentException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+ catch (InvalidTimeZoneException e)
+ {
+ throw new SerializationException(SR.Serialization_InvalidData, e);
+ }
+ }
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ info.AddValue("Id", _id); // Do not rename (binary serialization)
+ info.AddValue("DisplayName", _displayName); // Do not rename (binary serialization)
+ info.AddValue("StandardName", _standardDisplayName); // Do not rename (binary serialization)
+ info.AddValue("DaylightName", _daylightDisplayName); // Do not rename (binary serialization)
+ info.AddValue("BaseUtcOffset", _baseUtcOffset); // Do not rename (binary serialization)
+ info.AddValue("AdjustmentRules", _adjustmentRules); // Do not rename (binary serialization)
+ info.AddValue("SupportsDaylightSavingTime", _supportsDaylightSavingTime); // Do not rename (binary serialization)
+ }
+
+ private TimeZoneInfo(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ _id = (string)info.GetValue("Id", typeof(string)); // Do not rename (binary serialization)
+ _displayName = (string)info.GetValue("DisplayName", typeof(string)); // Do not rename (binary serialization)
+ _standardDisplayName = (string)info.GetValue("StandardName", typeof(string)); // Do not rename (binary serialization)
+ _daylightDisplayName = (string)info.GetValue("DaylightName", typeof(string)); // Do not rename (binary serialization)
+ _baseUtcOffset = (TimeSpan)info.GetValue("BaseUtcOffset", typeof(TimeSpan)); // Do not rename (binary serialization)
+ _adjustmentRules = (AdjustmentRule[])info.GetValue("AdjustmentRules", typeof(AdjustmentRule[])); // Do not rename (binary serialization)
+ _supportsDaylightSavingTime = (bool)info.GetValue("SupportsDaylightSavingTime", typeof(bool)); // Do not rename (binary serialization)
+ }
+
+ private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, out int? ruleIndex)
+ {
+ AdjustmentRule result = GetAdjustmentRuleForTime(dateTime, dateTimeisUtc: false, ruleIndex: out ruleIndex);
+ Debug.Assert(result == null || ruleIndex.HasValue, "If an AdjustmentRule was found, ruleIndex should also be set.");
+
+ return result;
+ }
+
+ private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, bool dateTimeisUtc, out int? ruleIndex)
+ {
+ if (_adjustmentRules == null || _adjustmentRules.Length == 0)
+ {
+ ruleIndex = null;
+ return null;
+ }
+
+ // Only check the whole-date portion of the dateTime for DateTimeKind.Unspecified rules -
+ // This is because the AdjustmentRule DateStart & DateEnd are stored as
+ // Date-only values {4/2/2006 - 10/28/2006} but actually represent the
+ // time span {4/2/2006@00:00:00.00000 - 10/28/2006@23:59:59.99999}
+ DateTime date = dateTimeisUtc ?
+ (dateTime + BaseUtcOffset).Date :
+ dateTime.Date;
+
+ int low = 0;
+ int high = _adjustmentRules.Length - 1;
+
+ while (low <= high)
+ {
+ int median = low + ((high - low) >> 1);
+
+ AdjustmentRule rule = _adjustmentRules[median];
+ AdjustmentRule previousRule = median > 0 ? _adjustmentRules[median - 1] : rule;
+
+ int compareResult = CompareAdjustmentRuleToDateTime(rule, previousRule, dateTime, date, dateTimeisUtc);
+ if (compareResult == 0)
+ {
+ ruleIndex = median;
+ return rule;
+ }
+ else if (compareResult < 0)
+ {
+ low = median + 1;
+ }
+ else
+ {
+ high = median - 1;
+ }
+ }
+
+ ruleIndex = null;
+ return null;
+ }
+
+ /// <summary>
+ /// Determines if 'rule' is the correct AdjustmentRule for the given dateTime.
+ /// </summary>
+ /// <returns>
+ /// A value less than zero if rule is for times before dateTime.
+ /// Zero if rule is correct for dateTime.
+ /// A value greater than zero if rule is for times after dateTime.
+ /// </returns>
+ private int CompareAdjustmentRuleToDateTime(AdjustmentRule rule, AdjustmentRule previousRule,
+ DateTime dateTime, DateTime dateOnly, bool dateTimeisUtc)
+ {
+ bool isAfterStart;
+ if (rule.DateStart.Kind == DateTimeKind.Utc)
+ {
+ DateTime dateTimeToCompare = dateTimeisUtc ?
+ dateTime :
+ // use the previous rule to compute the dateTimeToCompare, since the time daylight savings "switches"
+ // is based on the previous rule's offset
+ ConvertToUtc(dateTime, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta);
+
+ isAfterStart = dateTimeToCompare >= rule.DateStart;
+ }
+ else
+ {
+ // if the rule's DateStart is Unspecified, then use the whole-date portion
+ isAfterStart = dateOnly >= rule.DateStart;
+ }
+
+ if (!isAfterStart)
+ {
+ return 1;
+ }
+
+ bool isBeforeEnd;
+ if (rule.DateEnd.Kind == DateTimeKind.Utc)
+ {
+ DateTime dateTimeToCompare = dateTimeisUtc ?
+ dateTime :
+ ConvertToUtc(dateTime, rule.DaylightDelta, rule.BaseUtcOffsetDelta);
+
+ isBeforeEnd = dateTimeToCompare <= rule.DateEnd;
+ }
+ else
+ {
+ // if the rule's DateEnd is Unspecified, then use the whole-date portion
+ isBeforeEnd = dateOnly <= rule.DateEnd;
+ }
+
+ return isBeforeEnd ? 0 : -1;
+ }
+
+ /// <summary>
+ /// Converts the dateTime to UTC using the specified deltas.
+ /// </summary>
+ private DateTime ConvertToUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta) =>
+ ConvertToFromUtc(dateTime, daylightDelta, baseUtcOffsetDelta, convertToUtc: true);
+
+ /// <summary>
+ /// Converts the dateTime from UTC using the specified deltas.
+ /// </summary>
+ private DateTime ConvertFromUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta) =>
+ ConvertToFromUtc(dateTime, daylightDelta, baseUtcOffsetDelta, convertToUtc: false);
+
+ /// <summary>
+ /// Converts the dateTime to or from UTC using the specified deltas.
+ /// </summary>
+ private DateTime ConvertToFromUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta, bool convertToUtc)
+ {
+ TimeSpan offset = BaseUtcOffset + daylightDelta + baseUtcOffsetDelta;
+ if (convertToUtc)
+ {
+ offset = offset.Negate();
+ }
+
+ long ticks = dateTime.Ticks + offset.Ticks;
+
+ return
+ ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue :
+ ticks < DateTime.MinValue.Ticks ? DateTime.MinValue :
+ new DateTime(ticks);
+ }
+
+ /// <summary>
+ /// Helper function that converts a dateTime from UTC into the destinationTimeZone
+ /// - Returns DateTime.MaxValue when the converted value is too large.
+ /// - Returns DateTime.MinValue when the converted value is too small.
+ /// </summary>
+ private static DateTime ConvertUtcToTimeZone(long ticks, TimeZoneInfo destinationTimeZone, out bool isAmbiguousLocalDst)
+ {
+ // used to calculate the UTC offset in the destinationTimeZone
+ DateTime utcConverted =
+ ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue :
+ ticks < DateTime.MinValue.Ticks ? DateTime.MinValue :
+ new DateTime(ticks);
+
+ // verify the time is between MinValue and MaxValue in the new time zone
+ TimeSpan offset = GetUtcOffsetFromUtc(utcConverted, destinationTimeZone, out isAmbiguousLocalDst);
+ ticks += offset.Ticks;
+
+ return
+ ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue :
+ ticks < DateTime.MinValue.Ticks ? DateTime.MinValue :
+ new DateTime(ticks);
+ }
+
+ /// <summary>
+ /// Helper function that returns a DaylightTime from a year and AdjustmentRule.
+ /// </summary>
+ private DaylightTimeStruct GetDaylightTime(int year, AdjustmentRule rule, int? ruleIndex)
+ {
+ TimeSpan delta = rule.DaylightDelta;
+ DateTime startTime;
+ DateTime endTime;
+ if (rule.NoDaylightTransitions)
+ {
+ // NoDaylightTransitions rules don't use DaylightTransition Start and End, instead
+ // the DateStart and DateEnd are UTC times that represent when daylight savings time changes.
+ // Convert the UTC times into adjusted time zone times.
+
+ // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule
+ AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex);
+ startTime = ConvertFromUtc(rule.DateStart, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta);
+
+ endTime = ConvertFromUtc(rule.DateEnd, rule.DaylightDelta, rule.BaseUtcOffsetDelta);
+ }
+ else
+ {
+ startTime = TransitionTimeToDateTime(year, rule.DaylightTransitionStart);
+ endTime = TransitionTimeToDateTime(year, rule.DaylightTransitionEnd);
+ }
+ return new DaylightTimeStruct(startTime, endTime, delta);
+ }
+
+ /// <summary>
+ /// Helper function that checks if a given dateTime is in Daylight Saving Time (DST).
+ /// This function assumes the dateTime and AdjustmentRule are both in the same time zone.
+ /// </summary>
+ private static bool GetIsDaylightSavings(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime, TimeZoneInfoOptions flags)
+ {
+ if (rule == null)
+ {
+ return false;
+ }
+
+ DateTime startTime;
+ DateTime endTime;
+
+ if (time.Kind == DateTimeKind.Local)
+ {
+ // startTime and endTime represent the period from either the start of
+ // DST to the end and ***includes*** the potentially overlapped times
+ startTime = rule.IsStartDateMarkerForBeginningOfYear() ?
+ new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) :
+ daylightTime.Start + daylightTime.Delta;
+
+ endTime = rule.IsEndDateMarkerForEndOfYear() ?
+ new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) :
+ daylightTime.End;
+ }
+ else
+ {
+ // startTime and endTime represent the period from either the start of DST to the end and
+ // ***does not include*** the potentially overlapped times
+ //
+ // -=-=-=-=-=- Pacific Standard Time -=-=-=-=-=-=-
+ // April 2, 2006 October 29, 2006
+ // 2AM 3AM 1AM 2AM
+ // | +1 hr | | -1 hr |
+ // | <invalid time> | | <ambiguous time> |
+ // [========== DST ========>)
+ //
+ // -=-=-=-=-=- Some Weird Time Zone -=-=-=-=-=-=-
+ // April 2, 2006 October 29, 2006
+ // 1AM 2AM 2AM 3AM
+ // | -1 hr | | +1 hr |
+ // | <ambiguous time> | | <invalid time> |
+ // [======== DST ========>)
+ //
+ bool invalidAtStart = rule.DaylightDelta > TimeSpan.Zero;
+
+ startTime = rule.IsStartDateMarkerForBeginningOfYear() ?
+ new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) :
+ daylightTime.Start + (invalidAtStart ? rule.DaylightDelta : TimeSpan.Zero); /* FUTURE: - rule.StandardDelta; */
+
+ endTime = rule.IsEndDateMarkerForEndOfYear() ?
+ new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) :
+ daylightTime.End + (invalidAtStart ? -rule.DaylightDelta : TimeSpan.Zero);
+ }
+
+ bool isDst = CheckIsDst(startTime, time, endTime, false, rule);
+
+ // If this date was previously converted from a UTC date and we were able to detect that the local
+ // DateTime would be ambiguous, this data is stored in the DateTime to resolve this ambiguity.
+ if (isDst && time.Kind == DateTimeKind.Local)
+ {
+ // For normal time zones, the ambiguous hour is the last hour of daylight saving when you wind the
+ // clock back. It is theoretically possible to have a positive delta, (which would really be daylight
+ // reduction time), where you would have to wind the clock back in the begnning.
+ if (GetIsAmbiguousTime(time, rule, daylightTime))
+ {
+ isDst = time.IsAmbiguousDaylightSavingTime();
+ }
+ }
+
+ return isDst;
+ }
+
+ /// <summary>
+ /// Gets the offset that should be used to calculate DST start times from a UTC time.
+ /// </summary>
+ private TimeSpan GetDaylightSavingsStartOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule, int? ruleIndex)
+ {
+ if (rule.NoDaylightTransitions)
+ {
+ // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule
+ AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex);
+ return baseUtcOffset + previousRule.BaseUtcOffsetDelta + previousRule.DaylightDelta;
+ }
+ else
+ {
+ return baseUtcOffset + rule.BaseUtcOffsetDelta; /* FUTURE: + rule.StandardDelta; */
+ }
+ }
+
+ /// <summary>
+ /// Gets the offset that should be used to calculate DST end times from a UTC time.
+ /// </summary>
+ private TimeSpan GetDaylightSavingsEndOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule)
+ {
+ // NOTE: even NoDaylightTransitions rules use this logic since DST ends w.r.t. the current rule
+ return baseUtcOffset + rule.BaseUtcOffsetDelta + rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */
+ }
+
+ /// <summary>
+ /// Helper function that checks if a given dateTime is in Daylight Saving Time (DST).
+ /// This function assumes the dateTime is in UTC and AdjustmentRule is in a different time zone.
+ /// </summary>
+ private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpan utc, AdjustmentRule rule, int? ruleIndex, out bool isAmbiguousLocalDst, TimeZoneInfo zone)
+ {
+ isAmbiguousLocalDst = false;
+
+ if (rule == null)
+ {
+ return false;
+ }
+
+ // Get the daylight changes for the year of the specified time.
+ DaylightTimeStruct daylightTime = zone.GetDaylightTime(year, rule, ruleIndex);
+
+ // The start and end times represent the range of universal times that are in DST for that year.
+ // Within that there is an ambiguous hour, usually right at the end, but at the beginning in
+ // the unusual case of a negative daylight savings delta.
+ // We need to handle the case if the current rule has daylight saving end by the end of year. If so, we need to check if next year starts with daylight saving on
+ // and get the actual daylight saving end time. Here is example for such case:
+ // Converting the UTC datetime "12/31/2011 8:00:00 PM" to "(UTC+03:00) Moscow, St. Petersburg, Volgograd (RTZ 2)" zone.
+ // In 2011 the daylight saving will go through the end of the year. If we use the end of 2011 as the daylight saving end,
+ // that will fail the conversion because the UTC time +4 hours (3 hours for the zone UTC offset and 1 hour for daylight saving) will move us to the next year "1/1/2012 12:00 AM",
+ // checking against the end of 2011 will tell we are not in daylight saving which is wrong and the conversion will be off by one hour.
+ // Note we handle the similar case when rule year start with daylight saving and previous year end with daylight saving.
+
+ bool ignoreYearAdjustment = false;
+ TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule, ruleIndex);
+ DateTime startTime;
+ if (rule.IsStartDateMarkerForBeginningOfYear() && daylightTime.Start.Year > DateTime.MinValue.Year)
+ {
+ int? previousYearRuleIndex;
+ AdjustmentRule previousYearRule = zone.GetAdjustmentRuleForTime(
+ new DateTime(daylightTime.Start.Year - 1, 12, 31),
+ out previousYearRuleIndex);
+ if (previousYearRule != null && previousYearRule.IsEndDateMarkerForEndOfYear())
+ {
+ DaylightTimeStruct previousDaylightTime = zone.GetDaylightTime(
+ daylightTime.Start.Year - 1,
+ previousYearRule,
+ previousYearRuleIndex);
+ startTime = previousDaylightTime.Start - utc - previousYearRule.BaseUtcOffsetDelta;
+ ignoreYearAdjustment = true;
+ }
+ else
+ {
+ startTime = new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) - dstStartOffset;
+ }
+ }
+ else
+ {
+ startTime = daylightTime.Start - dstStartOffset;
+ }
+
+ TimeSpan dstEndOffset = zone.GetDaylightSavingsEndOffsetFromUtc(utc, rule);
+ DateTime endTime;
+ if (rule.IsEndDateMarkerForEndOfYear() && daylightTime.End.Year < DateTime.MaxValue.Year)
+ {
+ int? nextYearRuleIndex;
+ AdjustmentRule nextYearRule = zone.GetAdjustmentRuleForTime(
+ new DateTime(daylightTime.End.Year + 1, 1, 1),
+ out nextYearRuleIndex);
+ if (nextYearRule != null && nextYearRule.IsStartDateMarkerForBeginningOfYear())
+ {
+ if (nextYearRule.IsEndDateMarkerForEndOfYear())
+ {
+ // next year end with daylight saving on too
+ endTime = new DateTime(daylightTime.End.Year + 1, 12, 31) - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta;
+ }
+ else
+ {
+ DaylightTimeStruct nextdaylightTime = zone.GetDaylightTime(
+ daylightTime.End.Year + 1,
+ nextYearRule,
+ nextYearRuleIndex);
+ endTime = nextdaylightTime.End - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta;
+ }
+ ignoreYearAdjustment = true;
+ }
+ else
+ {
+ endTime = new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) - dstEndOffset;
+ }
+ }
+ else
+ {
+ endTime = daylightTime.End - dstEndOffset;
+ }
+
+ DateTime ambiguousStart;
+ DateTime ambiguousEnd;
+ if (daylightTime.Delta.Ticks > 0)
+ {
+ ambiguousStart = endTime - daylightTime.Delta;
+ ambiguousEnd = endTime;
+ }
+ else
+ {
+ ambiguousStart = startTime;
+ ambiguousEnd = startTime - daylightTime.Delta;
+ }
+
+ bool isDst = CheckIsDst(startTime, time, endTime, ignoreYearAdjustment, rule);
+
+ // See if the resulting local time becomes ambiguous. This must be captured here or the
+ // DateTime will not be able to round-trip back to UTC accurately.
+ if (isDst)
+ {
+ isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd);
+
+ if (!isAmbiguousLocalDst && ambiguousStart.Year != ambiguousEnd.Year)
+ {
+ // there exists an extreme corner case where the start or end period is on a year boundary and
+ // because of this the comparison above might have been performed for a year-early or a year-later
+ // than it should have been.
+ DateTime ambiguousStartModified;
+ DateTime ambiguousEndModified;
+ try
+ {
+ ambiguousStartModified = ambiguousStart.AddYears(1);
+ ambiguousEndModified = ambiguousEnd.AddYears(1);
+ isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd);
+ }
+ catch (ArgumentOutOfRangeException) { }
+
+ if (!isAmbiguousLocalDst)
+ {
+ try
+ {
+ ambiguousStartModified = ambiguousStart.AddYears(-1);
+ ambiguousEndModified = ambiguousEnd.AddYears(-1);
+ isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd);
+ }
+ catch (ArgumentOutOfRangeException) { }
+ }
+ }
+ }
+
+ return isDst;
+ }
+
+ private static bool CheckIsDst(DateTime startTime, DateTime time, DateTime endTime, bool ignoreYearAdjustment, AdjustmentRule rule)
+ {
+ // NoDaylightTransitions AdjustmentRules should never get their year adjusted since they adjust the offset for the
+ // entire time period - which may be for multiple years
+ if (!ignoreYearAdjustment && !rule.NoDaylightTransitions)
+ {
+ int startTimeYear = startTime.Year;
+ int endTimeYear = endTime.Year;
+
+ if (startTimeYear != endTimeYear)
+ {
+ endTime = endTime.AddYears(startTimeYear - endTimeYear);
+ }
+
+ int timeYear = time.Year;
+
+ if (startTimeYear != timeYear)
+ {
+ time = time.AddYears(startTimeYear - timeYear);
+ }
+ }
+
+ if (startTime > endTime)
+ {
+ // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year.
+ // Note, the summer in the southern hemisphere begins late in the year.
+ return (time < endTime || time >= startTime);
+ }
+ else if (rule.NoDaylightTransitions)
+ {
+ // In NoDaylightTransitions AdjustmentRules, the startTime is always before the endTime,
+ // and both the start and end times are inclusive
+ return time >= startTime && time <= endTime;
+ }
+ else
+ {
+ // In northern hemisphere, the daylight saving time starts in the middle of the year.
+ return time >= startTime && time < endTime;
+ }
+ }
+
+ /// <summary>
+ /// Returns true when the dateTime falls into an ambiguous time range.
+ ///
+ /// For example, in Pacific Standard Time on Sunday, October 29, 2006 time jumps from
+ /// 2AM to 1AM. This means the timeline on Sunday proceeds as follows:
+ /// 12AM ... [1AM ... 1:59:59AM -> 1AM ... 1:59:59AM] 2AM ... 3AM ...
+ ///
+ /// In this example, any DateTime values that fall into the [1AM - 1:59:59AM] range
+ /// are ambiguous; as it is unclear if these times are in Daylight Saving Time.
+ /// </summary>
+ private static bool GetIsAmbiguousTime(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime)
+ {
+ bool isAmbiguous = false;
+ if (rule == null || rule.DaylightDelta == TimeSpan.Zero)
+ {
+ return isAmbiguous;
+ }
+
+ DateTime startAmbiguousTime;
+ DateTime endAmbiguousTime;
+
+ // if at DST start we transition forward in time then there is an ambiguous time range at the DST end
+ if (rule.DaylightDelta > TimeSpan.Zero)
+ {
+ if (rule.IsEndDateMarkerForEndOfYear())
+ { // year end with daylight on so there is no ambiguous time
+ return false;
+ }
+ startAmbiguousTime = daylightTime.End;
+ endAmbiguousTime = daylightTime.End - rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */
+ }
+ else
+ {
+ if (rule.IsStartDateMarkerForBeginningOfYear())
+ { // year start with daylight on so there is no ambiguous time
+ return false;
+ }
+ startAmbiguousTime = daylightTime.Start;
+ endAmbiguousTime = daylightTime.Start + rule.DaylightDelta; /* FUTURE: - rule.StandardDelta; */
+ }
+
+ isAmbiguous = (time >= endAmbiguousTime && time < startAmbiguousTime);
+
+ if (!isAmbiguous && startAmbiguousTime.Year != endAmbiguousTime.Year)
+ {
+ // there exists an extreme corner case where the start or end period is on a year boundary and
+ // because of this the comparison above might have been performed for a year-early or a year-later
+ // than it should have been.
+ DateTime startModifiedAmbiguousTime;
+ DateTime endModifiedAmbiguousTime;
+ try
+ {
+ startModifiedAmbiguousTime = startAmbiguousTime.AddYears(1);
+ endModifiedAmbiguousTime = endAmbiguousTime.AddYears(1);
+ isAmbiguous = (time >= endModifiedAmbiguousTime && time < startModifiedAmbiguousTime);
+ }
+ catch (ArgumentOutOfRangeException) { }
+
+ if (!isAmbiguous)
+ {
+ try
+ {
+ startModifiedAmbiguousTime = startAmbiguousTime.AddYears(-1);
+ endModifiedAmbiguousTime = endAmbiguousTime.AddYears(-1);
+ isAmbiguous = (time >= endModifiedAmbiguousTime && time < startModifiedAmbiguousTime);
+ }
+ catch (ArgumentOutOfRangeException) { }
+ }
+ }
+ return isAmbiguous;
+ }
+
+ /// <summary>
+ /// Helper function that checks if a given DateTime is in an invalid time ("time hole")
+ /// A "time hole" occurs at a DST transition point when time jumps forward;
+ /// For example, in Pacific Standard Time on Sunday, April 2, 2006 time jumps from
+ /// 1:59:59.9999999 to 3AM. The time range 2AM to 2:59:59.9999999AM is the "time hole".
+ /// A "time hole" is not limited to only occurring at the start of DST, and may occur at
+ /// the end of DST as well.
+ /// </summary>
+ private static bool GetIsInvalidTime(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime)
+ {
+ bool isInvalid = false;
+ if (rule == null || rule.DaylightDelta == TimeSpan.Zero)
+ {
+ return isInvalid;
+ }
+
+ DateTime startInvalidTime;
+ DateTime endInvalidTime;
+
+ // if at DST start we transition forward in time then there is an ambiguous time range at the DST end
+ if (rule.DaylightDelta < TimeSpan.Zero)
+ {
+ // if the year ends with daylight saving on then there cannot be any time-hole's in that year.
+ if (rule.IsEndDateMarkerForEndOfYear())
+ return false;
+
+ startInvalidTime = daylightTime.End;
+ endInvalidTime = daylightTime.End - rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */
+ }
+ else
+ {
+ // if the year starts with daylight saving on then there cannot be any time-hole's in that year.
+ if (rule.IsStartDateMarkerForBeginningOfYear())
+ return false;
+
+ startInvalidTime = daylightTime.Start;
+ endInvalidTime = daylightTime.Start + rule.DaylightDelta; /* FUTURE: - rule.StandardDelta; */
+ }
+
+ isInvalid = (time >= startInvalidTime && time < endInvalidTime);
+
+ if (!isInvalid && startInvalidTime.Year != endInvalidTime.Year)
+ {
+ // there exists an extreme corner case where the start or end period is on a year boundary and
+ // because of this the comparison above might have been performed for a year-early or a year-later
+ // than it should have been.
+ DateTime startModifiedInvalidTime;
+ DateTime endModifiedInvalidTime;
+ try
+ {
+ startModifiedInvalidTime = startInvalidTime.AddYears(1);
+ endModifiedInvalidTime = endInvalidTime.AddYears(1);
+ isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime);
+ }
+ catch (ArgumentOutOfRangeException) { }
+
+ if (!isInvalid)
+ {
+ try
+ {
+ startModifiedInvalidTime = startInvalidTime.AddYears(-1);
+ endModifiedInvalidTime = endInvalidTime.AddYears(-1);
+ isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime);
+ }
+ catch (ArgumentOutOfRangeException) { }
+ }
+ }
+ return isInvalid;
+ }
+
+ /// <summary>
+ /// Helper function that calculates the UTC offset for a dateTime in a timeZone.
+ /// This function assumes that the dateTime is already converted into the timeZone.
+ /// </summary>
+ private static TimeSpan GetUtcOffset(DateTime time, TimeZoneInfo zone, TimeZoneInfoOptions flags)
+ {
+ TimeSpan baseOffset = zone.BaseUtcOffset;
+ int? ruleIndex;
+ AdjustmentRule rule = zone.GetAdjustmentRuleForTime(time, out ruleIndex);
+
+ if (rule != null)
+ {
+ baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
+ if (rule.HasDaylightSaving)
+ {
+ DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule, ruleIndex);
+ bool isDaylightSavings = GetIsDaylightSavings(time, rule, daylightTime, flags);
+ baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
+ }
+ }
+
+ return baseOffset;
+ }
+
+ /// <summary>
+ /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
+ /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
+ /// </summary>
+ private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone)
+ {
+ bool isDaylightSavings;
+ return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings);
+ }
+
+ /// <summary>
+ /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
+ /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
+ /// </summary>
+ private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings)
+ {
+ bool isAmbiguousLocalDst;
+ return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings, out isAmbiguousLocalDst);
+ }
+
+ /// <summary>
+ /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
+ /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
+ /// </summary>
+ internal static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings, out bool isAmbiguousLocalDst)
+ {
+ isDaylightSavings = false;
+ isAmbiguousLocalDst = false;
+ TimeSpan baseOffset = zone.BaseUtcOffset;
+ int year;
+ int? ruleIndex;
+ AdjustmentRule rule;
+
+ if (time > s_maxDateOnly)
+ {
+ rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue, out ruleIndex);
+ year = 9999;
+ }
+ else if (time < s_minDateOnly)
+ {
+ rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue, out ruleIndex);
+ year = 1;
+ }
+ else
+ {
+ rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true, ruleIndex: out ruleIndex);
+ Debug.Assert(rule == null || ruleIndex.HasValue,
+ "If GetAdjustmentRuleForTime returned an AdjustmentRule, ruleIndex should also be set.");
+
+ // As we get the associated rule using the adjusted targetTime, we should use the adjusted year (targetTime.Year) too as after adding the baseOffset,
+ // sometimes the year value can change if the input datetime was very close to the beginning or the end of the year. Examples of such cases:
+ // Libya Standard Time when used with the date 2011-12-31T23:59:59.9999999Z
+ // "W. Australia Standard Time" used with date 2005-12-31T23:59:00.0000000Z
+ DateTime targetTime = time + baseOffset;
+ year = targetTime.Year;
+ }
+
+ if (rule != null)
+ {
+ baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
+ if (rule.HasDaylightSaving)
+ {
+ isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, ruleIndex, out isAmbiguousLocalDst, zone);
+ baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
+ }
+ }
+
+ return baseOffset;
+ }
+
+ /// <summary>
+ /// Helper function that converts a year and TransitionTime into a DateTime.
+ /// </summary>
+ internal static DateTime TransitionTimeToDateTime(int year, TransitionTime transitionTime)
+ {
+ DateTime value;
+ DateTime timeOfDay = transitionTime.TimeOfDay;
+
+ if (transitionTime.IsFixedDateRule)
+ {
+ // create a DateTime from the passed in year and the properties on the transitionTime
+
+ // if the day is out of range for the month then use the last day of the month
+ int day = DateTime.DaysInMonth(year, transitionTime.Month);
+
+ value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day,
+ timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
+ }
+ else
+ {
+ if (transitionTime.Week <= 4)
+ {
+ //
+ // Get the (transitionTime.Week)th Sunday.
+ //
+ value = new DateTime(year, transitionTime.Month, 1,
+ timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
+
+ int dayOfWeek = (int)value.DayOfWeek;
+ int delta = (int)transitionTime.DayOfWeek - dayOfWeek;
+ if (delta < 0)
+ {
+ delta += 7;
+ }
+ delta += 7 * (transitionTime.Week - 1);
+
+ if (delta > 0)
+ {
+ value = value.AddDays(delta);
+ }
+ }
+ else
+ {
+ //
+ // If TransitionWeek is greater than 4, we will get the last week.
+ //
+ int daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month);
+ value = new DateTime(year, transitionTime.Month, daysInMonth,
+ timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
+
+ // This is the day of week for the last day of the month.
+ int dayOfWeek = (int)value.DayOfWeek;
+ int delta = dayOfWeek - (int)transitionTime.DayOfWeek;
+ if (delta < 0)
+ {
+ delta += 7;
+ }
+
+ if (delta > 0)
+ {
+ value = value.AddDays(-delta);
+ }
+ }
+ }
+ return value;
+ }
+
+ /// <summary>
+ /// Helper function for retrieving a TimeZoneInfo object by <time_zone_name>.
+ ///
+ /// This function may return null.
+ ///
+ /// assumes cachedData lock is taken
+ /// </summary>
+ private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, out TimeZoneInfo value, out Exception e, CachedData cachedData, bool alwaysFallbackToLocalMachine = false)
+ {
+ Debug.Assert(Monitor.IsEntered(cachedData));
+
+ TimeZoneInfoResult result = TimeZoneInfoResult.Success;
+ e = null;
+ TimeZoneInfo match = null;
+
+ // check the cache
+ if (cachedData._systemTimeZones != null)
+ {
+ if (cachedData._systemTimeZones.TryGetValue(id, out match))
+ {
+ if (dstDisabled && match._supportsDaylightSavingTime)
+ {
+ // we found a cache hit but we want a time zone without DST and this one has DST data
+ value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName);
+ }
+ else
+ {
+ value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName,
+ match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false);
+ }
+ return result;
+ }
+ }
+
+ // Fall back to reading from the local machine when the cache is not fully populated.
+ // On UNIX, there may be some tzfiles that aren't in the zones.tab file, and thus aren't returned from GetSystemTimeZones().
+ // If a caller asks for one of these zones before calling GetSystemTimeZones(), the time zone is returned successfully. But if
+ // GetSystemTimeZones() is called first, FindSystemTimeZoneById will throw TimeZoneNotFoundException, which is inconsistent.
+ // To fix this, when 'alwaysFallbackToLocalMachine' is true, even if _allSystemTimeZonesRead is true, try reading the tzfile
+ // from disk, but don't add the time zone to the list returned from GetSystemTimeZones(). These time zones will only be
+ // available if asked for directly.
+ if (!cachedData._allSystemTimeZonesRead || alwaysFallbackToLocalMachine)
+ {
+ result = TryGetTimeZoneFromLocalMachine(id, dstDisabled, out value, out e, cachedData);
+ }
+ else
+ {
+ result = TimeZoneInfoResult.TimeZoneNotFoundException;
+ value = null;
+ }
+
+ return result;
+ }
+
+ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, bool dstDisabled, out TimeZoneInfo value, out Exception e, CachedData cachedData)
+ {
+ TimeZoneInfoResult result;
+ TimeZoneInfo match;
+
+ result = TryGetTimeZoneFromLocalMachine(id, out match, out e);
+
+ if (result == TimeZoneInfoResult.Success)
+ {
+ if (cachedData._systemTimeZones == null)
+ cachedData._systemTimeZones = new Dictionary<string, TimeZoneInfo>(StringComparer.OrdinalIgnoreCase);
+
+ cachedData._systemTimeZones.Add(id, match);
+
+ if (dstDisabled && match._supportsDaylightSavingTime)
+ {
+ // we found a cache hit but we want a time zone without DST and this one has DST data
+ value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName);
+ }
+ else
+ {
+ value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName,
+ match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false);
+ }
+ }
+ else
+ {
+ value = null;
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Helper function that validates the TimeSpan is within +/- 14.0 hours
+ /// </summary>
+ internal static bool UtcOffsetOutOfRange(TimeSpan offset) =>
+ offset.TotalHours < -14.0 || offset.TotalHours > 14.0;
+
+ /// <summary>
+ /// Helper function that performs all of the validation checks for the
+ /// factory methods and deserialization callback.
+ /// </summary>
+ private static void ValidateTimeZoneInfo(string id, TimeSpan baseUtcOffset, AdjustmentRule[] adjustmentRules, out bool adjustmentRulesSupportDst)
+ {
+ if (id == null)
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+
+ if (id.Length == 0)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidId, id), nameof(id));
+ }
+
+ if (UtcOffsetOutOfRange(baseUtcOffset))
+ {
+ throw new ArgumentOutOfRangeException(nameof(baseUtcOffset), SR.ArgumentOutOfRange_UtcOffset);
+ }
+
+ if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
+ {
+ throw new ArgumentException(SR.Argument_TimeSpanHasSeconds, nameof(baseUtcOffset));
+ }
+
+ adjustmentRulesSupportDst = false;
+
+ //
+ // "adjustmentRules" can either be null or a valid array of AdjustmentRule objects.
+ // A valid array is one that does not contain any null elements and all elements
+ // are sorted in chronological order
+ //
+
+ if (adjustmentRules != null && adjustmentRules.Length != 0)
+ {
+ adjustmentRulesSupportDst = true;
+ AdjustmentRule prev = null;
+ AdjustmentRule current = null;
+ for (int i = 0; i < adjustmentRules.Length; i++)
+ {
+ prev = current;
+ current = adjustmentRules[i];
+
+ if (current == null)
+ {
+ throw new InvalidTimeZoneException(SR.Argument_AdjustmentRulesNoNulls);
+ }
+
+ // FUTURE: check to see if this rule supports Daylight Saving Time
+ // adjustmentRulesSupportDst = adjustmentRulesSupportDst || current.SupportsDaylightSavingTime;
+ // FUTURE: test baseUtcOffset + current.StandardDelta
+
+ if (UtcOffsetOutOfRange(baseUtcOffset + current.DaylightDelta))
+ {
+ throw new InvalidTimeZoneException(SR.ArgumentOutOfRange_UtcOffsetAndDaylightDelta);
+ }
+
+ if (prev != null && current.DateStart <= prev.DateEnd)
+ {
+ // verify the rules are in chronological order and the DateStart/DateEnd do not overlap
+ throw new InvalidTimeZoneException(SR.Argument_AdjustmentRulesOutOfOrder);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneNotFoundException.cs b/src/System.Private.CoreLib/shared/System/TimeZoneNotFoundException.cs
new file mode 100644
index 0000000000..f83c6443c3
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeZoneNotFoundException.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class TimeZoneNotFoundException : Exception
+ {
+ public TimeZoneNotFoundException()
+ {
+ }
+
+ public TimeZoneNotFoundException(String message)
+ : base(message)
+ {
+ }
+
+ public TimeZoneNotFoundException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ protected TimeZoneNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TimeoutException.cs b/src/System.Private.CoreLib/shared/System/TimeoutException.cs
new file mode 100644
index 0000000000..ddaa47747d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TimeoutException.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: Exception class for Timeout
+**
+**
+=============================================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class TimeoutException : SystemException
+ {
+ public TimeoutException()
+ : base(SR.Arg_TimeoutException)
+ {
+ HResult = HResults.COR_E_TIMEOUT;
+ }
+
+ public TimeoutException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_TIMEOUT;
+ }
+
+ public TimeoutException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_TIMEOUT;
+ }
+
+ protected TimeoutException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Tuple.cs b/src/System.Private.CoreLib/shared/System/Tuple.cs
new file mode 100644
index 0000000000..2d027cd40c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Tuple.cs
@@ -0,0 +1,1271 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+//
+// Note: F# compiler depends on the exact tuple hashing algorithm. Do not ever change it.
+//
+
+namespace System
+{
+ /// <summary>
+ /// Helper so we can call some tuple methods recursively without knowing the underlying types.
+ /// </summary>
+ internal interface ITupleInternal : ITuple
+ {
+ string ToString(StringBuilder sb);
+ int GetHashCode(IEqualityComparer comparer);
+ }
+
+ public static class Tuple
+ {
+ public static Tuple<T1> Create<T1>(T1 item1)
+ {
+ return new Tuple<T1>(item1);
+ }
+
+ public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2)
+ {
+ return new Tuple<T1, T2>(item1, item2);
+ }
+
+ public static Tuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
+ {
+ return new Tuple<T1, T2, T3>(item1, item2, item3);
+ }
+
+ public static Tuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, T4 item4)
+ {
+ return new Tuple<T1, T2, T3, T4>(item1, item2, item3, item4);
+ }
+
+ public static Tuple<T1, T2, T3, T4, T5> Create<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
+ {
+ return new Tuple<T1, T2, T3, T4, T5>(item1, item2, item3, item4, item5);
+ }
+
+ public static Tuple<T1, T2, T3, T4, T5, T6> Create<T1, T2, T3, T4, T5, T6>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
+ {
+ return new Tuple<T1, T2, T3, T4, T5, T6>(item1, item2, item3, item4, item5, item6);
+ }
+
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7> Create<T1, T2, T3, T4, T5, T6, T7>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
+ {
+ return new Tuple<T1, T2, T3, T4, T5, T6, T7>(item1, item2, item3, item4, item5, item6, item7);
+ }
+
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
+ {
+ return new Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>>(item1, item2, item3, item4, item5, item6, item7, new Tuple<T8>(item8));
+ }
+
+ // From System.Web.Util.HashCodeCombiner
+ internal static int CombineHashCodes(int h1, int h2)
+ {
+ return (((h1 << 5) + h1) ^ h2);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2), h3);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2), CombineHashCodes(h3, h4));
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), h5);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6));
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7));
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7, h8));
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Tuple<T1> : IStructuralEquatable, IStructuralComparable, IComparable, ITupleInternal, ITuple
+ {
+ private readonly T1 m_Item1; // Do not rename (binary serialization)
+
+ public T1 Item1 { get { return m_Item1; } }
+
+ public Tuple(T1 item1)
+ {
+ m_Item1 = item1;
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<Object>.Default);
+ }
+
+ Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer)
+ {
+ if (other == null) return false;
+
+ Tuple<T1> objTuple = other as Tuple<T1>;
+
+ if (objTuple == null)
+ {
+ return false;
+ }
+
+ return comparer.Equals(m_Item1, objTuple.m_Item1);
+ }
+
+ Int32 IComparable.CompareTo(Object obj)
+ {
+ return ((IStructuralComparable)this).CompareTo(obj, Comparer<Object>.Default);
+ }
+
+ Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ Tuple<T1> objTuple = other as Tuple<T1>;
+
+ if (objTuple == null)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_TupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return comparer.Compare(m_Item1, objTuple.m_Item1);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<Object>.Default);
+ }
+
+ Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return comparer.GetHashCode(m_Item1);
+ }
+
+ Int32 ITupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return ((IStructuralEquatable)this).GetHashCode(comparer);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append('(');
+ return ((ITupleInternal)this).ToString(sb);
+ }
+
+ string ITupleInternal.ToString(StringBuilder sb)
+ {
+ sb.Append(m_Item1);
+ sb.Append(')');
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 1;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ if (index != 0)
+ {
+ throw new IndexOutOfRangeException();
+ }
+ return Item1;
+ }
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Tuple<T1, T2> : IStructuralEquatable, IStructuralComparable, IComparable, ITupleInternal, ITuple
+ {
+ private readonly T1 m_Item1; // Do not rename (binary serialization)
+ private readonly T2 m_Item2; // Do not rename (binary serialization)
+
+ public T1 Item1 { get { return m_Item1; } }
+ public T2 Item2 { get { return m_Item2; } }
+
+ public Tuple(T1 item1, T2 item2)
+ {
+ m_Item1 = item1;
+ m_Item2 = item2;
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<Object>.Default); ;
+ }
+
+ Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer)
+ {
+ if (other == null) return false;
+
+ Tuple<T1, T2> objTuple = other as Tuple<T1, T2>;
+
+ if (objTuple == null)
+ {
+ return false;
+ }
+
+ return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2);
+ }
+
+ Int32 IComparable.CompareTo(Object obj)
+ {
+ return ((IStructuralComparable)this).CompareTo(obj, Comparer<Object>.Default);
+ }
+
+ Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ Tuple<T1, T2> objTuple = other as Tuple<T1, T2>;
+
+ if (objTuple == null)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_TupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ int c = 0;
+
+ c = comparer.Compare(m_Item1, objTuple.m_Item1);
+
+ if (c != 0) return c;
+
+ return comparer.Compare(m_Item2, objTuple.m_Item2);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<Object>.Default);
+ }
+
+ Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2));
+ }
+
+ Int32 ITupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return ((IStructuralEquatable)this).GetHashCode(comparer);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append('(');
+ return ((ITupleInternal)this).ToString(sb);
+ }
+
+ string ITupleInternal.ToString(StringBuilder sb)
+ {
+ sb.Append(m_Item1);
+ sb.Append(", ");
+ sb.Append(m_Item2);
+ sb.Append(')');
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 2;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Tuple<T1, T2, T3> : IStructuralEquatable, IStructuralComparable, IComparable, ITupleInternal, ITuple
+ {
+ private readonly T1 m_Item1; // Do not rename (binary serialization)
+ private readonly T2 m_Item2; // Do not rename (binary serialization)
+ private readonly T3 m_Item3; // Do not rename (binary serialization)
+
+ public T1 Item1 { get { return m_Item1; } }
+ public T2 Item2 { get { return m_Item2; } }
+ public T3 Item3 { get { return m_Item3; } }
+
+ public Tuple(T1 item1, T2 item2, T3 item3)
+ {
+ m_Item1 = item1;
+ m_Item2 = item2;
+ m_Item3 = item3;
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<Object>.Default); ;
+ }
+
+ Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer)
+ {
+ if (other == null) return false;
+
+ Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;
+
+ if (objTuple == null)
+ {
+ return false;
+ }
+
+ return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3);
+ }
+
+ Int32 IComparable.CompareTo(Object obj)
+ {
+ return ((IStructuralComparable)this).CompareTo(obj, Comparer<Object>.Default);
+ }
+
+ Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;
+
+ if (objTuple == null)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_TupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ int c = 0;
+
+ c = comparer.Compare(m_Item1, objTuple.m_Item1);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item2, objTuple.m_Item2);
+
+ if (c != 0) return c;
+
+ return comparer.Compare(m_Item3, objTuple.m_Item3);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<Object>.Default);
+ }
+
+ Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3));
+ }
+
+ Int32 ITupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return ((IStructuralEquatable)this).GetHashCode(comparer);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append('(');
+ return ((ITupleInternal)this).ToString(sb);
+ }
+
+ string ITupleInternal.ToString(StringBuilder sb)
+ {
+ sb.Append(m_Item1);
+ sb.Append(", ");
+ sb.Append(m_Item2);
+ sb.Append(", ");
+ sb.Append(m_Item3);
+ sb.Append(')');
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 3;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Tuple<T1, T2, T3, T4> : IStructuralEquatable, IStructuralComparable, IComparable, ITupleInternal, ITuple
+ {
+ private readonly T1 m_Item1; // Do not rename (binary serialization)
+ private readonly T2 m_Item2; // Do not rename (binary serialization)
+ private readonly T3 m_Item3; // Do not rename (binary serialization)
+ private readonly T4 m_Item4; // Do not rename (binary serialization)
+
+ public T1 Item1 { get { return m_Item1; } }
+ public T2 Item2 { get { return m_Item2; } }
+ public T3 Item3 { get { return m_Item3; } }
+ public T4 Item4 { get { return m_Item4; } }
+
+ public Tuple(T1 item1, T2 item2, T3 item3, T4 item4)
+ {
+ m_Item1 = item1;
+ m_Item2 = item2;
+ m_Item3 = item3;
+ m_Item4 = item4;
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<Object>.Default); ;
+ }
+
+ Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer)
+ {
+ if (other == null) return false;
+
+ Tuple<T1, T2, T3, T4> objTuple = other as Tuple<T1, T2, T3, T4>;
+
+ if (objTuple == null)
+ {
+ return false;
+ }
+
+ return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3) && comparer.Equals(m_Item4, objTuple.m_Item4);
+ }
+
+ Int32 IComparable.CompareTo(Object obj)
+ {
+ return ((IStructuralComparable)this).CompareTo(obj, Comparer<Object>.Default);
+ }
+
+ Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ Tuple<T1, T2, T3, T4> objTuple = other as Tuple<T1, T2, T3, T4>;
+
+ if (objTuple == null)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_TupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ int c = 0;
+
+ c = comparer.Compare(m_Item1, objTuple.m_Item1);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item2, objTuple.m_Item2);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item3, objTuple.m_Item3);
+
+ if (c != 0) return c;
+
+ return comparer.Compare(m_Item4, objTuple.m_Item4);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<Object>.Default);
+ }
+
+ Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3), comparer.GetHashCode(m_Item4));
+ }
+
+ Int32 ITupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return ((IStructuralEquatable)this).GetHashCode(comparer);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append('(');
+ return ((ITupleInternal)this).ToString(sb);
+ }
+
+ string ITupleInternal.ToString(StringBuilder sb)
+ {
+ sb.Append(m_Item1);
+ sb.Append(", ");
+ sb.Append(m_Item2);
+ sb.Append(", ");
+ sb.Append(m_Item3);
+ sb.Append(", ");
+ sb.Append(m_Item4);
+ sb.Append(')');
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 4;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Tuple<T1, T2, T3, T4, T5> : IStructuralEquatable, IStructuralComparable, IComparable, ITupleInternal, ITuple
+ {
+ private readonly T1 m_Item1; // Do not rename (binary serialization)
+ private readonly T2 m_Item2; // Do not rename (binary serialization)
+ private readonly T3 m_Item3; // Do not rename (binary serialization)
+ private readonly T4 m_Item4; // Do not rename (binary serialization)
+ private readonly T5 m_Item5; // Do not rename (binary serialization)
+
+ public T1 Item1 { get { return m_Item1; } }
+ public T2 Item2 { get { return m_Item2; } }
+ public T3 Item3 { get { return m_Item3; } }
+ public T4 Item4 { get { return m_Item4; } }
+ public T5 Item5 { get { return m_Item5; } }
+
+ public Tuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
+ {
+ m_Item1 = item1;
+ m_Item2 = item2;
+ m_Item3 = item3;
+ m_Item4 = item4;
+ m_Item5 = item5;
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<Object>.Default); ;
+ }
+
+ Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer)
+ {
+ if (other == null) return false;
+
+ Tuple<T1, T2, T3, T4, T5> objTuple = other as Tuple<T1, T2, T3, T4, T5>;
+
+ if (objTuple == null)
+ {
+ return false;
+ }
+
+ return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3) && comparer.Equals(m_Item4, objTuple.m_Item4) && comparer.Equals(m_Item5, objTuple.m_Item5);
+ }
+
+ Int32 IComparable.CompareTo(Object obj)
+ {
+ return ((IStructuralComparable)this).CompareTo(obj, Comparer<Object>.Default);
+ }
+
+ Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ Tuple<T1, T2, T3, T4, T5> objTuple = other as Tuple<T1, T2, T3, T4, T5>;
+
+ if (objTuple == null)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_TupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ int c = 0;
+
+ c = comparer.Compare(m_Item1, objTuple.m_Item1);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item2, objTuple.m_Item2);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item3, objTuple.m_Item3);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item4, objTuple.m_Item4);
+
+ if (c != 0) return c;
+
+ return comparer.Compare(m_Item5, objTuple.m_Item5);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<Object>.Default);
+ }
+
+ Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3), comparer.GetHashCode(m_Item4), comparer.GetHashCode(m_Item5));
+ }
+
+ Int32 ITupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return ((IStructuralEquatable)this).GetHashCode(comparer);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append('(');
+ return ((ITupleInternal)this).ToString(sb);
+ }
+
+ string ITupleInternal.ToString(StringBuilder sb)
+ {
+ sb.Append(m_Item1);
+ sb.Append(", ");
+ sb.Append(m_Item2);
+ sb.Append(", ");
+ sb.Append(m_Item3);
+ sb.Append(", ");
+ sb.Append(m_Item4);
+ sb.Append(", ");
+ sb.Append(m_Item5);
+ sb.Append(')');
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 5;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ case 4:
+ return Item5;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Tuple<T1, T2, T3, T4, T5, T6> : IStructuralEquatable, IStructuralComparable, IComparable, ITupleInternal, ITuple
+ {
+ private readonly T1 m_Item1; // Do not rename (binary serialization)
+ private readonly T2 m_Item2; // Do not rename (binary serialization)
+ private readonly T3 m_Item3; // Do not rename (binary serialization)
+ private readonly T4 m_Item4; // Do not rename (binary serialization)
+ private readonly T5 m_Item5; // Do not rename (binary serialization)
+ private readonly T6 m_Item6; // Do not rename (binary serialization)
+
+ public T1 Item1 { get { return m_Item1; } }
+ public T2 Item2 { get { return m_Item2; } }
+ public T3 Item3 { get { return m_Item3; } }
+ public T4 Item4 { get { return m_Item4; } }
+ public T5 Item5 { get { return m_Item5; } }
+ public T6 Item6 { get { return m_Item6; } }
+
+ public Tuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
+ {
+ m_Item1 = item1;
+ m_Item2 = item2;
+ m_Item3 = item3;
+ m_Item4 = item4;
+ m_Item5 = item5;
+ m_Item6 = item6;
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<Object>.Default); ;
+ }
+
+ Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer)
+ {
+ if (other == null) return false;
+
+ Tuple<T1, T2, T3, T4, T5, T6> objTuple = other as Tuple<T1, T2, T3, T4, T5, T6>;
+
+ if (objTuple == null)
+ {
+ return false;
+ }
+
+ return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3) && comparer.Equals(m_Item4, objTuple.m_Item4) && comparer.Equals(m_Item5, objTuple.m_Item5) && comparer.Equals(m_Item6, objTuple.m_Item6);
+ }
+
+ Int32 IComparable.CompareTo(Object obj)
+ {
+ return ((IStructuralComparable)this).CompareTo(obj, Comparer<Object>.Default);
+ }
+
+ Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ Tuple<T1, T2, T3, T4, T5, T6> objTuple = other as Tuple<T1, T2, T3, T4, T5, T6>;
+
+ if (objTuple == null)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_TupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ int c = 0;
+
+ c = comparer.Compare(m_Item1, objTuple.m_Item1);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item2, objTuple.m_Item2);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item3, objTuple.m_Item3);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item4, objTuple.m_Item4);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item5, objTuple.m_Item5);
+
+ if (c != 0) return c;
+
+ return comparer.Compare(m_Item6, objTuple.m_Item6);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<Object>.Default);
+ }
+
+ Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3), comparer.GetHashCode(m_Item4), comparer.GetHashCode(m_Item5), comparer.GetHashCode(m_Item6));
+ }
+
+ Int32 ITupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return ((IStructuralEquatable)this).GetHashCode(comparer);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append('(');
+ return ((ITupleInternal)this).ToString(sb);
+ }
+
+ string ITupleInternal.ToString(StringBuilder sb)
+ {
+ sb.Append(m_Item1);
+ sb.Append(", ");
+ sb.Append(m_Item2);
+ sb.Append(", ");
+ sb.Append(m_Item3);
+ sb.Append(", ");
+ sb.Append(m_Item4);
+ sb.Append(", ");
+ sb.Append(m_Item5);
+ sb.Append(", ");
+ sb.Append(m_Item6);
+ sb.Append(')');
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 6;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ case 4:
+ return Item5;
+ case 5:
+ return Item6;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Tuple<T1, T2, T3, T4, T5, T6, T7> : IStructuralEquatable, IStructuralComparable, IComparable, ITupleInternal, ITuple
+ {
+ private readonly T1 m_Item1; // Do not rename (binary serialization)
+ private readonly T2 m_Item2; // Do not rename (binary serialization)
+ private readonly T3 m_Item3; // Do not rename (binary serialization)
+ private readonly T4 m_Item4; // Do not rename (binary serialization)
+ private readonly T5 m_Item5; // Do not rename (binary serialization)
+ private readonly T6 m_Item6; // Do not rename (binary serialization)
+ private readonly T7 m_Item7; // Do not rename (binary serialization)
+
+ public T1 Item1 { get { return m_Item1; } }
+ public T2 Item2 { get { return m_Item2; } }
+ public T3 Item3 { get { return m_Item3; } }
+ public T4 Item4 { get { return m_Item4; } }
+ public T5 Item5 { get { return m_Item5; } }
+ public T6 Item6 { get { return m_Item6; } }
+ public T7 Item7 { get { return m_Item7; } }
+
+ public Tuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
+ {
+ m_Item1 = item1;
+ m_Item2 = item2;
+ m_Item3 = item3;
+ m_Item4 = item4;
+ m_Item5 = item5;
+ m_Item6 = item6;
+ m_Item7 = item7;
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<Object>.Default); ;
+ }
+
+ Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer)
+ {
+ if (other == null) return false;
+
+ Tuple<T1, T2, T3, T4, T5, T6, T7> objTuple = other as Tuple<T1, T2, T3, T4, T5, T6, T7>;
+
+ if (objTuple == null)
+ {
+ return false;
+ }
+
+ return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3) && comparer.Equals(m_Item4, objTuple.m_Item4) && comparer.Equals(m_Item5, objTuple.m_Item5) && comparer.Equals(m_Item6, objTuple.m_Item6) && comparer.Equals(m_Item7, objTuple.m_Item7);
+ }
+
+ Int32 IComparable.CompareTo(Object obj)
+ {
+ return ((IStructuralComparable)this).CompareTo(obj, Comparer<Object>.Default);
+ }
+
+ Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ Tuple<T1, T2, T3, T4, T5, T6, T7> objTuple = other as Tuple<T1, T2, T3, T4, T5, T6, T7>;
+
+ if (objTuple == null)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_TupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ int c = 0;
+
+ c = comparer.Compare(m_Item1, objTuple.m_Item1);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item2, objTuple.m_Item2);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item3, objTuple.m_Item3);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item4, objTuple.m_Item4);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item5, objTuple.m_Item5);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item6, objTuple.m_Item6);
+
+ if (c != 0) return c;
+
+ return comparer.Compare(m_Item7, objTuple.m_Item7);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<Object>.Default);
+ }
+
+ Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3), comparer.GetHashCode(m_Item4), comparer.GetHashCode(m_Item5), comparer.GetHashCode(m_Item6), comparer.GetHashCode(m_Item7));
+ }
+
+ Int32 ITupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return ((IStructuralEquatable)this).GetHashCode(comparer);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append('(');
+ return ((ITupleInternal)this).ToString(sb);
+ }
+
+ string ITupleInternal.ToString(StringBuilder sb)
+ {
+ sb.Append(m_Item1);
+ sb.Append(", ");
+ sb.Append(m_Item2);
+ sb.Append(", ");
+ sb.Append(m_Item3);
+ sb.Append(", ");
+ sb.Append(m_Item4);
+ sb.Append(", ");
+ sb.Append(m_Item5);
+ sb.Append(", ");
+ sb.Append(m_Item6);
+ sb.Append(", ");
+ sb.Append(m_Item7);
+ sb.Append(')');
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 7;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ case 4:
+ return Item5;
+ case 5:
+ return Item6;
+ case 6:
+ return Item7;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> : IStructuralEquatable, IStructuralComparable, IComparable, ITupleInternal, ITuple
+ {
+ private readonly T1 m_Item1; // Do not rename (binary serialization)
+ private readonly T2 m_Item2; // Do not rename (binary serialization)
+ private readonly T3 m_Item3; // Do not rename (binary serialization)
+ private readonly T4 m_Item4; // Do not rename (binary serialization)
+ private readonly T5 m_Item5; // Do not rename (binary serialization)
+ private readonly T6 m_Item6; // Do not rename (binary serialization)
+ private readonly T7 m_Item7; // Do not rename (binary serialization)
+ private readonly TRest m_Rest; // Do not rename (binary serialization)
+
+ public T1 Item1 { get { return m_Item1; } }
+ public T2 Item2 { get { return m_Item2; } }
+ public T3 Item3 { get { return m_Item3; } }
+ public T4 Item4 { get { return m_Item4; } }
+ public T5 Item5 { get { return m_Item5; } }
+ public T6 Item6 { get { return m_Item6; } }
+ public T7 Item7 { get { return m_Item7; } }
+ public TRest Rest { get { return m_Rest; } }
+
+ public Tuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest)
+ {
+ if (!(rest is ITupleInternal))
+ {
+ throw new ArgumentException(SR.ArgumentException_TupleLastArgumentNotATuple);
+ }
+
+ m_Item1 = item1;
+ m_Item2 = item2;
+ m_Item3 = item3;
+ m_Item4 = item4;
+ m_Item5 = item5;
+ m_Item6 = item6;
+ m_Item7 = item7;
+ m_Rest = rest;
+ }
+
+ public override Boolean Equals(Object obj)
+ {
+ return ((IStructuralEquatable)this).Equals(obj, EqualityComparer<Object>.Default); ;
+ }
+
+ Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer)
+ {
+ if (other == null) return false;
+
+ Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> objTuple = other as Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>;
+
+ if (objTuple == null)
+ {
+ return false;
+ }
+
+ return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3) && comparer.Equals(m_Item4, objTuple.m_Item4) && comparer.Equals(m_Item5, objTuple.m_Item5) && comparer.Equals(m_Item6, objTuple.m_Item6) && comparer.Equals(m_Item7, objTuple.m_Item7) && comparer.Equals(m_Rest, objTuple.m_Rest);
+ }
+
+ Int32 IComparable.CompareTo(Object obj)
+ {
+ return ((IStructuralComparable)this).CompareTo(obj, Comparer<Object>.Default);
+ }
+
+ Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> objTuple = other as Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>;
+
+ if (objTuple == null)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_TupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ int c = 0;
+
+ c = comparer.Compare(m_Item1, objTuple.m_Item1);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item2, objTuple.m_Item2);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item3, objTuple.m_Item3);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item4, objTuple.m_Item4);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item5, objTuple.m_Item5);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item6, objTuple.m_Item6);
+
+ if (c != 0) return c;
+
+ c = comparer.Compare(m_Item7, objTuple.m_Item7);
+
+ if (c != 0) return c;
+
+ return comparer.Compare(m_Rest, objTuple.m_Rest);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((IStructuralEquatable)this).GetHashCode(EqualityComparer<Object>.Default);
+ }
+
+ Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple
+ ITupleInternal t = (ITupleInternal)m_Rest;
+ if (t.Length >= 8) { return t.GetHashCode(comparer); }
+
+ // In this case, the rest memeber has less than 8 elements so we need to combine some our elements with the elements in rest
+ int k = 8 - t.Length;
+ switch (k)
+ {
+ case 1:
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item7), t.GetHashCode(comparer));
+ case 2:
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item6), comparer.GetHashCode(m_Item7), t.GetHashCode(comparer));
+ case 3:
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item5), comparer.GetHashCode(m_Item6), comparer.GetHashCode(m_Item7), t.GetHashCode(comparer));
+ case 4:
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item4), comparer.GetHashCode(m_Item5), comparer.GetHashCode(m_Item6), comparer.GetHashCode(m_Item7), t.GetHashCode(comparer));
+ case 5:
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item3), comparer.GetHashCode(m_Item4), comparer.GetHashCode(m_Item5), comparer.GetHashCode(m_Item6), comparer.GetHashCode(m_Item7), t.GetHashCode(comparer));
+ case 6:
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3), comparer.GetHashCode(m_Item4), comparer.GetHashCode(m_Item5), comparer.GetHashCode(m_Item6), comparer.GetHashCode(m_Item7), t.GetHashCode(comparer));
+ case 7:
+ return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3), comparer.GetHashCode(m_Item4), comparer.GetHashCode(m_Item5), comparer.GetHashCode(m_Item6), comparer.GetHashCode(m_Item7), t.GetHashCode(comparer));
+ }
+ Debug.Fail("Missed all cases for computing Tuple hash code");
+ return -1;
+ }
+
+ Int32 ITupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return ((IStructuralEquatable)this).GetHashCode(comparer);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append('(');
+ return ((ITupleInternal)this).ToString(sb);
+ }
+
+ string ITupleInternal.ToString(StringBuilder sb)
+ {
+ sb.Append(m_Item1);
+ sb.Append(", ");
+ sb.Append(m_Item2);
+ sb.Append(", ");
+ sb.Append(m_Item3);
+ sb.Append(", ");
+ sb.Append(m_Item4);
+ sb.Append(", ");
+ sb.Append(m_Item5);
+ sb.Append(", ");
+ sb.Append(m_Item6);
+ sb.Append(", ");
+ sb.Append(m_Item7);
+ sb.Append(", ");
+ return ((ITupleInternal)m_Rest).ToString(sb);
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length
+ {
+ get
+ {
+ return 7 + ((ITupleInternal)Rest).Length;
+ }
+ }
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ case 4:
+ return Item5;
+ case 5:
+ return Item6;
+ case 6:
+ return Item7;
+ }
+
+ return ((ITupleInternal)Rest)[index - 7];
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TupleExtensions.cs b/src/System.Private.CoreLib/shared/System/TupleExtensions.cs
new file mode 100644
index 0000000000..106a88a08b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TupleExtensions.cs
@@ -0,0 +1,930 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ /// <summary>
+ /// Provides extension methods for <see cref="Tuple"/> instances to interop with C# tuples features (deconstruction syntax, converting from and to <see cref="ValueTuple"/>).
+ /// </summary>
+ public static class TupleExtensions
+ {
+ #region Deconstruct
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 1 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1>(
+ this Tuple<T1> value,
+ out T1 item1)
+ {
+ item1 = value.Item1;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 2 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2>(
+ this Tuple<T1, T2> value,
+ out T1 item1, out T2 item2)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 3 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3>(
+ this Tuple<T1, T2, T3> value,
+ out T1 item1, out T2 item2, out T3 item3)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 4 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4>(
+ this Tuple<T1, T2, T3, T4> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 5 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5>(
+ this Tuple<T1, T2, T3, T4, T5> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 6 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6>(
+ this Tuple<T1, T2, T3, T4, T5, T6> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 7 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 8 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 9 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 10 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 11 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 12 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 13 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 14 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13, out T14 item14)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ item14 = value.Rest.Item7;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 15 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15>>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13, out T14 item14, out T15 item15)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ item14 = value.Rest.Item7;
+ item15 = value.Rest.Rest.Item1;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 16 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16>>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13, out T14 item14, out T15 item15, out T16 item16)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ item14 = value.Rest.Item7;
+ item15 = value.Rest.Rest.Item1;
+ item16 = value.Rest.Rest.Item2;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 17 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17>>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13, out T14 item14, out T15 item15, out T16 item16, out T17 item17)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ item14 = value.Rest.Item7;
+ item15 = value.Rest.Rest.Item1;
+ item16 = value.Rest.Rest.Item2;
+ item17 = value.Rest.Rest.Item3;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 18 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18>>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13, out T14 item14, out T15 item15, out T16 item16, out T17 item17, out T18 item18)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ item14 = value.Rest.Item7;
+ item15 = value.Rest.Rest.Item1;
+ item16 = value.Rest.Rest.Item2;
+ item17 = value.Rest.Rest.Item3;
+ item18 = value.Rest.Rest.Item4;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 19 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19>>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13, out T14 item14, out T15 item15, out T16 item16, out T17 item17, out T18 item18, out T19 item19)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ item14 = value.Rest.Item7;
+ item15 = value.Rest.Rest.Item1;
+ item16 = value.Rest.Rest.Item2;
+ item17 = value.Rest.Rest.Item3;
+ item18 = value.Rest.Rest.Item4;
+ item19 = value.Rest.Rest.Item5;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 20 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19, T20>>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13, out T14 item14, out T15 item15, out T16 item16, out T17 item17, out T18 item18, out T19 item19, out T20 item20)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ item14 = value.Rest.Item7;
+ item15 = value.Rest.Rest.Item1;
+ item16 = value.Rest.Rest.Item2;
+ item17 = value.Rest.Rest.Item3;
+ item18 = value.Rest.Rest.Item4;
+ item19 = value.Rest.Rest.Item5;
+ item20 = value.Rest.Rest.Item6;
+ }
+
+ /// <summary>
+ /// Deconstruct a properly nested <see cref="Tuple"/> with 21 elements.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Deconstruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19, T20, T21>>> value,
+ out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6, out T7 item7, out T8 item8, out T9 item9, out T10 item10, out T11 item11, out T12 item12, out T13 item13, out T14 item14, out T15 item15, out T16 item16, out T17 item17, out T18 item18, out T19 item19, out T20 item20, out T21 item21)
+ {
+ item1 = value.Item1;
+ item2 = value.Item2;
+ item3 = value.Item3;
+ item4 = value.Item4;
+ item5 = value.Item5;
+ item6 = value.Item6;
+ item7 = value.Item7;
+ item8 = value.Rest.Item1;
+ item9 = value.Rest.Item2;
+ item10 = value.Rest.Item3;
+ item11 = value.Rest.Item4;
+ item12 = value.Rest.Item5;
+ item13 = value.Rest.Item6;
+ item14 = value.Rest.Item7;
+ item15 = value.Rest.Rest.Item1;
+ item16 = value.Rest.Rest.Item2;
+ item17 = value.Rest.Rest.Item3;
+ item18 = value.Rest.Rest.Item4;
+ item19 = value.Rest.Rest.Item5;
+ item20 = value.Rest.Rest.Item6;
+ item21 = value.Rest.Rest.Item7;
+ }
+ #endregion
+
+ #region ToValueTuple
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 1 element.
+ /// </summary>
+ public static ValueTuple<T1>
+ ToValueTuple<T1>(
+ this Tuple<T1> value)
+ {
+ return ValueTuple.Create(value.Item1);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 2 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2>
+ ToValueTuple<T1, T2>(
+ this Tuple<T1, T2> value)
+ {
+ return ValueTuple.Create(value.Item1, value.Item2);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 3 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3>
+ ToValueTuple<T1, T2, T3>(
+ this Tuple<T1, T2, T3> value)
+ {
+ return ValueTuple.Create(value.Item1, value.Item2, value.Item3);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 4 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4>
+ ToValueTuple<T1, T2, T3, T4>(
+ this Tuple<T1, T2, T3, T4> value)
+ {
+ return ValueTuple.Create(value.Item1, value.Item2, value.Item3, value.Item4);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 5 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5>
+ ToValueTuple<T1, T2, T3, T4, T5>(
+ this Tuple<T1, T2, T3, T4, T5> value)
+ {
+ return ValueTuple.Create(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 6 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6>
+ ToValueTuple<T1, T2, T3, T4, T5, T6>(
+ this Tuple<T1, T2, T3, T4, T5, T6> value)
+ {
+ return ValueTuple.Create(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 7 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7> value)
+ {
+ return ValueTuple.Create(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 8 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ ValueTuple.Create(value.Rest.Item1));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 9 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ ValueTuple.Create(value.Rest.Item1, value.Rest.Item2));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 10 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ ValueTuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 11 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ ValueTuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 12 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ ValueTuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 13 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ ValueTuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 14 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ ValueTuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 15 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15>>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15>>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLong(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ ValueTuple.Create(value.Rest.Rest.Item1)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 16 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16>>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16>>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLong(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ ValueTuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 17 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17>>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17>>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLong(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ ValueTuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 18 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17, T18>>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18>>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLong(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ ValueTuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3, value.Rest.Rest.Item4)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 19 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17, T18, T19>>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19>>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLong(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ ValueTuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3, value.Rest.Rest.Item4, value.Rest.Rest.Item5)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 20 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17, T18, T19, T20>>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19, T20>>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLong(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ ValueTuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3, value.Rest.Rest.Item4, value.Rest.Rest.Item5, value.Rest.Rest.Item6)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="ValueTuple"/> from a properly nested <see cref="Tuple"/> with 21 elements.
+ /// </summary>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17, T18, T19, T20, T21>>>
+ ToValueTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21>(
+ this Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19, T20, T21>>> value)
+ {
+ return CreateLong(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLong(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ ValueTuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3, value.Rest.Rest.Item4, value.Rest.Rest.Item5, value.Rest.Rest.Item6, value.Rest.Rest.Item7)));
+ }
+ #endregion
+
+ #region ToTuple
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 1 element.
+ /// </summary>
+ public static Tuple<T1>
+ ToTuple<T1>(
+ this ValueTuple<T1> value)
+ {
+ return Tuple.Create(value.Item1);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 2 elements.
+ /// </summary>
+ public static Tuple<T1, T2>
+ ToTuple<T1, T2>(
+ this ValueTuple<T1, T2> value)
+ {
+ return Tuple.Create(value.Item1, value.Item2);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 3 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3>
+ ToTuple<T1, T2, T3>(
+ this ValueTuple<T1, T2, T3> value)
+ {
+ return Tuple.Create(value.Item1, value.Item2, value.Item3);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 4 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4>
+ ToTuple<T1, T2, T3, T4>(
+ this ValueTuple<T1, T2, T3, T4> value)
+ {
+ return Tuple.Create(value.Item1, value.Item2, value.Item3, value.Item4);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 5 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5>
+ ToTuple<T1, T2, T3, T4, T5>(
+ this ValueTuple<T1, T2, T3, T4, T5> value)
+ {
+ return Tuple.Create(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 6 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6>
+ ToTuple<T1, T2, T3, T4, T5, T6>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6> value)
+ {
+ return Tuple.Create(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 7 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7> value)
+ {
+ return Tuple.Create(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7);
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 8 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ Tuple.Create(value.Rest.Item1));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 9 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ Tuple.Create(value.Rest.Item1, value.Rest.Item2));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 10 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ Tuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 11 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ Tuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 12 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ Tuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 13 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ Tuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 14 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ Tuple.Create(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 15 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15>>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15>>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLongRef(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ Tuple.Create(value.Rest.Rest.Item1)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 16 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16>>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16>>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLongRef(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ Tuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 17 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17>>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17>>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLongRef(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ Tuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 18 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18>>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17, T18>>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLongRef(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ Tuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3, value.Rest.Rest.Item4)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 19 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19>>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17, T18, T19>>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLongRef(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ Tuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3, value.Rest.Rest.Item4, value.Rest.Rest.Item5)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 20 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19, T20>>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17, T18, T19, T20>>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLongRef(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ Tuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3, value.Rest.Rest.Item4, value.Rest.Rest.Item5, value.Rest.Rest.Item6)));
+ }
+
+ /// <summary>
+ /// Make a properly nested <see cref="Tuple"/> from a properly nested <see cref="ValueTuple"/> with 21 elements.
+ /// </summary>
+ public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10, T11, T12, T13, T14, Tuple<T15, T16, T17, T18, T19, T20, T21>>>
+ ToTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21>(
+ this ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10, T11, T12, T13, T14, ValueTuple<T15, T16, T17, T18, T19, T20, T21>>> value)
+ {
+ return CreateLongRef(value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7,
+ CreateLongRef(value.Rest.Item1, value.Rest.Item2, value.Rest.Item3, value.Rest.Item4, value.Rest.Item5, value.Rest.Item6, value.Rest.Item7,
+ Tuple.Create(value.Rest.Rest.Item1, value.Rest.Rest.Item2, value.Rest.Rest.Item3, value.Rest.Rest.Item4, value.Rest.Rest.Item5, value.Rest.Rest.Item6, value.Rest.Rest.Item7)));
+ }
+ #endregion
+
+ private static ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> CreateLong<T1, T2, T3, T4, T5, T6, T7, TRest>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) where TRest : struct, ITuple =>
+ new ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>(item1, item2, item3, item4, item5, item6, item7, rest);
+
+ private static Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> CreateLongRef<T1, T2, T3, T4, T5, T6, T7, TRest>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) where TRest : ITuple =>
+ new Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>(item1, item2, item3, item4, item5, item6, item7, rest);
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Type.Enum.cs b/src/System.Private.CoreLib/shared/System/Type.Enum.cs
new file mode 100644
index 0000000000..4d82410383
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Type.Enum.cs
@@ -0,0 +1,186 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace System
+{
+ //
+ // This file collects a set of Enum-related apis that run when the Type is subclassed by an application.
+ // None of it runs on normal Type objects supplied by the runtime (as those types override these methods.)
+ //
+ // Since app-subclassed Types are "untrusted classes" that may or may not implement the complete surface area correctly,
+ // this code should be considered brittle and not changed lightly.
+ //
+ public abstract partial class Type
+ {
+ public virtual bool IsEnumDefined(object value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (!IsEnum)
+ throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");
+
+ // Check if both of them are of the same type
+ Type valueType = value.GetType();
+
+ // If the value is an Enum then we need to extract the underlying value from it
+ if (valueType.IsEnum)
+ {
+ if (!valueType.IsEquivalentTo(this))
+ throw new ArgumentException(SR.Format(SR.Arg_EnumAndObjectMustBeSameType, valueType.ToString(), this.ToString()));
+
+ valueType = valueType.GetEnumUnderlyingType();
+ }
+
+ // If a string is passed in
+ if (valueType == typeof(string))
+ {
+ string[] names = GetEnumNames();
+ if (Array.IndexOf(names, value) >= 0)
+ return true;
+ else
+ return false;
+ }
+
+ // If an enum or integer value is passed in
+ if (Type.IsIntegerType(valueType))
+ {
+ Type underlyingType = GetEnumUnderlyingType();
+ // We cannot compare the types directly because valueType is always a runtime type but underlyingType might not be.
+ if (underlyingType.GetTypeCodeImpl() != valueType.GetTypeCodeImpl())
+ throw new ArgumentException(SR.Format(SR.Arg_EnumUnderlyingTypeAndObjectMustBeSameType, valueType.ToString(), underlyingType.ToString()));
+
+ Array values = GetEnumRawConstantValues();
+ return (BinarySearch(values, value) >= 0);
+ }
+ else
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType);
+ }
+ }
+
+ public virtual string GetEnumName(object value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (!IsEnum)
+ throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");
+
+ Type valueType = value.GetType();
+
+ if (!(valueType.IsEnum || Type.IsIntegerType(valueType)))
+ throw new ArgumentException(SR.Arg_MustBeEnumBaseTypeOrEnum, nameof(value));
+
+ Array values = GetEnumRawConstantValues();
+ int index = BinarySearch(values, value);
+
+ if (index >= 0)
+ {
+ string[] names = GetEnumNames();
+ return names[index];
+ }
+
+ return null;
+ }
+
+ public virtual string[] GetEnumNames()
+ {
+ if (!IsEnum)
+ throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");
+
+ string[] names;
+ Array values;
+ GetEnumData(out names, out values);
+ return names;
+ }
+
+
+ // Returns the enum values as an object array.
+ private Array GetEnumRawConstantValues()
+ {
+ string[] names;
+ Array values;
+ GetEnumData(out names, out values);
+ return values;
+ }
+
+ // This will return enumValues and enumNames sorted by the values.
+ private void GetEnumData(out string[] enumNames, out Array enumValues)
+ {
+ FieldInfo[] flds = GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
+
+ object[] values = new object[flds.Length];
+ string[] names = new string[flds.Length];
+
+ for (int i = 0; i < flds.Length; i++)
+ {
+ names[i] = flds[i].Name;
+ values[i] = flds[i].GetRawConstantValue();
+ }
+
+ // Insertion Sort these values in ascending order.
+ // We use this O(n^2) algorithm, but it turns out that most of the time the elements are already in sorted order and
+ // the common case performance will be faster than quick sorting this.
+ IComparer comparer = Comparer<object>.Default;
+ for (int i = 1; i < values.Length; i++)
+ {
+ int j = i;
+ string tempStr = names[i];
+ object val = values[i];
+ bool exchanged = false;
+
+ // Since the elements are sorted we only need to do one comparision, we keep the check for j inside the loop.
+ while (comparer.Compare(values[j - 1], val) > 0)
+ {
+ names[j] = names[j - 1];
+ values[j] = values[j - 1];
+ j--;
+ exchanged = true;
+ if (j == 0)
+ break;
+ }
+
+ if (exchanged)
+ {
+ names[j] = tempStr;
+ values[j] = val;
+ }
+ }
+
+ enumNames = names;
+ enumValues = values;
+ }
+
+ // Convert everything to ulong then perform a binary search.
+ private static int BinarySearch(Array array, object value)
+ {
+ ulong[] ulArray = new ulong[array.Length];
+ for (int i = 0; i < array.Length; ++i)
+ ulArray[i] = Enum.ToUInt64(array.GetValue(i));
+
+ ulong ulValue = Enum.ToUInt64(value);
+
+ return Array.BinarySearch(ulArray, ulValue);
+ }
+
+ internal static bool IsIntegerType(Type t)
+ {
+ return (t == typeof(int) ||
+ t == typeof(short) ||
+ t == typeof(ushort) ||
+ t == typeof(byte) ||
+ t == typeof(sbyte) ||
+ t == typeof(uint) ||
+ t == typeof(long) ||
+ t == typeof(ulong) ||
+ t == typeof(char) ||
+ t == typeof(bool));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Type.Helpers.cs b/src/System.Private.CoreLib/shared/System/Type.Helpers.cs
new file mode 100644
index 0000000000..db8df231e4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Type.Helpers.cs
@@ -0,0 +1,527 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+
+namespace System
+{
+ // This file collects the longer methods of Type to make the main Type class more readable.
+ public abstract partial class Type : MemberInfo, IReflect
+ {
+ public virtual bool IsSerializable
+ {
+ get
+ {
+ if ((GetAttributeFlagsImpl() & TypeAttributes.Serializable) != 0)
+ return true;
+
+ Type underlyingType = UnderlyingSystemType;
+ if (underlyingType.IsRuntimeImplemented())
+ {
+ do
+ {
+ // In all sane cases we only need to compare the direct level base type with
+ // System.Enum and System.MulticastDelegate. However, a generic parameter can
+ // have a base type constraint that is Delegate or even a real delegate type.
+ // Let's maintain compatibility and return true for them.
+ if (underlyingType == typeof(Delegate) || underlyingType == typeof(Enum))
+ return true;
+
+ underlyingType = underlyingType.BaseType;
+ }
+ while (underlyingType != null);
+ }
+
+ return false;
+ }
+ }
+
+ public virtual bool ContainsGenericParameters
+ {
+ get
+ {
+ if (HasElementType)
+ return GetRootElementType().ContainsGenericParameters;
+
+ if (IsGenericParameter)
+ return true;
+
+ if (!IsGenericType)
+ return false;
+
+ Type[] genericArguments = GetGenericArguments();
+ for (int i = 0; i < genericArguments.Length; i++)
+ {
+ if (genericArguments[i].ContainsGenericParameters)
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ internal Type GetRootElementType()
+ {
+ Type rootElementType = this;
+
+ while (rootElementType.HasElementType)
+ rootElementType = rootElementType.GetElementType();
+
+ return rootElementType;
+ }
+
+ public bool IsVisible
+ {
+ get
+ {
+#if CORECLR
+ RuntimeType rt = this as RuntimeType;
+ if (rt != null)
+ return RuntimeTypeHandle.IsVisible(rt);
+#endif //CORECLR
+
+ if (IsGenericParameter)
+ return true;
+
+ if (HasElementType)
+ return GetElementType().IsVisible;
+
+ Type type = this;
+ while (type.IsNested)
+ {
+ if (!type.IsNestedPublic)
+ return false;
+
+ // this should be null for non-nested types.
+ type = type.DeclaringType;
+ }
+
+ // Now "type" should be a top level type
+ if (!type.IsPublic)
+ return false;
+
+ if (IsGenericType && !IsGenericTypeDefinition)
+ {
+ foreach (Type t in GetGenericArguments())
+ {
+ if (!t.IsVisible)
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ public virtual Type[] FindInterfaces(TypeFilter filter, object filterCriteria)
+ {
+ if (filter == null)
+ throw new ArgumentNullException(nameof(filter));
+
+ Type[] c = GetInterfaces();
+ int cnt = 0;
+ for (int i = 0; i < c.Length; i++)
+ {
+ if (!filter(c[i], filterCriteria))
+ c[i] = null;
+ else
+ cnt++;
+ }
+ if (cnt == c.Length)
+ return c;
+
+ Type[] ret = new Type[cnt];
+ cnt = 0;
+ for (int i = 0; i < c.Length; i++)
+ {
+ if (c[i] != null)
+ ret[cnt++] = c[i];
+ }
+ return ret;
+ }
+
+ public virtual MemberInfo[] FindMembers(MemberTypes memberType, BindingFlags bindingAttr, MemberFilter filter, object filterCriteria)
+ {
+ // Define the work arrays
+ MethodInfo[] m = null;
+ ConstructorInfo[] c = null;
+ FieldInfo[] f = null;
+ PropertyInfo[] p = null;
+ EventInfo[] e = null;
+ Type[] t = null;
+
+ int i = 0;
+ int cnt = 0; // Total Matchs
+
+ // Check the methods
+ if ((memberType & MemberTypes.Method) != 0)
+ {
+ m = GetMethods(bindingAttr);
+ if (filter != null)
+ {
+ for (i = 0; i < m.Length; i++)
+ if (!filter(m[i], filterCriteria))
+ m[i] = null;
+ else
+ cnt++;
+ }
+ else
+ {
+ cnt += m.Length;
+ }
+ }
+
+ // Check the constructors
+ if ((memberType & MemberTypes.Constructor) != 0)
+ {
+ c = GetConstructors(bindingAttr);
+ if (filter != null)
+ {
+ for (i = 0; i < c.Length; i++)
+ if (!filter(c[i], filterCriteria))
+ c[i] = null;
+ else
+ cnt++;
+ }
+ else
+ {
+ cnt += c.Length;
+ }
+ }
+
+ // Check the fields
+ if ((memberType & MemberTypes.Field) != 0)
+ {
+ f = GetFields(bindingAttr);
+ if (filter != null)
+ {
+ for (i = 0; i < f.Length; i++)
+ if (!filter(f[i], filterCriteria))
+ f[i] = null;
+ else
+ cnt++;
+ }
+ else
+ {
+ cnt += f.Length;
+ }
+ }
+
+ // Check the Properties
+ if ((memberType & MemberTypes.Property) != 0)
+ {
+ p = GetProperties(bindingAttr);
+ if (filter != null)
+ {
+ for (i = 0; i < p.Length; i++)
+ if (!filter(p[i], filterCriteria))
+ p[i] = null;
+ else
+ cnt++;
+ }
+ else
+ {
+ cnt += p.Length;
+ }
+ }
+
+ // Check the Events
+ if ((memberType & MemberTypes.Event) != 0)
+ {
+ e = GetEvents(bindingAttr);
+ if (filter != null)
+ {
+ for (i = 0; i < e.Length; i++)
+ if (!filter(e[i], filterCriteria))
+ e[i] = null;
+ else
+ cnt++;
+ }
+ else
+ {
+ cnt += e.Length;
+ }
+ }
+
+ // Check the Types
+ if ((memberType & MemberTypes.NestedType) != 0)
+ {
+ t = GetNestedTypes(bindingAttr);
+ if (filter != null)
+ {
+ for (i = 0; i < t.Length; i++)
+ if (!filter(t[i], filterCriteria))
+ t[i] = null;
+ else
+ cnt++;
+ }
+ else
+ {
+ cnt += t.Length;
+ }
+ }
+
+ // Allocate the Member Info
+ MemberInfo[] ret = new MemberInfo[cnt];
+
+ // Copy the Methods
+ cnt = 0;
+ if (m != null)
+ {
+ for (i = 0; i < m.Length; i++)
+ if (m[i] != null)
+ ret[cnt++] = m[i];
+ }
+
+ // Copy the Constructors
+ if (c != null)
+ {
+ for (i = 0; i < c.Length; i++)
+ if (c[i] != null)
+ ret[cnt++] = c[i];
+ }
+
+ // Copy the Fields
+ if (f != null)
+ {
+ for (i = 0; i < f.Length; i++)
+ if (f[i] != null)
+ ret[cnt++] = f[i];
+ }
+
+ // Copy the Properties
+ if (p != null)
+ {
+ for (i = 0; i < p.Length; i++)
+ if (p[i] != null)
+ ret[cnt++] = p[i];
+ }
+
+ // Copy the Events
+ if (e != null)
+ {
+ for (i = 0; i < e.Length; i++)
+ if (e[i] != null)
+ ret[cnt++] = e[i];
+ }
+
+ // Copy the Types
+ if (t != null)
+ {
+ for (i = 0; i < t.Length; i++)
+ if (t[i] != null)
+ ret[cnt++] = t[i];
+ }
+
+ return ret;
+ }
+
+ public virtual bool IsSubclassOf(Type c)
+ {
+ Type p = this;
+ if (p == c)
+ return false;
+ while (p != null)
+ {
+ if (p == c)
+ return true;
+ p = p.BaseType;
+ }
+ return false;
+ }
+
+ public virtual bool IsAssignableFrom(Type c)
+ {
+ if (c == null)
+ return false;
+
+ if (this == c)
+ return true;
+
+ // For backward-compatibility, we need to special case for the types
+ // whose UnderlyingSystemType are runtime implemented.
+ Type toType = this.UnderlyingSystemType;
+ if (toType.IsRuntimeImplemented())
+ return toType.IsAssignableFrom(c);
+
+ // If c is a subclass of this class, then c can be cast to this type.
+ if (c.IsSubclassOf(this))
+ return true;
+
+ if (this.IsInterface)
+ {
+ return c.ImplementInterface(this);
+ }
+ else if (IsGenericParameter)
+ {
+ Type[] constraints = GetGenericParameterConstraints();
+ for (int i = 0; i < constraints.Length; i++)
+ if (!constraints[i].IsAssignableFrom(c))
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ internal bool ImplementInterface(Type ifaceType)
+ {
+ Type t = this;
+ while (t != null)
+ {
+ Type[] interfaces = t.GetInterfaces();
+ if (interfaces != null)
+ {
+ for (int i = 0; i < interfaces.Length; i++)
+ {
+ // Interfaces don't derive from other interfaces, they implement them.
+ // So instead of IsSubclassOf, we should use ImplementInterface instead.
+ if (interfaces[i] == ifaceType ||
+ (interfaces[i] != null && interfaces[i].ImplementInterface(ifaceType)))
+ return true;
+ }
+ }
+
+ t = t.BaseType;
+ }
+
+ return false;
+ }
+
+ // FilterAttribute
+ // This method will search for a member based upon the attribute passed in.
+ // filterCriteria -- an Int32 representing the attribute
+ private static bool FilterAttributeImpl(MemberInfo m, object filterCriteria)
+ {
+ // Check that the criteria object is an Integer object
+ if (filterCriteria == null)
+ throw new InvalidFilterCriteriaException(SR.InvalidFilterCriteriaException_CritInt);
+
+ switch (m.MemberType)
+ {
+ case MemberTypes.Constructor:
+ case MemberTypes.Method:
+ {
+ MethodAttributes criteria = 0;
+ try
+ {
+ int i = (int)filterCriteria;
+ criteria = (MethodAttributes)i;
+ }
+ catch
+ {
+ throw new InvalidFilterCriteriaException(SR.InvalidFilterCriteriaException_CritInt);
+ }
+
+
+ MethodAttributes attr;
+ if (m.MemberType == MemberTypes.Method)
+ attr = ((MethodInfo)m).Attributes;
+ else
+ attr = ((ConstructorInfo)m).Attributes;
+
+ if (((criteria & MethodAttributes.MemberAccessMask) != 0) && (attr & MethodAttributes.MemberAccessMask) != (criteria & MethodAttributes.MemberAccessMask))
+ return false;
+ if (((criteria & MethodAttributes.Static) != 0) && (attr & MethodAttributes.Static) == 0)
+ return false;
+ if (((criteria & MethodAttributes.Final) != 0) && (attr & MethodAttributes.Final) == 0)
+ return false;
+ if (((criteria & MethodAttributes.Virtual) != 0) && (attr & MethodAttributes.Virtual) == 0)
+ return false;
+ if (((criteria & MethodAttributes.Abstract) != 0) && (attr & MethodAttributes.Abstract) == 0)
+ return false;
+ if (((criteria & MethodAttributes.SpecialName) != 0) && (attr & MethodAttributes.SpecialName) == 0)
+ return false;
+ return true;
+ }
+ case MemberTypes.Field:
+ {
+ FieldAttributes criteria = 0;
+ try
+ {
+ int i = (int)filterCriteria;
+ criteria = (FieldAttributes)i;
+ }
+ catch
+ {
+ throw new InvalidFilterCriteriaException(SR.InvalidFilterCriteriaException_CritInt);
+ }
+
+ FieldAttributes attr = ((FieldInfo)m).Attributes;
+ if (((criteria & FieldAttributes.FieldAccessMask) != 0) && (attr & FieldAttributes.FieldAccessMask) != (criteria & FieldAttributes.FieldAccessMask))
+ return false;
+ if (((criteria & FieldAttributes.Static) != 0) && (attr & FieldAttributes.Static) == 0)
+ return false;
+ if (((criteria & FieldAttributes.InitOnly) != 0) && (attr & FieldAttributes.InitOnly) == 0)
+ return false;
+ if (((criteria & FieldAttributes.Literal) != 0) && (attr & FieldAttributes.Literal) == 0)
+ return false;
+ if (((criteria & FieldAttributes.NotSerialized) != 0) && (attr & FieldAttributes.NotSerialized) == 0)
+ return false;
+ if (((criteria & FieldAttributes.PinvokeImpl) != 0) && (attr & FieldAttributes.PinvokeImpl) == 0)
+ return false;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // FilterName
+ // This method will filter based upon the name. A partial wildcard
+ // at the end of the string is supported.
+ // filterCriteria -- This is the string name
+ private static bool FilterNameImpl(MemberInfo m, object filterCriteria)
+ {
+ // Check that the criteria object is a String object
+ if (filterCriteria == null || !(filterCriteria is string))
+ throw new InvalidFilterCriteriaException(SR.InvalidFilterCriteriaException_CritString);
+
+ // At the moment this fails if its done on a single line....
+ string str = ((string)filterCriteria);
+ str = str.Trim();
+
+ string name = m.Name;
+ // Get the nested class name only, as opposed to the mangled one
+ if (m.MemberType == MemberTypes.NestedType)
+ name = name.Substring(name.LastIndexOf('+') + 1);
+ // Check to see if this is a prefix or exact match requirement
+ if (str.Length > 0 && str[str.Length - 1] == '*')
+ {
+ str = str.Substring(0, str.Length - 1);
+ return (name.StartsWith(str, StringComparison.Ordinal));
+ }
+
+ return (name.Equals(str));
+ }
+
+ // FilterIgnoreCase
+ // This delegate will do a name search but does it with the
+ // ignore case specified.
+ private static bool FilterNameIgnoreCaseImpl(MemberInfo m, object filterCriteria)
+ {
+ // Check that the criteria object is a String object
+ if (filterCriteria == null || !(filterCriteria is string))
+ throw new InvalidFilterCriteriaException(SR.InvalidFilterCriteriaException_CritString);
+
+ string str = (string)filterCriteria;
+ str = str.Trim();
+
+ string name = m.Name;
+ // Get the nested class name only, as opposed to the mangled one
+ if (m.MemberType == MemberTypes.NestedType)
+ name = name.Substring(name.LastIndexOf('+') + 1);
+ // Check to see if this is a prefix or exact match requirement
+ if (str.Length > 0 && str[str.Length - 1] == '*')
+ {
+ str = str.Substring(0, str.Length - 1);
+ return (string.Compare(name, 0, str, 0, str.Length, StringComparison.OrdinalIgnoreCase) == 0);
+ }
+
+ return (string.Compare(str, name, StringComparison.OrdinalIgnoreCase) == 0);
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Type.cs b/src/System.Private.CoreLib/shared/System/Type.cs
new file mode 100644
index 0000000000..b57baa869f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Type.cs
@@ -0,0 +1,395 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+using System.Reflection;
+using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ public abstract partial class Type : MemberInfo, IReflect
+ {
+ protected Type() { }
+
+ public override MemberTypes MemberType => MemberTypes.TypeInfo;
+
+ public new Type GetType() => base.GetType();
+
+ public abstract string Namespace { get; }
+ public abstract string AssemblyQualifiedName { get; }
+ public abstract string FullName { get; }
+
+ public abstract Assembly Assembly { get; }
+ public abstract new Module Module { get; }
+
+ public bool IsNested => DeclaringType != null;
+ public override Type DeclaringType => null;
+ public virtual MethodBase DeclaringMethod => null;
+
+ public override Type ReflectedType => null;
+ public abstract Type UnderlyingSystemType { get; }
+
+ public virtual bool IsTypeDefinition { get { throw NotImplemented.ByDesign; } }
+ public bool IsArray => IsArrayImpl();
+ protected abstract bool IsArrayImpl();
+ public bool IsByRef => IsByRefImpl();
+ protected abstract bool IsByRefImpl();
+ public bool IsPointer => IsPointerImpl();
+ protected abstract bool IsPointerImpl();
+ public virtual bool IsConstructedGenericType { get { throw NotImplemented.ByDesign; } }
+ public virtual bool IsGenericParameter => false;
+ public virtual bool IsGenericTypeParameter => IsGenericParameter && DeclaringMethod == null;
+ public virtual bool IsGenericMethodParameter => IsGenericParameter && DeclaringMethod != null;
+ public virtual bool IsGenericType => false;
+ public virtual bool IsGenericTypeDefinition => false;
+
+ public virtual bool IsSZArray { get { throw NotImplemented.ByDesign; } }
+ public virtual bool IsVariableBoundArray => IsArray && !IsSZArray;
+
+ public virtual bool IsByRefLike => throw new NotSupportedException(SR.NotSupported_SubclassOverride);
+
+ public bool HasElementType => HasElementTypeImpl();
+ protected abstract bool HasElementTypeImpl();
+ public abstract Type GetElementType();
+
+ public virtual int GetArrayRank() { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+
+ public virtual Type GetGenericTypeDefinition() { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+ public virtual Type[] GenericTypeArguments => (IsGenericType && !IsGenericTypeDefinition) ? GetGenericArguments() : Array.Empty<Type>();
+ public virtual Type[] GetGenericArguments() { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+
+ public virtual int GenericParameterPosition { get { throw new InvalidOperationException(SR.Arg_NotGenericParameter); } }
+ public virtual GenericParameterAttributes GenericParameterAttributes { get { throw new NotSupportedException(); } }
+ public virtual Type[] GetGenericParameterConstraints()
+ {
+ if (!IsGenericParameter)
+ throw new InvalidOperationException(SR.Arg_NotGenericParameter);
+ throw new InvalidOperationException();
+ }
+
+ public TypeAttributes Attributes => GetAttributeFlagsImpl();
+ protected abstract TypeAttributes GetAttributeFlagsImpl();
+
+ public bool IsAbstract => (GetAttributeFlagsImpl() & TypeAttributes.Abstract) != 0;
+ public bool IsImport => (GetAttributeFlagsImpl() & TypeAttributes.Import) != 0;
+ public bool IsSealed => (GetAttributeFlagsImpl() & TypeAttributes.Sealed) != 0;
+ public bool IsSpecialName => (GetAttributeFlagsImpl() & TypeAttributes.SpecialName) != 0;
+
+ public bool IsClass => (GetAttributeFlagsImpl() & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class && !IsValueType;
+
+ public bool IsNestedAssembly => (GetAttributeFlagsImpl() & TypeAttributes.VisibilityMask) == TypeAttributes.NestedAssembly;
+ public bool IsNestedFamANDAssem => (GetAttributeFlagsImpl() & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamANDAssem;
+ public bool IsNestedFamily => (GetAttributeFlagsImpl() & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamily;
+ public bool IsNestedFamORAssem => (GetAttributeFlagsImpl() & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamORAssem;
+ public bool IsNestedPrivate => (GetAttributeFlagsImpl() & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPrivate;
+ public bool IsNestedPublic => (GetAttributeFlagsImpl() & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPublic;
+ public bool IsNotPublic => (GetAttributeFlagsImpl() & TypeAttributes.VisibilityMask) == TypeAttributes.NotPublic;
+ public bool IsPublic => (GetAttributeFlagsImpl() & TypeAttributes.VisibilityMask) == TypeAttributes.Public;
+
+ public bool IsAutoLayout => (GetAttributeFlagsImpl() & TypeAttributes.LayoutMask) == TypeAttributes.AutoLayout;
+ public bool IsExplicitLayout => (GetAttributeFlagsImpl() & TypeAttributes.LayoutMask) == TypeAttributes.ExplicitLayout;
+ public bool IsLayoutSequential => (GetAttributeFlagsImpl() & TypeAttributes.LayoutMask) == TypeAttributes.SequentialLayout;
+
+ public bool IsAnsiClass => (GetAttributeFlagsImpl() & TypeAttributes.StringFormatMask) == TypeAttributes.AnsiClass;
+ public bool IsAutoClass => (GetAttributeFlagsImpl() & TypeAttributes.StringFormatMask) == TypeAttributes.AutoClass;
+ public bool IsUnicodeClass => (GetAttributeFlagsImpl() & TypeAttributes.StringFormatMask) == TypeAttributes.UnicodeClass;
+
+ public bool IsCOMObject => IsCOMObjectImpl();
+ protected abstract bool IsCOMObjectImpl();
+ public bool IsContextful => IsContextfulImpl();
+ protected virtual bool IsContextfulImpl() => false;
+
+ public virtual bool IsCollectible => true;
+
+ public virtual bool IsEnum => IsSubclassOf(typeof(Enum));
+ public bool IsMarshalByRef => IsMarshalByRefImpl();
+ protected virtual bool IsMarshalByRefImpl() => false;
+ public bool IsPrimitive => IsPrimitiveImpl();
+ protected abstract bool IsPrimitiveImpl();
+ public bool IsValueType => IsValueTypeImpl();
+ protected virtual bool IsValueTypeImpl() => IsSubclassOf(typeof(ValueType));
+
+ public virtual bool IsSignatureType => false;
+
+ public virtual bool IsSecurityCritical { get { throw NotImplemented.ByDesign; } }
+ public virtual bool IsSecuritySafeCritical { get { throw NotImplemented.ByDesign; } }
+ public virtual bool IsSecurityTransparent { get { throw NotImplemented.ByDesign; } }
+
+ public virtual StructLayoutAttribute StructLayoutAttribute { get { throw new NotSupportedException(); } }
+ public ConstructorInfo TypeInitializer => GetConstructorImpl(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null);
+
+ public ConstructorInfo GetConstructor(Type[] types) => GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, types, null);
+ public ConstructorInfo GetConstructor(BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) => GetConstructor(bindingAttr, binder, CallingConventions.Any, types, modifiers);
+ public ConstructorInfo GetConstructor(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (types == null)
+ throw new ArgumentNullException(nameof(types));
+ for (int i = 0; i < types.Length; i++)
+ {
+ if (types[i] == null)
+ throw new ArgumentNullException(nameof(types));
+ }
+ return GetConstructorImpl(bindingAttr, binder, callConvention, types, modifiers);
+ }
+ protected abstract ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers);
+
+ public ConstructorInfo[] GetConstructors() => GetConstructors(BindingFlags.Public | BindingFlags.Instance);
+ public abstract ConstructorInfo[] GetConstructors(BindingFlags bindingAttr);
+
+ public EventInfo GetEvent(string name) => GetEvent(name, Type.DefaultLookup);
+ public abstract EventInfo GetEvent(string name, BindingFlags bindingAttr);
+
+ public virtual EventInfo[] GetEvents() => GetEvents(Type.DefaultLookup);
+ public abstract EventInfo[] GetEvents(BindingFlags bindingAttr);
+
+ public FieldInfo GetField(string name) => GetField(name, Type.DefaultLookup);
+ public abstract FieldInfo GetField(string name, BindingFlags bindingAttr);
+
+ public FieldInfo[] GetFields() => GetFields(Type.DefaultLookup);
+ public abstract FieldInfo[] GetFields(BindingFlags bindingAttr);
+
+ public MemberInfo[] GetMember(string name) => GetMember(name, Type.DefaultLookup);
+ public virtual MemberInfo[] GetMember(string name, BindingFlags bindingAttr) => GetMember(name, MemberTypes.All, bindingAttr);
+ public virtual MemberInfo[] GetMember(string name, MemberTypes type, BindingFlags bindingAttr) { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+
+ public MemberInfo[] GetMembers() => GetMembers(Type.DefaultLookup);
+ public abstract MemberInfo[] GetMembers(BindingFlags bindingAttr);
+
+ public MethodInfo GetMethod(string name) => GetMethod(name, Type.DefaultLookup);
+ public MethodInfo GetMethod(string name, BindingFlags bindingAttr)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ return GetMethodImpl(name, bindingAttr, null, CallingConventions.Any, null, null);
+ }
+
+ public MethodInfo GetMethod(string name, Type[] types) => GetMethod(name, types, null);
+ public MethodInfo GetMethod(string name, Type[] types, ParameterModifier[] modifiers) => GetMethod(name, Type.DefaultLookup, null, types, modifiers);
+ public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) => GetMethod(name, bindingAttr, binder, CallingConventions.Any, types, modifiers);
+ public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ if (types == null)
+ throw new ArgumentNullException(nameof(types));
+ for (int i = 0; i < types.Length; i++)
+ {
+ if (types[i] == null)
+ throw new ArgumentNullException(nameof(types));
+ }
+ return GetMethodImpl(name, bindingAttr, binder, callConvention, types, modifiers);
+ }
+
+ protected abstract MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers);
+
+ public MethodInfo GetMethod(string name, int genericParameterCount, Type[] types) => GetMethod(name, genericParameterCount, types, null);
+ public MethodInfo GetMethod(string name, int genericParameterCount, Type[] types, ParameterModifier[] modifiers) => GetMethod(name, genericParameterCount, Type.DefaultLookup, null, types, modifiers);
+ public MethodInfo GetMethod(string name, int genericParameterCount, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) => GetMethod(name, genericParameterCount, bindingAttr, binder, CallingConventions.Any, types, modifiers);
+ public MethodInfo GetMethod(string name, int genericParameterCount, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ if (genericParameterCount < 0)
+ throw new ArgumentException(SR.ArgumentOutOfRange_NeedNonNegNum, nameof(genericParameterCount));
+ if (types == null)
+ throw new ArgumentNullException(nameof(types));
+ for (int i = 0; i < types.Length; i++)
+ {
+ if (types[i] == null)
+ throw new ArgumentNullException(nameof(types));
+ }
+ return GetMethodImpl(name, genericParameterCount, bindingAttr, binder, callConvention, types, modifiers);
+ }
+
+ protected virtual MethodInfo GetMethodImpl(string name, int genericParameterCount, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) => throw new NotSupportedException();
+
+ public MethodInfo[] GetMethods() => GetMethods(Type.DefaultLookup);
+ public abstract MethodInfo[] GetMethods(BindingFlags bindingAttr);
+
+ public Type GetNestedType(string name) => GetNestedType(name, Type.DefaultLookup);
+ public abstract Type GetNestedType(string name, BindingFlags bindingAttr);
+
+ public Type[] GetNestedTypes() => GetNestedTypes(Type.DefaultLookup);
+ public abstract Type[] GetNestedTypes(BindingFlags bindingAttr);
+
+ public PropertyInfo GetProperty(string name) => GetProperty(name, Type.DefaultLookup);
+ public PropertyInfo GetProperty(string name, BindingFlags bindingAttr)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ return GetPropertyImpl(name, bindingAttr, null, null, null, null);
+ }
+
+ public PropertyInfo GetProperty(string name, Type returnType)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ if (returnType == null)
+ throw new ArgumentNullException(nameof(returnType));
+ return GetPropertyImpl(name, Type.DefaultLookup, null, returnType, null, null);
+ }
+
+ public PropertyInfo GetProperty(string name, Type[] types) => GetProperty(name, null, types);
+ public PropertyInfo GetProperty(string name, Type returnType, Type[] types) => GetProperty(name, returnType, types, null);
+ public PropertyInfo GetProperty(string name, Type returnType, Type[] types, ParameterModifier[] modifiers) => GetProperty(name, Type.DefaultLookup, null, returnType, types, modifiers);
+ public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ if (types == null)
+ throw new ArgumentNullException(nameof(types));
+ return GetPropertyImpl(name, bindingAttr, binder, returnType, types, modifiers);
+ }
+
+ protected abstract PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers);
+
+ public PropertyInfo[] GetProperties() => GetProperties(Type.DefaultLookup);
+ public abstract PropertyInfo[] GetProperties(BindingFlags bindingAttr);
+
+ public virtual MemberInfo[] GetDefaultMembers() { throw NotImplemented.ByDesign; }
+
+ public virtual RuntimeTypeHandle TypeHandle { get { throw new NotSupportedException(); } }
+ public static RuntimeTypeHandle GetTypeHandle(object o)
+ {
+ if (o == null)
+ throw new ArgumentNullException(null, SR.Arg_InvalidHandle);
+ Type type = o.GetType();
+ return type.TypeHandle;
+ }
+
+ public static Type[] GetTypeArray(object[] args)
+ {
+ if (args == null)
+ throw new ArgumentNullException(nameof(args));
+
+ Type[] cls = new Type[args.Length];
+ for (int i = 0; i < cls.Length; i++)
+ {
+ if (args[i] == null)
+ throw new ArgumentNullException();
+ cls[i] = args[i].GetType();
+ }
+ return cls;
+ }
+
+ public static TypeCode GetTypeCode(Type type)
+ {
+ if (type == null)
+ return TypeCode.Empty;
+ return type.GetTypeCodeImpl();
+ }
+ protected virtual TypeCode GetTypeCodeImpl()
+ {
+ if (this != UnderlyingSystemType && UnderlyingSystemType != null)
+ return Type.GetTypeCode(UnderlyingSystemType);
+
+ return TypeCode.Object;
+ }
+
+ public abstract Guid GUID { get; }
+
+ public static Type GetTypeFromCLSID(Guid clsid) => GetTypeFromCLSID(clsid, null, throwOnError: false);
+ public static Type GetTypeFromCLSID(Guid clsid, bool throwOnError) => GetTypeFromCLSID(clsid, null, throwOnError: throwOnError);
+ public static Type GetTypeFromCLSID(Guid clsid, string server) => GetTypeFromCLSID(clsid, server, throwOnError: false);
+
+ public static Type GetTypeFromProgID(string progID) => GetTypeFromProgID(progID, null, throwOnError: false);
+ public static Type GetTypeFromProgID(string progID, bool throwOnError) => GetTypeFromProgID(progID, null, throwOnError: throwOnError);
+ public static Type GetTypeFromProgID(string progID, string server) => GetTypeFromProgID(progID, server, throwOnError: false);
+
+ public abstract Type BaseType { get; }
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args) => InvokeMember(name, invokeAttr, binder, target, args, null, null, null);
+
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, CultureInfo culture) => InvokeMember(name, invokeAttr, binder, target, args, null, culture, null);
+ public abstract object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters);
+
+ public Type GetInterface(string name) => GetInterface(name, ignoreCase: false);
+ public abstract Type GetInterface(string name, bool ignoreCase);
+ public abstract Type[] GetInterfaces();
+
+ public virtual InterfaceMapping GetInterfaceMap(Type interfaceType) { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+
+ public virtual bool IsInstanceOfType(object o) => o == null ? false : IsAssignableFrom(o.GetType());
+ public virtual bool IsEquivalentTo(Type other) => this == other;
+
+ public virtual Type GetEnumUnderlyingType()
+ {
+ if (!IsEnum)
+ throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");
+
+ FieldInfo[] fields = GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ if (fields == null || fields.Length != 1)
+ throw new ArgumentException(SR.Argument_InvalidEnum, "enumType");
+
+ return fields[0].FieldType;
+ }
+ public virtual Array GetEnumValues()
+ {
+ if (!IsEnum)
+ throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");
+
+ // We don't support GetEnumValues in the default implementation because we cannot create an array of
+ // a non-runtime type. If there is strong need we can consider returning an object or int64 array.
+ throw NotImplemented.ByDesign;
+ }
+
+ public virtual Type MakeArrayType() { throw new NotSupportedException(); }
+ public virtual Type MakeArrayType(int rank) { throw new NotSupportedException(); }
+ public virtual Type MakeByRefType() { throw new NotSupportedException(); }
+ public virtual Type MakeGenericType(params Type[] typeArguments) { throw new NotSupportedException(SR.NotSupported_SubclassOverride); }
+ public virtual Type MakePointerType() { throw new NotSupportedException(); }
+
+ public static Type MakeGenericMethodParameter(int position)
+ {
+ if (position < 0)
+ throw new ArgumentException(SR.ArgumentOutOfRange_NeedNonNegNum, nameof(position));
+ return new SignatureGenericMethodParameterType(position);
+ }
+
+ public override string ToString() => "Type: " + Name; // Why do we add the "Type: " prefix?
+
+ public override bool Equals(object o) => o == null ? false : Equals(o as Type);
+ public override int GetHashCode()
+ {
+ Type systemType = UnderlyingSystemType;
+ if (!object.ReferenceEquals(systemType, this))
+ return systemType.GetHashCode();
+ return base.GetHashCode();
+ }
+ public virtual bool Equals(Type o) => o == null ? false : object.ReferenceEquals(this.UnderlyingSystemType, o.UnderlyingSystemType);
+
+ public static Type ReflectionOnlyGetType(string typeName, bool throwIfNotFound, bool ignoreCase) { throw new PlatformNotSupportedException(SR.PlatformNotSupported_ReflectionOnly); }
+
+ public static Binder DefaultBinder
+ {
+ get
+ {
+ if (s_defaultBinder == null)
+ {
+ DefaultBinder binder = new DefaultBinder();
+ Interlocked.CompareExchange<Binder>(ref s_defaultBinder, binder, null);
+ }
+ return s_defaultBinder;
+ }
+ }
+
+ private static volatile Binder s_defaultBinder;
+
+ public static readonly char Delimiter = '.';
+ public static readonly Type[] EmptyTypes = Array.Empty<Type>();
+ public static readonly object Missing = System.Reflection.Missing.Value;
+
+ public static readonly MemberFilter FilterAttribute = FilterAttributeImpl;
+ public static readonly MemberFilter FilterName = FilterNameImpl;
+ public static readonly MemberFilter FilterNameIgnoreCase = FilterNameIgnoreCaseImpl;
+
+ private const BindingFlags DefaultLookup = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TypeAccessException.cs b/src/System.Private.CoreLib/shared/System/TypeAccessException.cs
new file mode 100644
index 0000000000..e12a8b08dd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TypeAccessException.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // TypeAccessException derives from TypeLoadException rather than MemberAccessException because in
+ // pre-v4 releases of the runtime TypeLoadException was used in lieu of a TypeAccessException.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class TypeAccessException : TypeLoadException
+ {
+ public TypeAccessException()
+ : base(SR.Arg_TypeAccessException)
+ {
+ HResult = HResults.COR_E_TYPEACCESS;
+ }
+
+ public TypeAccessException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_TYPEACCESS;
+ }
+
+ public TypeAccessException(string message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_TYPEACCESS;
+ }
+
+ protected TypeAccessException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TypeCode.cs b/src/System.Private.CoreLib/shared/System/TypeCode.cs
new file mode 100644
index 0000000000..296198656b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TypeCode.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// The TypeCode enum represents the type code of an object. To obtain the
+// TypeCode for a given object, use the Value.GetTypeCode() method. The
+// TypeCode.Empty value represents a null object reference. The TypeCode.Object
+// value represents an object that doesn't implement the IConvertible interface. The
+// TypeCode.DBNull value represents the database null, which is distinct and
+// different from a null reference. The other type codes represent values that
+// use the given simple type encoding.
+//
+// Note that when an object has a given TypeCode, there is no guarantee that
+// the object is an instance of the corresponding System.XXX value class. For
+// example, an object with the type code TypeCode.Int32 might actually be an
+// instance of a nullable 32-bit integer type (with a value that isn't the
+// database null).
+//
+// There are no type codes for "Missing", "Error", "IDispatch", and "IUnknown".
+// These types of values are instead represented as classes. When the type code
+// of an object is TypeCode.Object, a further instance-of check can be used to
+// determine if the object is one of these values.
+
+namespace System
+{
+ public enum TypeCode
+ {
+ Empty = 0, // Null reference
+ Object = 1, // Instance that isn't a value
+ DBNull = 2, // Database null value
+ Boolean = 3, // Boolean
+ Char = 4, // Unicode character
+ SByte = 5, // Signed 8-bit integer
+ Byte = 6, // Unsigned 8-bit integer
+ Int16 = 7, // Signed 16-bit integer
+ UInt16 = 8, // Unsigned 16-bit integer
+ Int32 = 9, // Signed 32-bit integer
+ UInt32 = 10, // Unsigned 32-bit integer
+ Int64 = 11, // Signed 64-bit integer
+ UInt64 = 12, // Unsigned 64-bit integer
+ Single = 13, // IEEE 32-bit float
+ Double = 14, // IEEE 64-bit double
+ Decimal = 15, // Decimal
+ DateTime = 16, // DateTime
+ String = 18, // Unicode character string
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TypeInitializationException.cs b/src/System.Private.CoreLib/shared/System/TypeInitializationException.cs
new file mode 100644
index 0000000000..4bf2906217
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TypeInitializationException.cs
@@ -0,0 +1,80 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+**
+** Purpose: The exception class to wrap exceptions thrown by
+** a type's class initializer (.cctor). This is sufficiently
+** distinct from a TypeLoadException, which means we couldn't
+** find the type.
+**
+**
+=============================================================================*/
+
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class TypeInitializationException : SystemException
+ {
+ private String _typeName;
+
+ // This exception is not creatable without specifying the
+ // inner exception.
+ private TypeInitializationException()
+ : base(SR.TypeInitialization_Default)
+ {
+ HResult = HResults.COR_E_TYPEINITIALIZATION;
+ }
+
+
+ public TypeInitializationException(String fullTypeName, Exception innerException)
+ : this(fullTypeName, SR.Format(SR.TypeInitialization_Type, fullTypeName), innerException)
+ {
+ }
+
+ // This is called from within the runtime. I believe this is necessary
+ // for Interop only, though it's not particularly useful.
+ internal TypeInitializationException(String message) : base(message)
+ {
+ HResult = HResults.COR_E_TYPEINITIALIZATION;
+ }
+
+ internal TypeInitializationException(String fullTypeName, String message, Exception innerException)
+ : base(message, innerException)
+ {
+ _typeName = fullTypeName;
+ HResult = HResults.COR_E_TYPEINITIALIZATION;
+ }
+
+ internal TypeInitializationException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ _typeName = info.GetString("TypeName");
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("TypeName", TypeName, typeof(string));
+ }
+
+ public String TypeName
+ {
+ get
+ {
+ if (_typeName == null)
+ {
+ return String.Empty;
+ }
+ return _typeName;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/TypeUnloadedException.cs b/src/System.Private.CoreLib/shared/System/TypeUnloadedException.cs
new file mode 100644
index 0000000000..a01ef37a8b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/TypeUnloadedException.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class TypeUnloadedException : SystemException
+ {
+ public TypeUnloadedException()
+ : base(SR.Arg_TypeUnloadedException)
+ {
+ HResult = HResults.COR_E_TYPEUNLOADED;
+ }
+
+ public TypeUnloadedException(string message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_TYPEUNLOADED;
+ }
+
+ public TypeUnloadedException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ HResult = HResults.COR_E_TYPEUNLOADED;
+ }
+
+ protected TypeUnloadedException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/UInt16.cs b/src/System.Private.CoreLib/shared/System/UInt16.cs
new file mode 100644
index 0000000000..3047d18198
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/UInt16.cs
@@ -0,0 +1,289 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [CLSCompliant(false)]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct UInt16 : IComparable, IConvertible, IFormattable, IComparable<UInt16>, IEquatable<UInt16>, ISpanFormattable
+ {
+ private ushort m_value; // Do not rename (binary serialization)
+
+ public const ushort MaxValue = (ushort)0xFFFF;
+ public const ushort MinValue = 0;
+
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type UInt16, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (value is UInt16)
+ {
+ return ((int)m_value - (int)(((UInt16)value).m_value));
+ }
+ throw new ArgumentException(SR.Arg_MustBeUInt16);
+ }
+
+ public int CompareTo(UInt16 value)
+ {
+ return ((int)m_value - (int)value);
+ }
+
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is UInt16))
+ {
+ return false;
+ }
+ return m_value == ((UInt16)obj).m_value;
+ }
+
+ [NonVersionable]
+ public bool Equals(UInt16 obj)
+ {
+ return m_value == obj;
+ }
+
+ // Returns a HashCode for the UInt16
+ public override int GetHashCode()
+ {
+ return (int)m_value;
+ }
+
+ // Converts the current value to a String in base-10 with no extra padding.
+ public override String ToString()
+ {
+ return Number.FormatUInt32(m_value, null, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatUInt32(m_value, null, provider);
+ }
+
+
+ public String ToString(String format)
+ {
+ return Number.FormatUInt32(m_value, format, null);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return Number.FormatUInt32(m_value, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ return Number.TryFormatUInt32(m_value, format, provider, destination, out charsWritten);
+ }
+
+ [CLSCompliant(false)]
+ public static ushort Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
+ }
+
+ [CLSCompliant(false)]
+ public static ushort Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+
+ [CLSCompliant(false)]
+ public static ushort Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static ushort Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Parse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static ushort Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Parse(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ private static ushort Parse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info)
+ {
+ uint i = 0;
+ try
+ {
+ i = Number.ParseUInt32(s, style, info);
+ }
+ catch (OverflowException e)
+ {
+ throw new OverflowException(SR.Overflow_UInt16, e);
+ }
+
+ if (i > MaxValue) throw new OverflowException(SR.Overflow_UInt16);
+ return (ushort)i;
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(String s, out UInt16 result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<char> s, out ushort result)
+ {
+ return TryParse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(String s, NumberStyles style, IFormatProvider provider, out UInt16 result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return TryParse((ReadOnlySpan<char>)s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out ushort result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return TryParse(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ private static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out UInt16 result)
+ {
+ result = 0;
+ UInt32 i;
+ if (!Number.TryParseUInt32(s, style, info, out i))
+ {
+ return false;
+ }
+ if (i > MaxValue)
+ {
+ return false;
+ }
+ result = (UInt16)i;
+ return true;
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.UInt16;
+ }
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(m_value);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "UInt16", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/UInt32.cs b/src/System.Private.CoreLib/shared/System/UInt32.cs
new file mode 100644
index 0000000000..1e33dcf17b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/UInt32.cs
@@ -0,0 +1,265 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [CLSCompliant(false)]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct UInt32 : IComparable, IConvertible, IFormattable, IComparable<UInt32>, IEquatable<UInt32>, ISpanFormattable
+ {
+ private uint m_value; // Do not rename (binary serialization)
+
+ public const uint MaxValue = (uint)0xffffffff;
+ public const uint MinValue = 0U;
+
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type UInt32, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (value is UInt32)
+ {
+ // Need to use compare because subtraction will wrap
+ // to positive for very large neg numbers, etc.
+ uint i = (uint)value;
+ if (m_value < i) return -1;
+ if (m_value > i) return 1;
+ return 0;
+ }
+ throw new ArgumentException(SR.Arg_MustBeUInt32);
+ }
+
+ public int CompareTo(UInt32 value)
+ {
+ // Need to use compare because subtraction will wrap
+ // to positive for very large neg numbers, etc.
+ if (m_value < value) return -1;
+ if (m_value > value) return 1;
+ return 0;
+ }
+
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is UInt32))
+ {
+ return false;
+ }
+ return m_value == ((UInt32)obj).m_value;
+ }
+
+ [NonVersionable]
+ public bool Equals(UInt32 obj)
+ {
+ return m_value == obj;
+ }
+
+ // The absolute value of the int contained.
+ public override int GetHashCode()
+ {
+ return ((int)m_value);
+ }
+
+ // The base 10 representation of the number with no extra padding.
+ public override String ToString()
+ {
+ return Number.FormatUInt32(m_value, null, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatUInt32(m_value, null, provider);
+ }
+
+ public String ToString(String format)
+ {
+ return Number.FormatUInt32(m_value, format, null);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return Number.FormatUInt32(m_value, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ return Number.TryFormatUInt32(m_value, format, provider, destination, out charsWritten);
+ }
+
+ [CLSCompliant(false)]
+ public static uint Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
+ }
+
+ [CLSCompliant(false)]
+ public static uint Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseUInt32(s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+
+ [CLSCompliant(false)]
+ public static uint Parse(String s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static uint Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseUInt32(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static uint Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Number.ParseUInt32(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(String s, out UInt32 result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return Number.TryParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<char> s, out uint result)
+ {
+ return Number.TryParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(String s, NumberStyles style, IFormatProvider provider, out UInt32 result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out uint result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.UInt32;
+ }
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(m_value);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return Convert.ToUInt64(m_value);
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "UInt32", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/UInt64.cs b/src/System.Private.CoreLib/shared/System/UInt64.cs
new file mode 100644
index 0000000000..d30fbe1e42
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/UInt64.cs
@@ -0,0 +1,262 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace System
+{
+ [Serializable]
+ [CLSCompliant(false)]
+ [StructLayout(LayoutKind.Sequential)]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct UInt64 : IComparable, IConvertible, IFormattable, IComparable<UInt64>, IEquatable<UInt64>, ISpanFormattable
+ {
+ private ulong m_value; // Do not rename (binary serialization)
+
+ public const ulong MaxValue = (ulong)0xffffffffffffffffL;
+ public const ulong MinValue = 0x0;
+
+ // Compares this object to another object, returning an integer that
+ // indicates the relationship.
+ // Returns a value less than zero if this object
+ // null is considered to be less than any instance.
+ // If object is not of type UInt64, this method throws an ArgumentException.
+ //
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+ if (value is UInt64)
+ {
+ // Need to use compare because subtraction will wrap
+ // to positive for very large neg numbers, etc.
+ ulong i = (ulong)value;
+ if (m_value < i) return -1;
+ if (m_value > i) return 1;
+ return 0;
+ }
+ throw new ArgumentException(SR.Arg_MustBeUInt64);
+ }
+
+ public int CompareTo(UInt64 value)
+ {
+ // Need to use compare because subtraction will wrap
+ // to positive for very large neg numbers, etc.
+ if (m_value < value) return -1;
+ if (m_value > value) return 1;
+ return 0;
+ }
+
+ public override bool Equals(Object obj)
+ {
+ if (!(obj is UInt64))
+ {
+ return false;
+ }
+ return m_value == ((UInt64)obj).m_value;
+ }
+
+ [NonVersionable]
+ public bool Equals(UInt64 obj)
+ {
+ return m_value == obj;
+ }
+
+ // The value of the lower 32 bits XORed with the uppper 32 bits.
+ public override int GetHashCode()
+ {
+ return ((int)m_value) ^ (int)(m_value >> 32);
+ }
+
+ public override String ToString()
+ {
+ return Number.FormatUInt64(m_value, null, null);
+ }
+
+ public String ToString(IFormatProvider provider)
+ {
+ return Number.FormatUInt64(m_value, null, provider);
+ }
+
+ public String ToString(String format)
+ {
+ return Number.FormatUInt64(m_value, format, null);
+ }
+
+ public String ToString(String format, IFormatProvider provider)
+ {
+ return Number.FormatUInt64(m_value, format, provider);
+ }
+
+ public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+ {
+ return Number.TryFormatUInt64(m_value, format, provider, destination, out charsWritten);
+ }
+
+ [CLSCompliant(false)]
+ public static ulong Parse(String s)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
+ }
+
+ [CLSCompliant(false)]
+ public static ulong Parse(String s, NumberStyles style)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseUInt64(s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ [CLSCompliant(false)]
+ public static ulong Parse(string s, IFormatProvider provider)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static ulong Parse(String s, NumberStyles style, IFormatProvider provider)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseUInt64(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static ulong Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Number.ParseUInt64(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ [CLSCompliant(false)]
+ public static Boolean TryParse(String s, out UInt64 result)
+ {
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return Number.TryParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<char> s, out ulong result)
+ {
+ return Number.TryParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ [CLSCompliant(false)]
+ public static Boolean TryParse(String s, NumberStyles style, IFormatProvider provider, out UInt64 result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+
+ if (s == null)
+ {
+ result = 0;
+ return false;
+ }
+
+ return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out ulong result)
+ {
+ NumberFormatInfo.ValidateParseStyleInteger(style);
+ return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ //
+ // IConvertible implementation
+ //
+
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.UInt64;
+ }
+
+ bool IConvertible.ToBoolean(IFormatProvider provider)
+ {
+ return Convert.ToBoolean(m_value);
+ }
+
+ char IConvertible.ToChar(IFormatProvider provider)
+ {
+ return Convert.ToChar(m_value);
+ }
+
+ sbyte IConvertible.ToSByte(IFormatProvider provider)
+ {
+ return Convert.ToSByte(m_value);
+ }
+
+ byte IConvertible.ToByte(IFormatProvider provider)
+ {
+ return Convert.ToByte(m_value);
+ }
+
+ short IConvertible.ToInt16(IFormatProvider provider)
+ {
+ return Convert.ToInt16(m_value);
+ }
+
+ ushort IConvertible.ToUInt16(IFormatProvider provider)
+ {
+ return Convert.ToUInt16(m_value);
+ }
+
+ int IConvertible.ToInt32(IFormatProvider provider)
+ {
+ return Convert.ToInt32(m_value);
+ }
+
+ uint IConvertible.ToUInt32(IFormatProvider provider)
+ {
+ return Convert.ToUInt32(m_value);
+ }
+
+ long IConvertible.ToInt64(IFormatProvider provider)
+ {
+ return Convert.ToInt64(m_value);
+ }
+
+ ulong IConvertible.ToUInt64(IFormatProvider provider)
+ {
+ return m_value;
+ }
+
+ float IConvertible.ToSingle(IFormatProvider provider)
+ {
+ return Convert.ToSingle(m_value);
+ }
+
+ double IConvertible.ToDouble(IFormatProvider provider)
+ {
+ return Convert.ToDouble(m_value);
+ }
+
+ Decimal IConvertible.ToDecimal(IFormatProvider provider)
+ {
+ return Convert.ToDecimal(m_value);
+ }
+
+ DateTime IConvertible.ToDateTime(IFormatProvider provider)
+ {
+ throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "UInt64", "DateTime"));
+ }
+
+ Object IConvertible.ToType(Type type, IFormatProvider provider)
+ {
+ return Convert.DefaultToType((IConvertible)this, type, provider);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/UIntPtr.cs b/src/System.Private.CoreLib/shared/System/UIntPtr.cs
new file mode 100644
index 0000000000..8b2568fde1
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/UIntPtr.cs
@@ -0,0 +1,221 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Runtime.Versioning;
+
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
+namespace System
+{
+ [Serializable]
+ [CLSCompliant(false)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct UIntPtr : IEquatable<UIntPtr>, ISerializable
+ {
+ private unsafe void* _value; // Do not rename (binary serialization)
+
+ [Intrinsic]
+ public static readonly UIntPtr Zero;
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe UIntPtr(uint value)
+ {
+ _value = (void*)value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe UIntPtr(ulong value)
+ {
+#if BIT64
+ _value = (void*)value;
+#else
+ _value = (void*)checked((uint)value);
+#endif
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe UIntPtr(void* value)
+ {
+ _value = value;
+ }
+
+ private unsafe UIntPtr(SerializationInfo info, StreamingContext context)
+ {
+ ulong l = info.GetUInt64("value");
+
+ if (Size == 4 && l > uint.MaxValue)
+ throw new ArgumentException(SR.Serialization_InvalidPtrValue);
+
+ _value = (void*)l;
+ }
+
+ unsafe void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ throw new ArgumentNullException(nameof(info));
+
+ info.AddValue("value", ToUInt64());
+ }
+
+ public unsafe override bool Equals(Object obj)
+ {
+ if (obj is UIntPtr)
+ {
+ return (_value == ((UIntPtr)obj)._value);
+ }
+ return false;
+ }
+
+ unsafe bool IEquatable<UIntPtr>.Equals(UIntPtr other)
+ {
+ return _value == other._value;
+ }
+
+ public unsafe override int GetHashCode()
+ {
+#if BIT64
+ ulong l = (ulong)_value;
+ return (unchecked((int)l) ^ (int)(l >> 32));
+#else
+ return unchecked((int)_value);
+#endif
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe uint ToUInt32()
+ {
+#if BIT64
+ return checked((uint)_value);
+#else
+ return (uint)_value;
+#endif
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe ulong ToUInt64()
+ {
+ return (ulong)_value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static explicit operator UIntPtr(uint value)
+ {
+ return new UIntPtr(value);
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static explicit operator UIntPtr(ulong value)
+ {
+ return new UIntPtr(value);
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator UIntPtr(void* value)
+ {
+ return new UIntPtr(value);
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator void* (UIntPtr value)
+ {
+ return value._value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator uint(UIntPtr value)
+ {
+#if BIT64
+ return checked((uint)value._value);
+#else
+ return (uint)value._value;
+#endif
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe explicit operator ulong(UIntPtr value)
+ {
+ return (ulong)value._value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe bool operator ==(UIntPtr value1, UIntPtr value2)
+ {
+ return value1._value == value2._value;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe bool operator !=(UIntPtr value1, UIntPtr value2)
+ {
+ return value1._value != value2._value;
+ }
+
+ [NonVersionable]
+ public static UIntPtr Add(UIntPtr pointer, int offset)
+ {
+ return pointer + offset;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe UIntPtr operator +(UIntPtr pointer, int offset)
+ {
+ return new UIntPtr((nuint)pointer._value + (nuint)offset);
+ }
+
+ [NonVersionable]
+ public static UIntPtr Subtract(UIntPtr pointer, int offset)
+ {
+ return pointer - offset;
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public static unsafe UIntPtr operator -(UIntPtr pointer, int offset)
+ {
+ return new UIntPtr((nuint)pointer._value - (nuint)offset);
+ }
+
+ public static unsafe int Size
+ {
+ [Intrinsic]
+ [NonVersionable]
+ get
+ {
+ return sizeof(nuint);
+ }
+ }
+
+ [Intrinsic]
+ [NonVersionable]
+ public unsafe void* ToPointer()
+ {
+ return _value;
+ }
+
+ public unsafe override string ToString()
+ {
+ return ((nuint)_value).ToString(CultureInfo.InvariantCulture);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/UnauthorizedAccessException.cs b/src/System.Private.CoreLib/shared/System/UnauthorizedAccessException.cs
new file mode 100644
index 0000000000..a28f6dd73c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/UnauthorizedAccessException.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: An exception for OS 'access denied' types of
+** errors, including IO and limited security types
+** of errors.
+**
+**
+===========================================================*/
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ // The UnauthorizedAccessException is thrown when access errors
+ // occur from IO or other OS methods.
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public class UnauthorizedAccessException : SystemException
+ {
+ public UnauthorizedAccessException()
+ : base(SR.Arg_UnauthorizedAccessException)
+ {
+ HResult = HResults.COR_E_UNAUTHORIZEDACCESS;
+ }
+
+ public UnauthorizedAccessException(String message)
+ : base(message)
+ {
+ HResult = HResults.COR_E_UNAUTHORIZEDACCESS;
+ }
+
+ public UnauthorizedAccessException(String message, Exception inner)
+ : base(message, inner)
+ {
+ HResult = HResults.COR_E_UNAUTHORIZEDACCESS;
+ }
+
+ protected UnauthorizedAccessException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/UnhandledExceptionEventArgs.cs b/src/System.Private.CoreLib/shared/System/UnhandledExceptionEventArgs.cs
new file mode 100644
index 0000000000..5cde572161
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/UnhandledExceptionEventArgs.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public class UnhandledExceptionEventArgs : EventArgs
+ {
+ private Object _exception;
+ private bool _isTerminating;
+
+ public UnhandledExceptionEventArgs(Object exception, bool isTerminating)
+ {
+ _exception = exception;
+ _isTerminating = isTerminating;
+ }
+
+ public Object ExceptionObject
+ {
+ get { return _exception; }
+ }
+
+ public bool IsTerminating
+ {
+ get { return _isTerminating; }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/UnhandledExceptionEventHandler.cs b/src/System.Private.CoreLib/shared/System/UnhandledExceptionEventHandler.cs
new file mode 100644
index 0000000000..14e31c7bbd
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/UnhandledExceptionEventHandler.cs
@@ -0,0 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System
+{
+ public delegate void UnhandledExceptionEventHandler(Object sender, UnhandledExceptionEventArgs e);
+}
diff --git a/src/System.Private.CoreLib/shared/System/UnitySerializationHolder.cs b/src/System.Private.CoreLib/shared/System/UnitySerializationHolder.cs
new file mode 100644
index 0000000000..53323c32bc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/UnitySerializationHolder.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+ /// <summary>
+ /// Holds Null class for which we guarantee that there is only ever one instance of.
+ /// This only exists for compatibility with .NET Framework.
+ /// </summary>
+ [Serializable]
+ // Needs to be public to support binary serialization compatibility
+ public sealed class UnitySerializationHolder : ISerializable, IObjectReference
+ {
+ internal const int NullUnity = 0x0002;
+ private readonly int _unityType;
+ private readonly string _data;
+
+ /// <summary>
+ /// A helper method that returns the SerializationInfo that a class utilizing
+ /// UnitySerializationHelper should return from a call to GetObjectData. It contains
+ /// the unityType (defined above) and any optional data (used only for the reflection types).
+ /// </summary>
+ internal static void GetUnitySerializationInfo(SerializationInfo info, int unityType)
+ {
+ info.SetType(typeof(UnitySerializationHolder));
+ info.AddValue("Data", null, typeof(string));
+ info.AddValue("UnityType", unityType);
+ info.AddValue("AssemblyName", string.Empty);
+ }
+
+ public UnitySerializationHolder(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ // We are ignoring any other serialization input as we are only concerned about DBNull.
+ // We also store data and use it for erorr logging.
+ _unityType = info.GetInt32("UnityType");
+ _data = info.GetString("Data");
+ }
+
+ public void GetObjectData(SerializationInfo info, StreamingContext context) =>
+ throw new NotSupportedException(SR.NotSupported_UnitySerHolder);
+
+ public object GetRealObject(StreamingContext context)
+ {
+ // We are only support deserializing DBNull and throwing for everything else.
+ if (_unityType != NullUnity)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidUnity, _data ?? "UnityType"));
+ }
+
+ // We are always returning the same DBNull instance.
+ return DBNull.Value;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/ValueTuple.cs b/src/System.Private.CoreLib/shared/System/ValueTuple.cs
new file mode 100644
index 0000000000..d3ae20f416
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/ValueTuple.cs
@@ -0,0 +1,2333 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using HashHelpers = System.Numerics.Hashing.HashHelpers;
+
+namespace System
+{
+ /// <summary>
+ /// Helper so we can call some tuple methods recursively without knowing the underlying types.
+ /// </summary>
+ internal interface IValueTupleInternal : ITuple
+ {
+ int GetHashCode(IEqualityComparer comparer);
+ string ToStringEnd();
+ }
+
+ /// <summary>
+ /// The ValueTuple types (from arity 0 to 8) comprise the runtime implementation that underlies tuples in C# and struct tuples in F#.
+ /// Aside from created via language syntax, they are most easily created via the ValueTuple.Create factory methods.
+ /// The System.ValueTuple types differ from the System.Tuple types in that:
+ /// - they are structs rather than classes,
+ /// - they are mutable rather than readonly, and
+ /// - their members (such as Item1, Item2, etc) are fields rather than properties.
+ /// </summary>
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple
+ : IEquatable<ValueTuple>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple>, IValueTupleInternal, ITuple
+ {
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if <paramref name="obj"/> is a <see cref="ValueTuple"/>.</returns>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple;
+ }
+
+ /// <summary>Returns a value indicating whether this instance is equal to a specified value.</summary>
+ /// <param name="other">An instance to compare to this instance.</param>
+ /// <returns>true if <paramref name="other"/> has the same value as this instance; otherwise, false.</returns>
+ public bool Equals(ValueTuple other)
+ {
+ return true;
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ return other is ValueTuple;
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return 0;
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple other)
+ {
+ return 0;
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return 0;
+ }
+
+ /// <summary>Returns the hash code for this instance.</summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ return 0;
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return 0;
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return 0;
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>()</c>.
+ /// </remarks>
+ public override string ToString()
+ {
+ return "()";
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ return ")";
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 0;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ throw new IndexOutOfRangeException();
+ }
+ }
+
+ /// <summary>Creates a new struct 0-tuple.</summary>
+ /// <returns>A 0-tuple.</returns>
+ public static ValueTuple Create() =>
+ new ValueTuple();
+
+ /// <summary>Creates a new struct 1-tuple, or singleton.</summary>
+ /// <typeparam name="T1">The type of the first component of the tuple.</typeparam>
+ /// <param name="item1">The value of the first component of the tuple.</param>
+ /// <returns>A 1-tuple (singleton) whose value is (item1).</returns>
+ public static ValueTuple<T1> Create<T1>(T1 item1) =>
+ new ValueTuple<T1>(item1);
+
+ /// <summary>Creates a new struct 2-tuple, or pair.</summary>
+ /// <typeparam name="T1">The type of the first component of the tuple.</typeparam>
+ /// <typeparam name="T2">The type of the second component of the tuple.</typeparam>
+ /// <param name="item1">The value of the first component of the tuple.</param>
+ /// <param name="item2">The value of the second component of the tuple.</param>
+ /// <returns>A 2-tuple (pair) whose value is (item1, item2).</returns>
+ public static ValueTuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2) =>
+ new ValueTuple<T1, T2>(item1, item2);
+
+ /// <summary>Creates a new struct 3-tuple, or triple.</summary>
+ /// <typeparam name="T1">The type of the first component of the tuple.</typeparam>
+ /// <typeparam name="T2">The type of the second component of the tuple.</typeparam>
+ /// <typeparam name="T3">The type of the third component of the tuple.</typeparam>
+ /// <param name="item1">The value of the first component of the tuple.</param>
+ /// <param name="item2">The value of the second component of the tuple.</param>
+ /// <param name="item3">The value of the third component of the tuple.</param>
+ /// <returns>A 3-tuple (triple) whose value is (item1, item2, item3).</returns>
+ public static ValueTuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3) =>
+ new ValueTuple<T1, T2, T3>(item1, item2, item3);
+
+ /// <summary>Creates a new struct 4-tuple, or quadruple.</summary>
+ /// <typeparam name="T1">The type of the first component of the tuple.</typeparam>
+ /// <typeparam name="T2">The type of the second component of the tuple.</typeparam>
+ /// <typeparam name="T3">The type of the third component of the tuple.</typeparam>
+ /// <typeparam name="T4">The type of the fourth component of the tuple.</typeparam>
+ /// <param name="item1">The value of the first component of the tuple.</param>
+ /// <param name="item2">The value of the second component of the tuple.</param>
+ /// <param name="item3">The value of the third component of the tuple.</param>
+ /// <param name="item4">The value of the fourth component of the tuple.</param>
+ /// <returns>A 4-tuple (quadruple) whose value is (item1, item2, item3, item4).</returns>
+ public static ValueTuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, T4 item4) =>
+ new ValueTuple<T1, T2, T3, T4>(item1, item2, item3, item4);
+
+ /// <summary>Creates a new struct 5-tuple, or quintuple.</summary>
+ /// <typeparam name="T1">The type of the first component of the tuple.</typeparam>
+ /// <typeparam name="T2">The type of the second component of the tuple.</typeparam>
+ /// <typeparam name="T3">The type of the third component of the tuple.</typeparam>
+ /// <typeparam name="T4">The type of the fourth component of the tuple.</typeparam>
+ /// <typeparam name="T5">The type of the fifth component of the tuple.</typeparam>
+ /// <param name="item1">The value of the first component of the tuple.</param>
+ /// <param name="item2">The value of the second component of the tuple.</param>
+ /// <param name="item3">The value of the third component of the tuple.</param>
+ /// <param name="item4">The value of the fourth component of the tuple.</param>
+ /// <param name="item5">The value of the fifth component of the tuple.</param>
+ /// <returns>A 5-tuple (quintuple) whose value is (item1, item2, item3, item4, item5).</returns>
+ public static ValueTuple<T1, T2, T3, T4, T5> Create<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) =>
+ new ValueTuple<T1, T2, T3, T4, T5>(item1, item2, item3, item4, item5);
+
+ /// <summary>Creates a new struct 6-tuple, or sextuple.</summary>
+ /// <typeparam name="T1">The type of the first component of the tuple.</typeparam>
+ /// <typeparam name="T2">The type of the second component of the tuple.</typeparam>
+ /// <typeparam name="T3">The type of the third component of the tuple.</typeparam>
+ /// <typeparam name="T4">The type of the fourth component of the tuple.</typeparam>
+ /// <typeparam name="T5">The type of the fifth component of the tuple.</typeparam>
+ /// <typeparam name="T6">The type of the sixth component of the tuple.</typeparam>
+ /// <param name="item1">The value of the first component of the tuple.</param>
+ /// <param name="item2">The value of the second component of the tuple.</param>
+ /// <param name="item3">The value of the third component of the tuple.</param>
+ /// <param name="item4">The value of the fourth component of the tuple.</param>
+ /// <param name="item5">The value of the fifth component of the tuple.</param>
+ /// <param name="item6">The value of the sixth component of the tuple.</param>
+ /// <returns>A 6-tuple (sextuple) whose value is (item1, item2, item3, item4, item5, item6).</returns>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6> Create<T1, T2, T3, T4, T5, T6>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) =>
+ new ValueTuple<T1, T2, T3, T4, T5, T6>(item1, item2, item3, item4, item5, item6);
+
+ /// <summary>Creates a new struct 7-tuple, or septuple.</summary>
+ /// <typeparam name="T1">The type of the first component of the tuple.</typeparam>
+ /// <typeparam name="T2">The type of the second component of the tuple.</typeparam>
+ /// <typeparam name="T3">The type of the third component of the tuple.</typeparam>
+ /// <typeparam name="T4">The type of the fourth component of the tuple.</typeparam>
+ /// <typeparam name="T5">The type of the fifth component of the tuple.</typeparam>
+ /// <typeparam name="T6">The type of the sixth component of the tuple.</typeparam>
+ /// <typeparam name="T7">The type of the seventh component of the tuple.</typeparam>
+ /// <param name="item1">The value of the first component of the tuple.</param>
+ /// <param name="item2">The value of the second component of the tuple.</param>
+ /// <param name="item3">The value of the third component of the tuple.</param>
+ /// <param name="item4">The value of the fourth component of the tuple.</param>
+ /// <param name="item5">The value of the fifth component of the tuple.</param>
+ /// <param name="item6">The value of the sixth component of the tuple.</param>
+ /// <param name="item7">The value of the seventh component of the tuple.</param>
+ /// <returns>A 7-tuple (septuple) whose value is (item1, item2, item3, item4, item5, item6, item7).</returns>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7> Create<T1, T2, T3, T4, T5, T6, T7>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) =>
+ new ValueTuple<T1, T2, T3, T4, T5, T6, T7>(item1, item2, item3, item4, item5, item6, item7);
+
+ /// <summary>Creates a new struct 8-tuple, or octuple.</summary>
+ /// <typeparam name="T1">The type of the first component of the tuple.</typeparam>
+ /// <typeparam name="T2">The type of the second component of the tuple.</typeparam>
+ /// <typeparam name="T3">The type of the third component of the tuple.</typeparam>
+ /// <typeparam name="T4">The type of the fourth component of the tuple.</typeparam>
+ /// <typeparam name="T5">The type of the fifth component of the tuple.</typeparam>
+ /// <typeparam name="T6">The type of the sixth component of the tuple.</typeparam>
+ /// <typeparam name="T7">The type of the seventh component of the tuple.</typeparam>
+ /// <typeparam name="T8">The type of the eighth component of the tuple.</typeparam>
+ /// <param name="item1">The value of the first component of the tuple.</param>
+ /// <param name="item2">The value of the second component of the tuple.</param>
+ /// <param name="item3">The value of the third component of the tuple.</param>
+ /// <param name="item4">The value of the fourth component of the tuple.</param>
+ /// <param name="item5">The value of the fifth component of the tuple.</param>
+ /// <param name="item6">The value of the sixth component of the tuple.</param>
+ /// <param name="item7">The value of the seventh component of the tuple.</param>
+ /// <param name="item8">The value of the eighth component of the tuple.</param>
+ /// <returns>An 8-tuple (octuple) whose value is (item1, item2, item3, item4, item5, item6, item7, item8).</returns>
+ public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8) =>
+ new ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>(item1, item2, item3, item4, item5, item6, item7, ValueTuple.Create(item8));
+
+ internal static int CombineHashCodes(int h1, int h2)
+ {
+ return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3)
+ {
+ return HashHelpers.Combine(CombineHashCodes(h1, h2), h3);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4)
+ {
+ return HashHelpers.Combine(CombineHashCodes(h1, h2, h3), h4);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5)
+ {
+ return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4), h5);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6)
+ {
+ return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5), h6);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7)
+ {
+ return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5, h6), h7);
+ }
+
+ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8)
+ {
+ return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5, h6, h7), h8);
+ }
+ }
+
+ /// <summary>Represents a 1-tuple, or singleton, as a value type.</summary>
+ /// <typeparam name="T1">The type of the tuple's only component.</typeparam>
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple<T1>
+ : IEquatable<ValueTuple<T1>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1>>, IValueTupleInternal, ITuple
+ {
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1}"/> instance's first component.
+ /// </summary>
+ public T1 Item1;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueTuple{T1}"/> value type.
+ /// </summary>
+ /// <param name="item1">The value of the tuple's first component.</param>
+ public ValueTuple(T1 item1)
+ {
+ Item1 = item1;
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1}"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
+ /// <list type="bullet">
+ /// <item><description>It is a <see cref="ValueTuple{T1}"/> value type.</description></item>
+ /// <item><description>Its components are of the same types as those of the current instance.</description></item>
+ /// <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
+ /// </list>
+ /// </remarks>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple<T1> && Equals((ValueTuple<T1>)obj);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1}"/>
+ /// instance is equal to a specified <see cref="ValueTuple{T1}"/>.
+ /// </summary>
+ /// <param name="other">The tuple to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its field
+ /// is equal to that of the current instance, using the default comparer for that field's type.
+ /// </remarks>
+ public bool Equals(ValueTuple<T1> other)
+ {
+ return EqualityComparer<T1>.Default.Equals(Item1, other.Item1);
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other == null || !(other is ValueTuple<T1>)) return false;
+
+ var objTuple = (ValueTuple<T1>)other;
+
+ return comparer.Equals(Item1, objTuple.Item1);
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1>)other;
+
+ return Comparer<T1>.Default.Compare(Item1, objTuple.Item1);
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple<T1> other)
+ {
+ return Comparer<T1>.Default.Compare(Item1, other.Item1);
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1>)other;
+
+ return comparer.Compare(Item1, objTuple.Item1);
+ }
+
+ /// <summary>
+ /// Returns the hash code for the current <see cref="ValueTuple{T1}"/> instance.
+ /// </summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ return Item1?.GetHashCode() ?? 0;
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return comparer.GetHashCode(Item1);
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return comparer.GetHashCode(Item1);
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple{T1}"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple{T1}"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>(Item1)</c>,
+ /// where <c>Item1</c> represents the value of <see cref="Item1"/>. If the field is <see langword="null"/>,
+ /// it is represented as <see cref="string.Empty"/>.
+ /// </remarks>
+ public override string ToString()
+ {
+ return "(" + Item1?.ToString() + ")";
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ return Item1?.ToString() + ")";
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 1;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ if (index != 0)
+ {
+ throw new IndexOutOfRangeException();
+ }
+ return Item1;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Represents a 2-tuple, or pair, as a value type.
+ /// </summary>
+ /// <typeparam name="T1">The type of the tuple's first component.</typeparam>
+ /// <typeparam name="T2">The type of the tuple's second component.</typeparam>
+ [Serializable]
+ [StructLayout(LayoutKind.Auto)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple<T1, T2>
+ : IEquatable<ValueTuple<T1, T2>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1, T2>>, IValueTupleInternal, ITuple
+ {
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2}"/> instance's first component.
+ /// </summary>
+ public T1 Item1;
+
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2}"/> instance's first component.
+ /// </summary>
+ public T2 Item2;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueTuple{T1, T2}"/> value type.
+ /// </summary>
+ /// <param name="item1">The value of the tuple's first component.</param>
+ /// <param name="item2">The value of the tuple's second component.</param>
+ public ValueTuple(T1 item1, T2 item2)
+ {
+ Item1 = item1;
+ Item2 = item2;
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2}"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ ///
+ /// <remarks>
+ /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
+ /// <list type="bullet">
+ /// <item><description>It is a <see cref="ValueTuple{T1, T2}"/> value type.</description></item>
+ /// <item><description>Its components are of the same types as those of the current instance.</description></item>
+ /// <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
+ /// </list>
+ /// </remarks>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple<T1, T2> && Equals((ValueTuple<T1, T2>)obj);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2}"/> instance is equal to a specified <see cref="ValueTuple{T1, T2}"/>.
+ /// </summary>
+ /// <param name="other">The tuple to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its fields
+ /// are equal to that of the current instance, using the default comparer for that field's type.
+ /// </remarks>
+ public bool Equals(ValueTuple<T1, T2> other)
+ {
+ return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+ && EqualityComparer<T2>.Default.Equals(Item2, other.Item2);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2}"/> instance is equal to a specified object based on a specified comparison method.
+ /// </summary>
+ /// <param name="other">The object to compare with this instance.</param>
+ /// <param name="comparer">An object that defines the method to use to evaluate whether the two objects are equal.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ ///
+ /// <remarks>
+ /// This member is an explicit interface member implementation. It can be used only when the
+ /// <see cref="ValueTuple{T1, T2}"/> instance is cast to an <see cref="IStructuralEquatable"/> interface.
+ ///
+ /// The <see cref="IEqualityComparer.Equals"/> implementation is called only if <c>other</c> is not <see langword="null"/>,
+ /// and if it can be successfully cast (in C#) or converted (in Visual Basic) to a <see cref="ValueTuple{T1, T2}"/>
+ /// whose components are of the same types as those of the current instance. The IStructuralEquatable.Equals(Object, IEqualityComparer) method
+ /// first passes the <see cref="Item1"/> values of the <see cref="ValueTuple{T1, T2}"/> objects to be compared to the
+ /// <see cref="IEqualityComparer.Equals"/> implementation. If this method call returns <see langword="true"/>, the method is
+ /// called again and passed the <see cref="Item2"/> values of the two <see cref="ValueTuple{T1, T2}"/> instances.
+ /// </remarks>
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other == null || !(other is ValueTuple<T1, T2>)) return false;
+
+ var objTuple = (ValueTuple<T1, T2>)other;
+
+ return comparer.Equals(Item1, objTuple.Item1)
+ && comparer.Equals(Item2, objTuple.Item2);
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return CompareTo((ValueTuple<T1, T2>)other);
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple<T1, T2> other)
+ {
+ int c = Comparer<T1>.Default.Compare(Item1, other.Item1);
+ if (c != 0) return c;
+
+ return Comparer<T2>.Default.Compare(Item2, other.Item2);
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1, T2>)other;
+
+ int c = comparer.Compare(Item1, objTuple.Item1);
+ if (c != 0) return c;
+
+ return comparer.Compare(Item2, objTuple.Item2);
+ }
+
+ /// <summary>
+ /// Returns the hash code for the current <see cref="ValueTuple{T1, T2}"/> instance.
+ /// </summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
+ Item2?.GetHashCode() ?? 0);
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ private int GetHashCodeCore(IEqualityComparer comparer)
+ {
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1),
+ comparer.GetHashCode(Item2));
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple{T1, T2}"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple{T1, T2}"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>(Item1, Item2)</c>,
+ /// where <c>Item1</c> and <c>Item2</c> represent the values of the <see cref="Item1"/>
+ /// and <see cref="Item2"/> fields. If either field value is <see langword="null"/>,
+ /// it is represented as <see cref="String.Empty"/>.
+ /// </remarks>
+ public override string ToString()
+ {
+ return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ")";
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ return Item1?.ToString() + ", " + Item2?.ToString() + ")";
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 2;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Represents a 3-tuple, or triple, as a value type.
+ /// </summary>
+ /// <typeparam name="T1">The type of the tuple's first component.</typeparam>
+ /// <typeparam name="T2">The type of the tuple's second component.</typeparam>
+ /// <typeparam name="T3">The type of the tuple's third component.</typeparam>
+ [Serializable]
+ [StructLayout(LayoutKind.Auto)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple<T1, T2, T3>
+ : IEquatable<ValueTuple<T1, T2, T3>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1, T2, T3>>, IValueTupleInternal, ITuple
+ {
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3}"/> instance's first component.
+ /// </summary>
+ public T1 Item1;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3}"/> instance's second component.
+ /// </summary>
+ public T2 Item2;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3}"/> instance's third component.
+ /// </summary>
+ public T3 Item3;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueTuple{T1, T2, T3}"/> value type.
+ /// </summary>
+ /// <param name="item1">The value of the tuple's first component.</param>
+ /// <param name="item2">The value of the tuple's second component.</param>
+ /// <param name="item3">The value of the tuple's third component.</param>
+ public ValueTuple(T1 item1, T2 item2, T3 item3)
+ {
+ Item1 = item1;
+ Item2 = item2;
+ Item3 = item3;
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3}"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
+ /// <list type="bullet">
+ /// <item><description>It is a <see cref="ValueTuple{T1, T2, T3}"/> value type.</description></item>
+ /// <item><description>Its components are of the same types as those of the current instance.</description></item>
+ /// <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
+ /// </list>
+ /// </remarks>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple<T1, T2, T3> && Equals((ValueTuple<T1, T2, T3>)obj);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3}"/>
+ /// instance is equal to a specified <see cref="ValueTuple{T1, T2, T3}"/>.
+ /// </summary>
+ /// <param name="other">The tuple to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its fields
+ /// are equal to that of the current instance, using the default comparer for that field's type.
+ /// </remarks>
+ public bool Equals(ValueTuple<T1, T2, T3> other)
+ {
+ return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+ && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+ && EqualityComparer<T3>.Default.Equals(Item3, other.Item3);
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other == null || !(other is ValueTuple<T1, T2, T3>)) return false;
+
+ var objTuple = (ValueTuple<T1, T2, T3>)other;
+
+ return comparer.Equals(Item1, objTuple.Item1)
+ && comparer.Equals(Item2, objTuple.Item2)
+ && comparer.Equals(Item3, objTuple.Item3);
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return CompareTo((ValueTuple<T1, T2, T3>)other);
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple<T1, T2, T3> other)
+ {
+ int c = Comparer<T1>.Default.Compare(Item1, other.Item1);
+ if (c != 0) return c;
+
+ c = Comparer<T2>.Default.Compare(Item2, other.Item2);
+ if (c != 0) return c;
+
+ return Comparer<T3>.Default.Compare(Item3, other.Item3);
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1, T2, T3>)other;
+
+ int c = comparer.Compare(Item1, objTuple.Item1);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item2, objTuple.Item2);
+ if (c != 0) return c;
+
+ return comparer.Compare(Item3, objTuple.Item3);
+ }
+
+ /// <summary>
+ /// Returns the hash code for the current <see cref="ValueTuple{T1, T2, T3}"/> instance.
+ /// </summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
+ Item2?.GetHashCode() ?? 0,
+ Item3?.GetHashCode() ?? 0);
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ private int GetHashCodeCore(IEqualityComparer comparer)
+ {
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1),
+ comparer.GetHashCode(Item2),
+ comparer.GetHashCode(Item3));
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple{T1, T2, T3}"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple{T1, T2, T3}"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>(Item1, Item2, Item3)</c>.
+ /// If any field value is <see langword="null"/>, it is represented as <see cref="String.Empty"/>.
+ /// </remarks>
+ public override string ToString()
+ {
+ return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")";
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")";
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 3;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Represents a 4-tuple, or quadruple, as a value type.
+ /// </summary>
+ /// <typeparam name="T1">The type of the tuple's first component.</typeparam>
+ /// <typeparam name="T2">The type of the tuple's second component.</typeparam>
+ /// <typeparam name="T3">The type of the tuple's third component.</typeparam>
+ /// <typeparam name="T4">The type of the tuple's fourth component.</typeparam>
+ [Serializable]
+ [StructLayout(LayoutKind.Auto)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple<T1, T2, T3, T4>
+ : IEquatable<ValueTuple<T1, T2, T3, T4>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1, T2, T3, T4>>, IValueTupleInternal, ITuple
+ {
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4}"/> instance's first component.
+ /// </summary>
+ public T1 Item1;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4}"/> instance's second component.
+ /// </summary>
+ public T2 Item2;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4}"/> instance's third component.
+ /// </summary>
+ public T3 Item3;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4}"/> instance's fourth component.
+ /// </summary>
+ public T4 Item4;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueTuple{T1, T2, T3, T4}"/> value type.
+ /// </summary>
+ /// <param name="item1">The value of the tuple's first component.</param>
+ /// <param name="item2">The value of the tuple's second component.</param>
+ /// <param name="item3">The value of the tuple's third component.</param>
+ /// <param name="item4">The value of the tuple's fourth component.</param>
+ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4)
+ {
+ Item1 = item1;
+ Item2 = item2;
+ Item3 = item3;
+ Item4 = item4;
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4}"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
+ /// <list type="bullet">
+ /// <item><description>It is a <see cref="ValueTuple{T1, T2, T3, T4}"/> value type.</description></item>
+ /// <item><description>Its components are of the same types as those of the current instance.</description></item>
+ /// <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
+ /// </list>
+ /// </remarks>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple<T1, T2, T3, T4> && Equals((ValueTuple<T1, T2, T3, T4>)obj);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4}"/>
+ /// instance is equal to a specified <see cref="ValueTuple{T1, T2, T3, T4}"/>.
+ /// </summary>
+ /// <param name="other">The tuple to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its fields
+ /// are equal to that of the current instance, using the default comparer for that field's type.
+ /// </remarks>
+ public bool Equals(ValueTuple<T1, T2, T3, T4> other)
+ {
+ return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+ && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+ && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+ && EqualityComparer<T4>.Default.Equals(Item4, other.Item4);
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other == null || !(other is ValueTuple<T1, T2, T3, T4>)) return false;
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4>)other;
+
+ return comparer.Equals(Item1, objTuple.Item1)
+ && comparer.Equals(Item2, objTuple.Item2)
+ && comparer.Equals(Item3, objTuple.Item3)
+ && comparer.Equals(Item4, objTuple.Item4);
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return CompareTo((ValueTuple<T1, T2, T3, T4>)other);
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple<T1, T2, T3, T4> other)
+ {
+ int c = Comparer<T1>.Default.Compare(Item1, other.Item1);
+ if (c != 0) return c;
+
+ c = Comparer<T2>.Default.Compare(Item2, other.Item2);
+ if (c != 0) return c;
+
+ c = Comparer<T3>.Default.Compare(Item3, other.Item3);
+ if (c != 0) return c;
+
+ return Comparer<T4>.Default.Compare(Item4, other.Item4);
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4>)other;
+
+ int c = comparer.Compare(Item1, objTuple.Item1);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item2, objTuple.Item2);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item3, objTuple.Item3);
+ if (c != 0) return c;
+
+ return comparer.Compare(Item4, objTuple.Item4);
+ }
+
+ /// <summary>
+ /// Returns the hash code for the current <see cref="ValueTuple{T1, T2, T3, T4}"/> instance.
+ /// </summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
+ Item2?.GetHashCode() ?? 0,
+ Item3?.GetHashCode() ?? 0,
+ Item4?.GetHashCode() ?? 0);
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ private int GetHashCodeCore(IEqualityComparer comparer)
+ {
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1),
+ comparer.GetHashCode(Item2),
+ comparer.GetHashCode(Item3),
+ comparer.GetHashCode(Item4));
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple{T1, T2, T3, T4}"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple{T1, T2, T3, T4}"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>(Item1, Item2, Item3, Item4)</c>.
+ /// If any field value is <see langword="null"/>, it is represented as <see cref="String.Empty"/>.
+ /// </remarks>
+ public override string ToString()
+ {
+ return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")";
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")";
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 4;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Represents a 5-tuple, or quintuple, as a value type.
+ /// </summary>
+ /// <typeparam name="T1">The type of the tuple's first component.</typeparam>
+ /// <typeparam name="T2">The type of the tuple's second component.</typeparam>
+ /// <typeparam name="T3">The type of the tuple's third component.</typeparam>
+ /// <typeparam name="T4">The type of the tuple's fourth component.</typeparam>
+ /// <typeparam name="T5">The type of the tuple's fifth component.</typeparam>
+ [Serializable]
+ [StructLayout(LayoutKind.Auto)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple<T1, T2, T3, T4, T5>
+ : IEquatable<ValueTuple<T1, T2, T3, T4, T5>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1, T2, T3, T4, T5>>, IValueTupleInternal, ITuple
+ {
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance's first component.
+ /// </summary>
+ public T1 Item1;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance's second component.
+ /// </summary>
+ public T2 Item2;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance's third component.
+ /// </summary>
+ public T3 Item3;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance's fourth component.
+ /// </summary>
+ public T4 Item4;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance's fifth component.
+ /// </summary>
+ public T5 Item5;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> value type.
+ /// </summary>
+ /// <param name="item1">The value of the tuple's first component.</param>
+ /// <param name="item2">The value of the tuple's second component.</param>
+ /// <param name="item3">The value of the tuple's third component.</param>
+ /// <param name="item4">The value of the tuple's fourth component.</param>
+ /// <param name="item5">The value of the tuple's fifth component.</param>
+ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
+ {
+ Item1 = item1;
+ Item2 = item2;
+ Item3 = item3;
+ Item4 = item4;
+ Item5 = item5;
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
+ /// <list type="bullet">
+ /// <item><description>It is a <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> value type.</description></item>
+ /// <item><description>Its components are of the same types as those of the current instance.</description></item>
+ /// <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
+ /// </list>
+ /// </remarks>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple<T1, T2, T3, T4, T5> && Equals((ValueTuple<T1, T2, T3, T4, T5>)obj);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4, T5}"/>
+ /// instance is equal to a specified <see cref="ValueTuple{T1, T2, T3, T4, T5}"/>.
+ /// </summary>
+ /// <param name="other">The tuple to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its fields
+ /// are equal to that of the current instance, using the default comparer for that field's type.
+ /// </remarks>
+ public bool Equals(ValueTuple<T1, T2, T3, T4, T5> other)
+ {
+ return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+ && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+ && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+ && EqualityComparer<T4>.Default.Equals(Item4, other.Item4)
+ && EqualityComparer<T5>.Default.Equals(Item5, other.Item5);
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other == null || !(other is ValueTuple<T1, T2, T3, T4, T5>)) return false;
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4, T5>)other;
+
+ return comparer.Equals(Item1, objTuple.Item1)
+ && comparer.Equals(Item2, objTuple.Item2)
+ && comparer.Equals(Item3, objTuple.Item3)
+ && comparer.Equals(Item4, objTuple.Item4)
+ && comparer.Equals(Item5, objTuple.Item5);
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4, T5>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return CompareTo((ValueTuple<T1, T2, T3, T4, T5>)other);
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple<T1, T2, T3, T4, T5> other)
+ {
+ int c = Comparer<T1>.Default.Compare(Item1, other.Item1);
+ if (c != 0) return c;
+
+ c = Comparer<T2>.Default.Compare(Item2, other.Item2);
+ if (c != 0) return c;
+
+ c = Comparer<T3>.Default.Compare(Item3, other.Item3);
+ if (c != 0) return c;
+
+ c = Comparer<T4>.Default.Compare(Item4, other.Item4);
+ if (c != 0) return c;
+
+ return Comparer<T5>.Default.Compare(Item5, other.Item5);
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4, T5>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4, T5>)other;
+
+ int c = comparer.Compare(Item1, objTuple.Item1);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item2, objTuple.Item2);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item3, objTuple.Item3);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item4, objTuple.Item4);
+ if (c != 0) return c;
+
+ return comparer.Compare(Item5, objTuple.Item5);
+ }
+
+ /// <summary>
+ /// Returns the hash code for the current <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance.
+ /// </summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
+ Item2?.GetHashCode() ?? 0,
+ Item3?.GetHashCode() ?? 0,
+ Item4?.GetHashCode() ?? 0,
+ Item5?.GetHashCode() ?? 0);
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ private int GetHashCodeCore(IEqualityComparer comparer)
+ {
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1),
+ comparer.GetHashCode(Item2),
+ comparer.GetHashCode(Item3),
+ comparer.GetHashCode(Item4),
+ comparer.GetHashCode(Item5));
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple{T1, T2, T3, T4, T5}"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>(Item1, Item2, Item3, Item4, Item5)</c>.
+ /// If any field value is <see langword="null"/>, it is represented as <see cref="String.Empty"/>.
+ /// </remarks>
+ public override string ToString()
+ {
+ return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")";
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")";
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 5;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ case 4:
+ return Item5;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Represents a 6-tuple, or sixtuple, as a value type.
+ /// </summary>
+ /// <typeparam name="T1">The type of the tuple's first component.</typeparam>
+ /// <typeparam name="T2">The type of the tuple's second component.</typeparam>
+ /// <typeparam name="T3">The type of the tuple's third component.</typeparam>
+ /// <typeparam name="T4">The type of the tuple's fourth component.</typeparam>
+ /// <typeparam name="T5">The type of the tuple's fifth component.</typeparam>
+ /// <typeparam name="T6">The type of the tuple's sixth component.</typeparam>
+ [Serializable]
+ [StructLayout(LayoutKind.Auto)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple<T1, T2, T3, T4, T5, T6>
+ : IEquatable<ValueTuple<T1, T2, T3, T4, T5, T6>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1, T2, T3, T4, T5, T6>>, IValueTupleInternal, ITuple
+ {
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance's first component.
+ /// </summary>
+ public T1 Item1;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance's second component.
+ /// </summary>
+ public T2 Item2;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance's third component.
+ /// </summary>
+ public T3 Item3;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance's fourth component.
+ /// </summary>
+ public T4 Item4;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance's fifth component.
+ /// </summary>
+ public T5 Item5;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance's sixth component.
+ /// </summary>
+ public T6 Item6;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> value type.
+ /// </summary>
+ /// <param name="item1">The value of the tuple's first component.</param>
+ /// <param name="item2">The value of the tuple's second component.</param>
+ /// <param name="item3">The value of the tuple's third component.</param>
+ /// <param name="item4">The value of the tuple's fourth component.</param>
+ /// <param name="item5">The value of the tuple's fifth component.</param>
+ /// <param name="item6">The value of the tuple's sixth component.</param>
+ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
+ {
+ Item1 = item1;
+ Item2 = item2;
+ Item3 = item3;
+ Item4 = item4;
+ Item5 = item5;
+ Item6 = item6;
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
+ /// <list type="bullet">
+ /// <item><description>It is a <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> value type.</description></item>
+ /// <item><description>Its components are of the same types as those of the current instance.</description></item>
+ /// <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
+ /// </list>
+ /// </remarks>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple<T1, T2, T3, T4, T5, T6> && Equals((ValueTuple<T1, T2, T3, T4, T5, T6>)obj);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/>
+ /// instance is equal to a specified <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/>.
+ /// </summary>
+ /// <param name="other">The tuple to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its fields
+ /// are equal to that of the current instance, using the default comparer for that field's type.
+ /// </remarks>
+ public bool Equals(ValueTuple<T1, T2, T3, T4, T5, T6> other)
+ {
+ return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+ && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+ && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+ && EqualityComparer<T4>.Default.Equals(Item4, other.Item4)
+ && EqualityComparer<T5>.Default.Equals(Item5, other.Item5)
+ && EqualityComparer<T6>.Default.Equals(Item6, other.Item6);
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other == null || !(other is ValueTuple<T1, T2, T3, T4, T5, T6>)) return false;
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4, T5, T6>)other;
+
+ return comparer.Equals(Item1, objTuple.Item1)
+ && comparer.Equals(Item2, objTuple.Item2)
+ && comparer.Equals(Item3, objTuple.Item3)
+ && comparer.Equals(Item4, objTuple.Item4)
+ && comparer.Equals(Item5, objTuple.Item5)
+ && comparer.Equals(Item6, objTuple.Item6);
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4, T5, T6>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return CompareTo((ValueTuple<T1, T2, T3, T4, T5, T6>)other);
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple<T1, T2, T3, T4, T5, T6> other)
+ {
+ int c = Comparer<T1>.Default.Compare(Item1, other.Item1);
+ if (c != 0) return c;
+
+ c = Comparer<T2>.Default.Compare(Item2, other.Item2);
+ if (c != 0) return c;
+
+ c = Comparer<T3>.Default.Compare(Item3, other.Item3);
+ if (c != 0) return c;
+
+ c = Comparer<T4>.Default.Compare(Item4, other.Item4);
+ if (c != 0) return c;
+
+ c = Comparer<T5>.Default.Compare(Item5, other.Item5);
+ if (c != 0) return c;
+
+ return Comparer<T6>.Default.Compare(Item6, other.Item6);
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4, T5, T6>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4, T5, T6>)other;
+
+ int c = comparer.Compare(Item1, objTuple.Item1);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item2, objTuple.Item2);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item3, objTuple.Item3);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item4, objTuple.Item4);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item5, objTuple.Item5);
+ if (c != 0) return c;
+
+ return comparer.Compare(Item6, objTuple.Item6);
+ }
+
+ /// <summary>
+ /// Returns the hash code for the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance.
+ /// </summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
+ Item2?.GetHashCode() ?? 0,
+ Item3?.GetHashCode() ?? 0,
+ Item4?.GetHashCode() ?? 0,
+ Item5?.GetHashCode() ?? 0,
+ Item6?.GetHashCode() ?? 0);
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ private int GetHashCodeCore(IEqualityComparer comparer)
+ {
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1),
+ comparer.GetHashCode(Item2),
+ comparer.GetHashCode(Item3),
+ comparer.GetHashCode(Item4),
+ comparer.GetHashCode(Item5),
+ comparer.GetHashCode(Item6));
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple{T1, T2, T3, T4, T5, T6}"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>(Item1, Item2, Item3, Item4, Item5, Item6)</c>.
+ /// If any field value is <see langword="null"/>, it is represented as <see cref="String.Empty"/>.
+ /// </remarks>
+ public override string ToString()
+ {
+ return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")";
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")";
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 6;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ case 4:
+ return Item5;
+ case 5:
+ return Item6;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Represents a 7-tuple, or sentuple, as a value type.
+ /// </summary>
+ /// <typeparam name="T1">The type of the tuple's first component.</typeparam>
+ /// <typeparam name="T2">The type of the tuple's second component.</typeparam>
+ /// <typeparam name="T3">The type of the tuple's third component.</typeparam>
+ /// <typeparam name="T4">The type of the tuple's fourth component.</typeparam>
+ /// <typeparam name="T5">The type of the tuple's fifth component.</typeparam>
+ /// <typeparam name="T6">The type of the tuple's sixth component.</typeparam>
+ /// <typeparam name="T7">The type of the tuple's seventh component.</typeparam>
+ [Serializable]
+ [StructLayout(LayoutKind.Auto)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple<T1, T2, T3, T4, T5, T6, T7>
+ : IEquatable<ValueTuple<T1, T2, T3, T4, T5, T6, T7>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1, T2, T3, T4, T5, T6, T7>>, IValueTupleInternal, ITuple
+ {
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance's first component.
+ /// </summary>
+ public T1 Item1;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance's second component.
+ /// </summary>
+ public T2 Item2;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance's third component.
+ /// </summary>
+ public T3 Item3;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance's fourth component.
+ /// </summary>
+ public T4 Item4;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance's fifth component.
+ /// </summary>
+ public T5 Item5;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance's sixth component.
+ /// </summary>
+ public T6 Item6;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance's seventh component.
+ /// </summary>
+ public T7 Item7;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> value type.
+ /// </summary>
+ /// <param name="item1">The value of the tuple's first component.</param>
+ /// <param name="item2">The value of the tuple's second component.</param>
+ /// <param name="item3">The value of the tuple's third component.</param>
+ /// <param name="item4">The value of the tuple's fourth component.</param>
+ /// <param name="item5">The value of the tuple's fifth component.</param>
+ /// <param name="item6">The value of the tuple's sixth component.</param>
+ /// <param name="item7">The value of the tuple's seventh component.</param>
+ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
+ {
+ Item1 = item1;
+ Item2 = item2;
+ Item3 = item3;
+ Item4 = item4;
+ Item5 = item5;
+ Item6 = item6;
+ Item7 = item7;
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
+ /// <list type="bullet">
+ /// <item><description>It is a <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> value type.</description></item>
+ /// <item><description>Its components are of the same types as those of the current instance.</description></item>
+ /// <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
+ /// </list>
+ /// </remarks>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple<T1, T2, T3, T4, T5, T6, T7> && Equals((ValueTuple<T1, T2, T3, T4, T5, T6, T7>)obj);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/>
+ /// instance is equal to a specified <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/>.
+ /// </summary>
+ /// <param name="other">The tuple to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its fields
+ /// are equal to that of the current instance, using the default comparer for that field's type.
+ /// </remarks>
+ public bool Equals(ValueTuple<T1, T2, T3, T4, T5, T6, T7> other)
+ {
+ return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+ && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+ && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+ && EqualityComparer<T4>.Default.Equals(Item4, other.Item4)
+ && EqualityComparer<T5>.Default.Equals(Item5, other.Item5)
+ && EqualityComparer<T6>.Default.Equals(Item6, other.Item6)
+ && EqualityComparer<T7>.Default.Equals(Item7, other.Item7);
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other == null || !(other is ValueTuple<T1, T2, T3, T4, T5, T6, T7>)) return false;
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4, T5, T6, T7>)other;
+
+ return comparer.Equals(Item1, objTuple.Item1)
+ && comparer.Equals(Item2, objTuple.Item2)
+ && comparer.Equals(Item3, objTuple.Item3)
+ && comparer.Equals(Item4, objTuple.Item4)
+ && comparer.Equals(Item5, objTuple.Item5)
+ && comparer.Equals(Item6, objTuple.Item6)
+ && comparer.Equals(Item7, objTuple.Item7);
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4, T5, T6, T7>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return CompareTo((ValueTuple<T1, T2, T3, T4, T5, T6, T7>)other);
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple<T1, T2, T3, T4, T5, T6, T7> other)
+ {
+ int c = Comparer<T1>.Default.Compare(Item1, other.Item1);
+ if (c != 0) return c;
+
+ c = Comparer<T2>.Default.Compare(Item2, other.Item2);
+ if (c != 0) return c;
+
+ c = Comparer<T3>.Default.Compare(Item3, other.Item3);
+ if (c != 0) return c;
+
+ c = Comparer<T4>.Default.Compare(Item4, other.Item4);
+ if (c != 0) return c;
+
+ c = Comparer<T5>.Default.Compare(Item5, other.Item5);
+ if (c != 0) return c;
+
+ c = Comparer<T6>.Default.Compare(Item6, other.Item6);
+ if (c != 0) return c;
+
+ return Comparer<T7>.Default.Compare(Item7, other.Item7);
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4, T5, T6, T7>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4, T5, T6, T7>)other;
+
+ int c = comparer.Compare(Item1, objTuple.Item1);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item2, objTuple.Item2);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item3, objTuple.Item3);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item4, objTuple.Item4);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item5, objTuple.Item5);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item6, objTuple.Item6);
+ if (c != 0) return c;
+
+ return comparer.Compare(Item7, objTuple.Item7);
+ }
+
+ /// <summary>
+ /// Returns the hash code for the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance.
+ /// </summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
+ Item2?.GetHashCode() ?? 0,
+ Item3?.GetHashCode() ?? 0,
+ Item4?.GetHashCode() ?? 0,
+ Item5?.GetHashCode() ?? 0,
+ Item6?.GetHashCode() ?? 0,
+ Item7?.GetHashCode() ?? 0);
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ private int GetHashCodeCore(IEqualityComparer comparer)
+ {
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1),
+ comparer.GetHashCode(Item2),
+ comparer.GetHashCode(Item3),
+ comparer.GetHashCode(Item4),
+ comparer.GetHashCode(Item5),
+ comparer.GetHashCode(Item6),
+ comparer.GetHashCode(Item7));
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7}"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>(Item1, Item2, Item3, Item4, Item5, Item6, Item7)</c>.
+ /// If any field value is <see langword="null"/>, it is represented as <see cref="String.Empty"/>.
+ /// </remarks>
+ public override string ToString()
+ {
+ return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")";
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")";
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length => 7;
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ case 4:
+ return Item5;
+ case 5:
+ return Item6;
+ case 6:
+ return Item7;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Represents an 8-tuple, or octuple, as a value type.
+ /// </summary>
+ /// <typeparam name="T1">The type of the tuple's first component.</typeparam>
+ /// <typeparam name="T2">The type of the tuple's second component.</typeparam>
+ /// <typeparam name="T3">The type of the tuple's third component.</typeparam>
+ /// <typeparam name="T4">The type of the tuple's fourth component.</typeparam>
+ /// <typeparam name="T5">The type of the tuple's fifth component.</typeparam>
+ /// <typeparam name="T6">The type of the tuple's sixth component.</typeparam>
+ /// <typeparam name="T7">The type of the tuple's seventh component.</typeparam>
+ /// <typeparam name="TRest">The type of the tuple's eighth component.</typeparam>
+ [Serializable]
+ [StructLayout(LayoutKind.Auto)]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public struct ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>
+ : IEquatable<ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>>, IValueTupleInternal, ITuple
+ where TRest : struct
+ {
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance's first component.
+ /// </summary>
+ public T1 Item1;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance's second component.
+ /// </summary>
+ public T2 Item2;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance's third component.
+ /// </summary>
+ public T3 Item3;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance's fourth component.
+ /// </summary>
+ public T4 Item4;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance's fifth component.
+ /// </summary>
+ public T5 Item5;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance's sixth component.
+ /// </summary>
+ public T6 Item6;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance's seventh component.
+ /// </summary>
+ public T7 Item7;
+ /// <summary>
+ /// The current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance's eighth component.
+ /// </summary>
+ public TRest Rest;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> value type.
+ /// </summary>
+ /// <param name="item1">The value of the tuple's first component.</param>
+ /// <param name="item2">The value of the tuple's second component.</param>
+ /// <param name="item3">The value of the tuple's third component.</param>
+ /// <param name="item4">The value of the tuple's fourth component.</param>
+ /// <param name="item5">The value of the tuple's fifth component.</param>
+ /// <param name="item6">The value of the tuple's sixth component.</param>
+ /// <param name="item7">The value of the tuple's seventh component.</param>
+ /// <param name="rest">The value of the tuple's eight component.</param>
+ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest)
+ {
+ if (!(rest is IValueTupleInternal))
+ {
+ throw new ArgumentException(SR.ArgumentException_ValueTupleLastArgumentNotAValueTuple);
+ }
+
+ Item1 = item1;
+ Item2 = item2;
+ Item3 = item3;
+ Item4 = item4;
+ Item5 = item5;
+ Item6 = item6;
+ Item7 = item7;
+ Rest = rest;
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance is equal to a specified object.
+ /// </summary>
+ /// <param name="obj">The object to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified object; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="obj"/> parameter is considered to be equal to the current instance under the following conditions:
+ /// <list type="bullet">
+ /// <item><description>It is a <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> value type.</description></item>
+ /// <item><description>Its components are of the same types as those of the current instance.</description></item>
+ /// <item><description>Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.</description></item>
+ /// </list>
+ /// </remarks>
+ public override bool Equals(object obj)
+ {
+ return obj is ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> && Equals((ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>)obj);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/>
+ /// instance is equal to a specified <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/>.
+ /// </summary>
+ /// <param name="other">The tuple to compare with this instance.</param>
+ /// <returns><see langword="true"/> if the current instance is equal to the specified tuple; otherwise, <see langword="false"/>.</returns>
+ /// <remarks>
+ /// The <paramref name="other"/> parameter is considered to be equal to the current instance if each of its fields
+ /// are equal to that of the current instance, using the default comparer for that field's type.
+ /// </remarks>
+ public bool Equals(ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> other)
+ {
+ return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+ && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+ && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+ && EqualityComparer<T4>.Default.Equals(Item4, other.Item4)
+ && EqualityComparer<T5>.Default.Equals(Item5, other.Item5)
+ && EqualityComparer<T6>.Default.Equals(Item6, other.Item6)
+ && EqualityComparer<T7>.Default.Equals(Item7, other.Item7)
+ && EqualityComparer<TRest>.Default.Equals(Rest, other.Rest);
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other == null || !(other is ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>)) return false;
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>)other;
+
+ return comparer.Equals(Item1, objTuple.Item1)
+ && comparer.Equals(Item2, objTuple.Item2)
+ && comparer.Equals(Item3, objTuple.Item3)
+ && comparer.Equals(Item4, objTuple.Item4)
+ && comparer.Equals(Item5, objTuple.Item5)
+ && comparer.Equals(Item6, objTuple.Item6)
+ && comparer.Equals(Item7, objTuple.Item7)
+ && comparer.Equals(Rest, objTuple.Rest);
+ }
+
+ int IComparable.CompareTo(object other)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ return CompareTo((ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>)other);
+ }
+
+ /// <summary>Compares this instance to a specified instance and returns an indication of their relative values.</summary>
+ /// <param name="other">An instance to compare.</param>
+ /// <returns>
+ /// A signed number indicating the relative values of this instance and <paramref name="other"/>.
+ /// Returns less than zero if this instance is less than <paramref name="other"/>, zero if this
+ /// instance is equal to <paramref name="other"/>, and greater than zero if this instance is greater
+ /// than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> other)
+ {
+ int c = Comparer<T1>.Default.Compare(Item1, other.Item1);
+ if (c != 0) return c;
+
+ c = Comparer<T2>.Default.Compare(Item2, other.Item2);
+ if (c != 0) return c;
+
+ c = Comparer<T3>.Default.Compare(Item3, other.Item3);
+ if (c != 0) return c;
+
+ c = Comparer<T4>.Default.Compare(Item4, other.Item4);
+ if (c != 0) return c;
+
+ c = Comparer<T5>.Default.Compare(Item5, other.Item5);
+ if (c != 0) return c;
+
+ c = Comparer<T6>.Default.Compare(Item6, other.Item6);
+ if (c != 0) return c;
+
+ c = Comparer<T7>.Default.Compare(Item7, other.Item7);
+ if (c != 0) return c;
+
+ return Comparer<TRest>.Default.Compare(Rest, other.Rest);
+ }
+
+ int IStructuralComparable.CompareTo(object other, IComparer comparer)
+ {
+ if (other == null) return 1;
+
+ if (!(other is ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>))
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentException_ValueTupleIncorrectType, this.GetType().ToString()), nameof(other));
+ }
+
+ var objTuple = (ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>)other;
+
+ int c = comparer.Compare(Item1, objTuple.Item1);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item2, objTuple.Item2);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item3, objTuple.Item3);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item4, objTuple.Item4);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item5, objTuple.Item5);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item6, objTuple.Item6);
+ if (c != 0) return c;
+
+ c = comparer.Compare(Item7, objTuple.Item7);
+ if (c != 0) return c;
+
+ return comparer.Compare(Rest, objTuple.Rest);
+ }
+
+ /// <summary>
+ /// Returns the hash code for the current <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance.
+ /// </summary>
+ /// <returns>A 32-bit signed integer hash code.</returns>
+ public override int GetHashCode()
+ {
+ // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple
+ IValueTupleInternal rest = Rest as IValueTupleInternal;
+ if (rest == null)
+ {
+ return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
+ Item2?.GetHashCode() ?? 0,
+ Item3?.GetHashCode() ?? 0,
+ Item4?.GetHashCode() ?? 0,
+ Item5?.GetHashCode() ?? 0,
+ Item6?.GetHashCode() ?? 0,
+ Item7?.GetHashCode() ?? 0);
+ }
+
+ int size = rest.Length;
+ if (size >= 8) { return rest.GetHashCode(); }
+
+ // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest
+ int k = 8 - size;
+ switch (k)
+ {
+ case 1:
+ return ValueTuple.CombineHashCodes(Item7?.GetHashCode() ?? 0,
+ rest.GetHashCode());
+ case 2:
+ return ValueTuple.CombineHashCodes(Item6?.GetHashCode() ?? 0,
+ Item7?.GetHashCode() ?? 0,
+ rest.GetHashCode());
+ case 3:
+ return ValueTuple.CombineHashCodes(Item5?.GetHashCode() ?? 0,
+ Item6?.GetHashCode() ?? 0,
+ Item7?.GetHashCode() ?? 0,
+ rest.GetHashCode());
+ case 4:
+ return ValueTuple.CombineHashCodes(Item4?.GetHashCode() ?? 0,
+ Item5?.GetHashCode() ?? 0,
+ Item6?.GetHashCode() ?? 0,
+ Item7?.GetHashCode() ?? 0,
+ rest.GetHashCode());
+ case 5:
+ return ValueTuple.CombineHashCodes(Item3?.GetHashCode() ?? 0,
+ Item4?.GetHashCode() ?? 0,
+ Item5?.GetHashCode() ?? 0,
+ Item6?.GetHashCode() ?? 0,
+ Item7?.GetHashCode() ?? 0,
+ rest.GetHashCode());
+ case 6:
+ return ValueTuple.CombineHashCodes(Item2?.GetHashCode() ?? 0,
+ Item3?.GetHashCode() ?? 0,
+ Item4?.GetHashCode() ?? 0,
+ Item5?.GetHashCode() ?? 0,
+ Item6?.GetHashCode() ?? 0,
+ Item7?.GetHashCode() ?? 0,
+ rest.GetHashCode());
+ case 7:
+ case 8:
+ return ValueTuple.CombineHashCodes(Item1?.GetHashCode() ?? 0,
+ Item2?.GetHashCode() ?? 0,
+ Item3?.GetHashCode() ?? 0,
+ Item4?.GetHashCode() ?? 0,
+ Item5?.GetHashCode() ?? 0,
+ Item6?.GetHashCode() ?? 0,
+ Item7?.GetHashCode() ?? 0,
+ rest.GetHashCode());
+ }
+
+ Debug.Fail("Missed all cases for computing ValueTuple hash code");
+ return -1;
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ private int GetHashCodeCore(IEqualityComparer comparer)
+ {
+ // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple
+ IValueTupleInternal rest = Rest as IValueTupleInternal;
+ if (rest == null)
+ {
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), comparer.GetHashCode(Item2), comparer.GetHashCode(Item3),
+ comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6),
+ comparer.GetHashCode(Item7));
+ }
+
+ int size = rest.Length;
+ if (size >= 8) { return rest.GetHashCode(comparer); }
+
+ // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest
+ int k = 8 - size;
+ switch (k)
+ {
+ case 1:
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item7), rest.GetHashCode(comparer));
+ case 2:
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), rest.GetHashCode(comparer));
+ case 3:
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), comparer.GetHashCode(Item7),
+ rest.GetHashCode(comparer));
+ case 4:
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6),
+ comparer.GetHashCode(Item7), rest.GetHashCode(comparer));
+ case 5:
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item3), comparer.GetHashCode(Item4), comparer.GetHashCode(Item5),
+ comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), rest.GetHashCode(comparer));
+ case 6:
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item2), comparer.GetHashCode(Item3), comparer.GetHashCode(Item4),
+ comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), comparer.GetHashCode(Item7),
+ rest.GetHashCode(comparer));
+ case 7:
+ case 8:
+ return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), comparer.GetHashCode(Item2), comparer.GetHashCode(Item3),
+ comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6),
+ comparer.GetHashCode(Item7), rest.GetHashCode(comparer));
+ }
+
+ Debug.Fail("Missed all cases for computing ValueTuple hash code");
+ return -1;
+ }
+
+ int IValueTupleInternal.GetHashCode(IEqualityComparer comparer)
+ {
+ return GetHashCodeCore(comparer);
+ }
+
+ /// <summary>
+ /// Returns a string that represents the value of this <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance.
+ /// </summary>
+ /// <returns>The string representation of this <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> instance.</returns>
+ /// <remarks>
+ /// The string returned by this method takes the form <c>(Item1, Item2, Item3, Item4, Item5, Item6, Item7, Rest)</c>.
+ /// If any field value is <see langword="null"/>, it is represented as <see cref="String.Empty"/>.
+ /// </remarks>
+ public override string ToString()
+ {
+ IValueTupleInternal rest = Rest as IValueTupleInternal;
+ if (rest == null)
+ {
+ return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + Rest.ToString() + ")";
+ }
+ else
+ {
+ return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + rest.ToStringEnd();
+ }
+ }
+
+ string IValueTupleInternal.ToStringEnd()
+ {
+ IValueTupleInternal rest = Rest as IValueTupleInternal;
+ if (rest == null)
+ {
+ return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + Rest.ToString() + ")";
+ }
+ else
+ {
+ return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + rest.ToStringEnd();
+ }
+ }
+
+ /// <summary>
+ /// The number of positions in this data structure.
+ /// </summary>
+ int ITuple.Length
+ {
+ get
+ {
+ IValueTupleInternal rest = Rest as IValueTupleInternal;
+ return rest == null ? 8 : 7 + rest.Length;
+ }
+ }
+
+ /// <summary>
+ /// Get the element at position <param name="index"/>.
+ /// </summary>
+ object ITuple.this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return Item1;
+ case 1:
+ return Item2;
+ case 2:
+ return Item3;
+ case 3:
+ return Item4;
+ case 4:
+ return Item5;
+ case 5:
+ return Item6;
+ case 6:
+ return Item7;
+ }
+
+ IValueTupleInternal rest = Rest as IValueTupleInternal;
+ if (rest == null)
+ {
+ if (index == 7)
+ {
+ return Rest;
+ }
+ throw new IndexOutOfRangeException();
+ }
+ return rest[index - 7];
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Version.cs b/src/System.Private.CoreLib/shared/System/Version.cs
new file mode 100644
index 0000000000..fe086be512
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Version.cs
@@ -0,0 +1,447 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Diagnostics;
+using System.Text;
+
+namespace System
+{
+ // A Version object contains four hierarchical numeric components: major, minor,
+ // build and revision. Build and revision may be unspecified, which is represented
+ // internally as a -1. By definition, an unspecified component matches anything
+ // (both unspecified and specified), and an unspecified component is "less than" any
+ // specified component.
+
+ [Serializable]
+ [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ public sealed class Version : ICloneable, IComparable, IComparable<Version>, IEquatable<Version>, ISpanFormattable
+ {
+ // AssemblyName depends on the order staying the same
+ private readonly int _Major; // Do not rename (binary serialization)
+ private readonly int _Minor; // Do not rename (binary serialization)
+ private readonly int _Build = -1; // Do not rename (binary serialization)
+ private readonly int _Revision = -1; // Do not rename (binary serialization)
+
+ public Version(int major, int minor, int build, int revision)
+ {
+ if (major < 0)
+ throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
+
+ if (minor < 0)
+ throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
+
+ if (build < 0)
+ throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
+
+ if (revision < 0)
+ throw new ArgumentOutOfRangeException(nameof(revision), SR.ArgumentOutOfRange_Version);
+
+ _Major = major;
+ _Minor = minor;
+ _Build = build;
+ _Revision = revision;
+ }
+
+ public Version(int major, int minor, int build)
+ {
+ if (major < 0)
+ throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
+
+ if (minor < 0)
+ throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
+
+ if (build < 0)
+ throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
+
+
+ _Major = major;
+ _Minor = minor;
+ _Build = build;
+ }
+
+ public Version(int major, int minor)
+ {
+ if (major < 0)
+ throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
+
+ if (minor < 0)
+ throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
+
+ _Major = major;
+ _Minor = minor;
+ }
+
+ public Version(String version)
+ {
+ Version v = Version.Parse(version);
+ _Major = v.Major;
+ _Minor = v.Minor;
+ _Build = v.Build;
+ _Revision = v.Revision;
+ }
+
+ public Version()
+ {
+ _Major = 0;
+ _Minor = 0;
+ }
+
+ private Version(Version version)
+ {
+ Debug.Assert(version != null);
+
+ _Major = version._Major;
+ _Minor = version._Minor;
+ _Build = version._Build;
+ _Revision = version._Revision;
+ }
+
+ public object Clone()
+ {
+ return new Version(this);
+ }
+
+ // Properties for setting and getting version numbers
+ public int Major
+ {
+ get { return _Major; }
+ }
+
+ public int Minor
+ {
+ get { return _Minor; }
+ }
+
+ public int Build
+ {
+ get { return _Build; }
+ }
+
+ public int Revision
+ {
+ get { return _Revision; }
+ }
+
+ public short MajorRevision
+ {
+ get { return (short)(_Revision >> 16); }
+ }
+
+ public short MinorRevision
+ {
+ get { return (short)(_Revision & 0xFFFF); }
+ }
+
+ public int CompareTo(Object version)
+ {
+ if (version == null)
+ {
+ return 1;
+ }
+
+ Version v = version as Version;
+ if (v == null)
+ {
+ throw new ArgumentException(SR.Arg_MustBeVersion);
+ }
+
+ return CompareTo(v);
+ }
+
+ public int CompareTo(Version value)
+ {
+ return
+ object.ReferenceEquals(value, this) ? 0 :
+ object.ReferenceEquals(value, null) ? 1 :
+ _Major != value._Major ? (_Major > value._Major ? 1 : -1) :
+ _Minor != value._Minor ? (_Minor > value._Minor ? 1 : -1) :
+ _Build != value._Build ? (_Build > value._Build ? 1 : -1) :
+ _Revision != value._Revision ? (_Revision > value._Revision ? 1 : -1) :
+ 0;
+ }
+
+ public override bool Equals(Object obj)
+ {
+ return Equals(obj as Version);
+ }
+
+ public bool Equals(Version obj)
+ {
+ return object.ReferenceEquals(obj, this) ||
+ (!object.ReferenceEquals(obj, null) &&
+ _Major == obj._Major &&
+ _Minor == obj._Minor &&
+ _Build == obj._Build &&
+ _Revision == obj._Revision);
+ }
+
+ public override int GetHashCode()
+ {
+ // Let's assume that most version numbers will be pretty small and just
+ // OR some lower order bits together.
+
+ int accumulator = 0;
+
+ accumulator |= (_Major & 0x0000000F) << 28;
+ accumulator |= (_Minor & 0x000000FF) << 20;
+ accumulator |= (_Build & 0x000000FF) << 12;
+ accumulator |= (_Revision & 0x00000FFF);
+
+ return accumulator;
+ }
+
+ public override string ToString() =>
+ ToString(DefaultFormatFieldCount);
+
+ public string ToString(int fieldCount) =>
+ fieldCount == 0 ? string.Empty :
+ fieldCount == 1 ? _Major.ToString() :
+ StringBuilderCache.GetStringAndRelease(ToCachedStringBuilder(fieldCount));
+
+ public bool TryFormat(Span<char> destination, out int charsWritten) =>
+ TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
+
+ public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten)
+ {
+ if (fieldCount == 0)
+ {
+ charsWritten = 0;
+ return true;
+ }
+ else if (fieldCount == 1)
+ {
+ return _Major.TryFormat(destination, out charsWritten);
+ }
+
+ StringBuilder sb = ToCachedStringBuilder(fieldCount);
+ if (sb.Length <= destination.Length)
+ {
+ sb.CopyTo(0, destination, sb.Length);
+ StringBuilderCache.Release(sb);
+ charsWritten = sb.Length;
+ return true;
+ }
+
+ StringBuilderCache.Release(sb);
+ charsWritten = 0;
+ return false;
+ }
+
+ bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
+ {
+ // format and provider are ignored.
+ return TryFormat(destination, out charsWritten);
+ }
+
+ private int DefaultFormatFieldCount =>
+ _Build == -1 ? 2 :
+ _Revision == -1 ? 3 :
+ 4;
+
+ private StringBuilder ToCachedStringBuilder(int fieldCount)
+ {
+ // Note: As we always have positive numbers then it is safe to convert the number to string
+ // regardless of the current culture as we'll not have any punctuation marks in the number.
+
+ if (fieldCount == 2)
+ {
+ StringBuilder sb = StringBuilderCache.Acquire();
+ sb.Append(_Major);
+ sb.Append('.');
+ sb.Append(_Minor);
+ return sb;
+ }
+ else
+ {
+ if (_Build == -1)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "2"), nameof(fieldCount));
+ }
+
+ if (fieldCount == 3)
+ {
+ StringBuilder sb = StringBuilderCache.Acquire();
+ sb.Append(_Major);
+ sb.Append('.');
+ sb.Append(_Minor);
+ sb.Append('.');
+ sb.Append(_Build);
+ return sb;
+ }
+
+ if (_Revision == -1)
+ {
+ throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "3"), nameof(fieldCount));
+ }
+
+ if (fieldCount == 4)
+ {
+ StringBuilder sb = StringBuilderCache.Acquire();
+ sb.Append(_Major);
+ sb.Append('.');
+ sb.Append(_Minor);
+ sb.Append('.');
+ sb.Append(_Build);
+ sb.Append('.');
+ sb.Append(_Revision);
+ return sb;
+ }
+
+ throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "4"), nameof(fieldCount));
+ }
+ }
+
+ public static Version Parse(string input)
+ {
+ if (input == null)
+ {
+ throw new ArgumentNullException(nameof(input));
+ }
+
+ return ParseVersion(input.AsSpan(), throwOnFailure: true);
+ }
+
+ public static Version Parse(ReadOnlySpan<char> input) =>
+ ParseVersion(input, throwOnFailure: true);
+
+ public static bool TryParse(string input, out Version result)
+ {
+ if (input == null)
+ {
+ result = null;
+ return false;
+ }
+
+ return (result = ParseVersion(input.AsSpan(), throwOnFailure: false)) != null;
+ }
+
+ public static bool TryParse(ReadOnlySpan<char> input, out Version result) =>
+ (result = ParseVersion(input, throwOnFailure: false)) != null;
+
+ private static Version ParseVersion(ReadOnlySpan<char> input, bool throwOnFailure)
+ {
+ // Find the separator between major and minor. It must exist.
+ int majorEnd = input.IndexOf('.');
+ if (majorEnd < 0)
+ {
+ if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
+ return null;
+ }
+
+ // Find the ends of the optional minor and build portions.
+ // We musn't have any separators after build.
+ int buildEnd = -1;
+ int minorEnd = input.Slice(majorEnd + 1).IndexOf('.');
+ if (minorEnd != -1)
+ {
+ minorEnd += (majorEnd + 1);
+ buildEnd = input.Slice(minorEnd + 1).IndexOf('.');
+ if (buildEnd != -1)
+ {
+ buildEnd += (minorEnd + 1);
+ if (input.Slice(buildEnd + 1).IndexOf('.') != -1)
+ {
+ if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
+ return null;
+ }
+ }
+ }
+
+ int minor, build, revision;
+
+ // Parse the major version
+ if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out int major))
+ {
+ return null;
+ }
+
+ if (minorEnd != -1)
+ {
+ // If there's more than a major and minor, parse the minor, too.
+ if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, out minor))
+ {
+ return null;
+ }
+
+ if (buildEnd != -1)
+ {
+ // major.minor.build.revision
+ return
+ TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, out build) &&
+ TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, out revision) ?
+ new Version(major, minor, build, revision) :
+ null;
+ }
+ else
+ {
+ // major.minor.build
+ return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, out build) ?
+ new Version(major, minor, build) :
+ null;
+ }
+ }
+ else
+ {
+ // major.minor
+ return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, out minor) ?
+ new Version(major, minor) :
+ null;
+ }
+ }
+
+ private static bool TryParseComponent(ReadOnlySpan<char> component, string componentName, bool throwOnFailure, out int parsedComponent)
+ {
+ if (throwOnFailure)
+ {
+ if ((parsedComponent = int.Parse(component, NumberStyles.Integer, CultureInfo.InvariantCulture)) < 0)
+ {
+ throw new ArgumentOutOfRangeException(componentName, SR.ArgumentOutOfRange_Version);
+ }
+ return true;
+ }
+
+ return int.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent) && parsedComponent >= 0;
+ }
+
+ public static bool operator ==(Version v1, Version v2)
+ {
+ if (Object.ReferenceEquals(v1, null))
+ {
+ return Object.ReferenceEquals(v2, null);
+ }
+
+ return v1.Equals(v2);
+ }
+
+ public static bool operator !=(Version v1, Version v2)
+ {
+ return !(v1 == v2);
+ }
+
+ public static bool operator <(Version v1, Version v2)
+ {
+ if ((Object)v1 == null)
+ throw new ArgumentNullException(nameof(v1));
+ return (v1.CompareTo(v2) < 0);
+ }
+
+ public static bool operator <=(Version v1, Version v2)
+ {
+ if ((Object)v1 == null)
+ throw new ArgumentNullException(nameof(v1));
+ return (v1.CompareTo(v2) <= 0);
+ }
+
+ public static bool operator >(Version v1, Version v2)
+ {
+ return (v2 < v1);
+ }
+
+ public static bool operator >=(Version v1, Version v2)
+ {
+ return (v2 <= v1);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Void.cs b/src/System.Private.CoreLib/shared/System/Void.cs
new file mode 100644
index 0000000000..5162e6ad02
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Void.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////////
+// Void
+// This class represents the void return type
+////////////////////////////////////////////////////////////////////////////////
+
+namespace System
+{
+ // This class represents the void return type
+ public struct Void
+ {
+ }
+}